[WCF] Сериализация и атрибуты
От: Ocenochka  
Дата: 05.07.09 18:58
Оценка:
Я не любитель ставить атрибуты в классах объектной модели по соображениям зависимостей:
не зачем объектам из доменной модели (domain model) знать как они сериализуются — это может менятся
от сервиса к сервису.
Но в таком случае появляется необходимость в интерфейсах сервисов прописывать атрибут:
[ServiceKnownType(typeof(MyDTOType))]
и следить за тем, чтобы при появлении новых объектов в сервисе добавлялись новые записи атрибута, иначе
только в runtime'е вылетит ошибка (как раз при сдаче проекта заказчику ).

Вынесение в базовый интерфейс атрибутов всех возможных объектов ничего не дал — атрибуты
просто не унаследовались, хотя в msdn'е описана реализация атрибута так:


Интересно, нельзя ли получить программный доступ к объекту сериализации, чтобы
инициализировать его кодом, перебрав объекты доменной модели и указав их сериализатору?


зы Сейчас приходится в каждом итенфейсе сервиса прописывать атрибуты всех объектов доменной модели
— не сильно накладно, но хотелось бы чтобы это в одном месте было.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Люблю ставить оценки.
Re: [WCF] Сериализация и атрибуты
От: baranovda Российская Империя  
Дата: 05.07.09 19:00
Оценка: 1 (1)
Здравствуйте, Ocenochka, Вы писали:

См. перегруженный конструктор KnownTypeAttribute
Re[2]: [WCF] Сериализация и атрибуты
От: baranovda Российская Империя  
Дата: 05.07.09 19:03
Оценка: 2 (1)
Здравствуйте, baranovda, Вы писали:

B>Здравствуйте, Ocenochka, Вы писали:


B>См. перегруженный конструктор KnownTypeAttribute


То же самое — см. ServiceKnownTypeAttribute::MethodName

[ServiceKnownType("GetKnownTypes", typeof(Helper))]
[ServiceContract()]
public interface ICatalog
{
}
Re[3]: [WCF] Сериализация и атрибуты
От: Ocenochka  
Дата: 05.07.09 19:08
Оценка:
Здравствуйте, baranovda, Вы писали:

B>>См. перегруженный конструктор KnownTypeAttribute

B>То же самое — см. ServiceKnownTypeAttribute::MethodName
B>
B>[ServiceKnownType("GetKnownTypes", typeof(Helper))]
B>[ServiceContract()]
B>public interface ICatalog
B>{
B>}
B>


Что-то не пойму о чем вы?
Предлагаете в атрибутах каждого интерфейса сервиса указывать список всех его методов?
То же как-то не то.
Хотелось бы одним разом разрешить сериализовать все что угодно.

Вообще, не понятно зачем это по-умолчанию включено... на случай, чтобы не указанный объект случайно не сериализовали?
Может есть какая-то настройка в конфиге, которая разрешит для всех сервисов любые типы сериализовать?
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Люблю ставить оценки.
Re[4]: [WCF] Сериализация и атрибуты
От: baranovda Российская Империя  
Дата: 05.07.09 19:14
Оценка: 3 (1)
Здравствуйте, Ocenochka, Вы писали:

O>Здравствуйте, baranovda, Вы писали:


O> Что-то не пойму о чем вы?

O> Предлагаете в атрибутах каждого интерфейса сервиса указывать список всех его методов?

Зачем. Этот конструктор в runtime дёргает метод вашего самописного хелперного класса, который возвращает массив Well-known Types и подсовывает его сериализатору.
Re[5]: [WCF] Сериализация и атрибуты
От: Ocenochka  
Дата: 05.07.09 19:27
Оценка:
Здравствуйте, baranovda, Вы писали:

O>> Что-то не пойму о чем вы?

O>> Предлагаете в атрибутах каждого интерфейса сервиса указывать список всех его методов?

B>Зачем. Этот конструктор в runtime дёргает метод вашего самописного хелперного класса, который возвращает массив Well-known Types и подсовывает его сериализатору.


А, вот оно как. Большое спасибо, попробую и напишу...
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Люблю ставить оценки.
Re[5]: [WCF] Сериализация и атрибуты
От: Ocenochka  
Дата: 05.07.09 19:46
Оценка:
Здравствуйте, baranovda, Вы писали:

O>> Что-то не пойму о чем вы?

O>> Предлагаете в атрибутах каждого интерфейса сервиса указывать список всех его методов?

B>Зачем. Этот конструктор в runtime дёргает метод вашего самописного хелперного класса, который возвращает массив Well-known Types и подсовывает его сериализатору.


Попробовал — работает! Спасибо!

Не знаю, важна ли сигнатура метода, поэтому погуглив я нашел проверенный вариант:

private static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)
{
IList<Type> allTypes = new List<Type>();

// Заполняем allTypes всеми известными типами.

return allTypes;
}
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Люблю ставить оценки.
Re: [WCF] Сериализация и атрибуты
От: Ocenochka  
Дата: 05.07.09 19:57
Оценка:
Сделал вариант на рефлексии, чтобы о сериализуемых типах можно было вообще забыть:



internal class ToolsWCF
{
private static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)
{
IList<Type> allTypes = new List<Type>();


foreach(Type type in Assembly.GetAssembly(typeof(Client)).GetTypes())
{
if (type.Namespace.Contains("Entities") || type.Namespace.Contains("Collections") )
if (!type.IsGenericType)
allTypes.Add(type);
}

return allTypes;
}
}



Вместо Client подставте любой класс доменной модели.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Люблю ставить оценки.
Re: [WCF] Сериализация и атрибуты
От: AUDev  
Дата: 07.07.09 07:08
Оценка: 2 (1)
Здравствуйте, Ocenochka, Вы писали:

O> Интересно, нельзя ли получить программный доступ к объекту сериализации, чтобы

O> инициализировать его кодом, перебрав объекты доменной модели и указав их сериализатору?


O>зы Сейчас приходится в каждом итенфейсе сервиса прописывать атрибуты всех объектов доменной модели

O>- не сильно накладно, но хотелось бы чтобы это в одном месте было.

Реализуйте класс-аттрибут MyBehavior: Attribute, IContractBehavior;
в имплементации методов ApplyClientBehavior и ApplyDispatchBehavior интерфейса IContractBehavior пройдитесь по операциям и добавьте известные типы в нужные операции (следующий код добавит все типы во все операции; knownTypes — коллекция всех известных типов)

knownTypes.Where(type => !type.IsGenericType)
.ForEach(type => contractDescription.Operations.ForEach(op => op.KnownTypes.Add(type)));

и финал — декорируйте созданным аттрибутом ваш контракт сервиса (интерфейс).
Re: [WCF] Сериализация и атрибуты
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 07.07.09 14:45
Оценка: 2 (1)
Здравствуйте, Ocenochka, Вы писали:

Есть еще вариант о котором никто не упомянул, это использование NetDataContractSerializer вместо DataContractSerializer.
Например вот так:

 public class NetDataContractFormatAttribute : Attribute, IOperationBehavior
    {
        #region IOperationBehavior Members

        public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
        {
        }
        public void ApplyClientBehavior(OperationDescription description, System.ServiceModel.Dispatcher.ClientOperation proxy)
        {
            ReplaceDataContractSerializerOperationBehavior(description);
        }
        public void ApplyDispatchBehavior(OperationDescription description, System.ServiceModel.Dispatcher.DispatchOperation dispatch)
        {
            ReplaceDataContractSerializerOperationBehavior(description);
        }
        public void Validate(OperationDescription description)
        {
        }

        #endregion

        private static void ReplaceDataContractSerializerOperationBehavior(
            OperationDescription description)
        {
            DataContractSerializerOperationBehavior dcsOperationBehavior =
                description.Behaviors.Find<DataContractSerializerOperationBehavior>();
            if (dcsOperationBehavior != null)
            {
                int idx = description.Behaviors.IndexOf(dcsOperationBehavior);
                description.Behaviors.Remove(dcsOperationBehavior);
                description.Behaviors.Insert(idx, new NetDataContractSerializerOperationBehavior(description));
            }
        }
        public class NetDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior
        {
            public NetDataContractSerializerOperationBehavior(
                OperationDescription operationDescription)
                : base(operationDescription) { }
            public override XmlObjectSerializer CreateSerializer(
                Type type, string name, string ns, IList<Type> knownTypes)
            {
                return new NetDataContractSerializer();
            }

            public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes)
            {
                return new NetDataContractSerializer();
            }
        }
    }


Способ применения:

[NetDataContractFormat]
void MyMethod(SomeClass someClass);

Или вам обязательно применение NetDataContractSerializer-а?
Re[2]: [WCF] Сериализация и атрибуты
От: AUDev  
Дата: 07.07.09 22:45
Оценка:
Здравствуйте, SergeyT., Вы писали:

ST>Здравствуйте, Ocenochka, Вы писали:


ST>Есть еще вариант о котором никто не упомянул, это использование NetDataContractSerializer вместо DataContractSerializer.


ST>Или вам обязательно применение NetDataContractSerializer-а?


Просто добавить.
Реализовав это не как IOperationBehavior, а как IContractBehavior, — можно декорировать контракт целиком, а не каждую операцию в отдельности. В имплементации нужно будет пройтись по contractDescription.Operations и сделать то же самое.
Если использование DataContractSerializer не требуется, то использовать можно не только NetDataContractSerializer как показано выше, но и вообще любой кастомный производный от XmlObjectSerializer сериализатор.
Re[2]: [WCF] Сериализация и атрибуты
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 08.07.09 09:42
Оценка: 12 (3) +1
Здравствуйте, AUDev, Вы писали:

Ладно, раз пошла такая пьянка, поговорим немного более подробно. Я думаю, что вы это прекрасно знаете, но видимо топикпастер нет.

В WCF существует три типа сериализатора, которые применяются для маршалинга по значению параметров методов и возвращаемых значений методов службы. Это DataContractSerializer, NetDataContractSerializer и старый добрый XmlSerializer.

По-умолчанию применяется DataContractSerizlier, который при сериализации не помещает информацию о CLR типе во время сериализации и вполне может сериализировать/десериализировать эквивалентные типы (т.е. типы, имеющие одинаковое сериализованное представление, но не обязательно являющиеся одним и тем же типом CLR). Поскольку информация о типе во время выполнения не передается, DataContractSerializer должен каким-то другим способом узнать о возможных полиморфных типах при десериализации. В этом, никто кроме программиста помочь ему не может.

Итак, по-умолчанию WCF использует DataContractSerializer в качестве сериализатора. Это объясняется прежде всего тем, что WCF — это не столько приемник .Net Remoting (как многие могут подумать), сколько реализация Service Oriented Programming, и поэтому предпочтение отдается идиоме разделения контракта (using shared contracts), а не разделению (или совместному использованию) типов (using shared types) (поскольку разделение типов, подразумевает одну платформу). Использование DataContractSerializer по-умолчанию и свидетельствует о подобных предпочтениях команды разработчиков WCF.

Вероятнее всего, что топикпастер столкнулся с проблемой, что во время выполнения в метод сервиса нельзя передать объект производного класса вместо объекта базового класса (или что параметр метода содержит полиморфное свойство). Поскольку, опять же, вероятно, взаимодействие осуществляется между WCF-WCF, вполне понятно желание обойти это досадное ограничение DataContractSerializer-а.

Тогда, если проблема действительно в этом, то стоит рассмотреть все способы решения этой проблемы, и уже после этого выбрать наиболее оптимальный в конкретной ситуации.

Итак, способ №1. Указать перечень известных типов.
У этого способа есть несколько вариантов решения:
1. Использование KnownTypeAttribute для типов DataContract

[DataContract]
[KnownType(typeof(Derived))]
public class Base
{
  [DataMember]
  public int I {get;set;}
}

[DataContract]
public class Derived : Base
{
  [DataMember]
  public int J {get;set;}
}


2. Использование атрибута ServiceKnownTypeAttribute для контракта или операции.

[ServiceContract]
interface IContract
{
  //в этот метод можно передавать объекты класса Derived вместо объекта класса Base
  [OperationContract]
  [ServiceKnownType(typeof(Derived))]
  void Foo(Base b); 
}


или так

[ServiceContract]
[ServiceKnownType(typeof(Derived))]
interface IContract
{
  //в этот и любой другой метод этого класса можно передавать объекты класса Derived вместо объекта класса Base
  [OperationContract]
  void Foo(Base b); 
}


2. Задание KnownTypes во время выполнения с помощью метода, возвращающего набор известных типов
С помощью строкового параметра атрибута KnownTypeAttribute
[DataContract]
[KnownType("GetKnownTypes")]
public class Base
{
  [DataMember]
  public int I {get;set;}
  static Type[] GetKnownTypes()
  {
    return new Type[] {typeof(Derived), typeof(Derived2), ...};
  }
}

Или с помощью строкового параметра атрибута ServiceKnownTypeAttribute

[ServiceContract]
[ServiceKnownType("GetKnownTypes")]
public class Contract
{
  static Type[] GetKnownTypes()
  {
    return new Type[] {typeof(Derived), typeof(Derived2), ...};
  }
}


3. Конфигурирование KnownTypes с помощью конфигурационного файла приложения

<?xml version="1.0"?>
<configuration>
  <system.runtime.serialization>
    <dataContractSerializer>
      <declaredTypes>
        <add type = "Contract,Host,Version=1.0.3419.22708, Culture=neutral">
          <knownType type = "Derived,MyAssembly,Version=1.0.3419.22708, Culture=neutral"/>
          <knownType type = "Derived2,MyAssembly,Version=1.0.3419.22708, Culture=neutral"/>
        </add>
      </declaredTypes>
    </dataContractSerializer>
  </system.runtime.serialization>
  <!--
   Остальная конфигурация сервиса
  -->
</configuration>


Способ №2. Использование NetDataContractSerializer.
NetDataContractSerializer эквивалентен DataContractSerizlier за исключением того, что при сериализации добавляется информацию о CLR-типе. Именно информация о CLR-типе позволяет корректно десериализировать объект на стороне клиента.
Но, возникает одна небольшая проблема, разработчики WCF так сильно не хотели, чтобы их детище использовалось не по назначению (опять же с их точки зрения), что простого способа добавить информацию о том, что некоторый метод или контракт должен использовать NetDataContractSerializer просто не существует.
Вы можете с помощью параметров SvcUtil.exe задать использование XmlSerializer вместо DataContractSerializer, кроме того, вы можете использовать атрибут XmlSerializerFormatAttribute для указания, что метод контракта или метод целиком использовал XmlSerializer, но у вас нет подобного способа использования NetDataContractSerializer-а.
Поэтому, общепринятым способом (под общепринятостью я понимаю примеры в книгах по WCF и на форуме MSDN) решения этой проблемы является один из следующих двух способов (спасибо AUDev за наводку, я использовал только первый способ, но второй несколько проще).
Суть этих способов заключается в написании атрибута, который бы применялся к методу контракта или к контракту целиком, после чего вместо DataContractSerializer-а использовался NetDataContractSerializer.
Первый способ я приводил в пред. посте, вот он:

    public class NetDataContractFormatAttribute : Attribute, IOperationBehavior
    {
        #region IOperationBehavior Members

        public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
        {
        }
        public void ApplyClientBehavior(OperationDescription description, System.ServiceModel.Dispatcher.ClientOperation proxy)
        {
            ReplaceDataContractSerializerOperationBehavior(description);
        }
        public void ApplyDispatchBehavior(OperationDescription description, System.ServiceModel.Dispatcher.DispatchOperation dispatch)
        {
            ReplaceDataContractSerializerOperationBehavior(description);
        }
        public void Validate(OperationDescription description)
        {
        }

        #endregion

        private static void ReplaceDataContractSerializerOperationBehavior(
            OperationDescription description)
        {
            DataContractSerializerOperationBehavior dcsOperationBehavior =
                description.Behaviors.Find<DataContractSerializerOperationBehavior>();
            if (dcsOperationBehavior != null)
            {
                int idx = description.Behaviors.IndexOf(dcsOperationBehavior);
                description.Behaviors.Remove(dcsOperationBehavior);
                description.Behaviors.Insert(idx, new NetDataContractSerializerOperationBehavior(description));
            }
        }
        public class NetDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior
        {
            public NetDataContractSerializerOperationBehavior(
                OperationDescription operationDescription)
                : base(operationDescription) { }
            public override XmlObjectSerializer CreateSerializer(
                Type type, string name, string ns, IList<Type> knownTypes)
            {
                return new NetDataContractSerializer();
            }

            public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes)
            {
                return new NetDataContractSerializer();
            }
        }
    }


Также напомню способ применения:

[NetDataContractFormat]
void MyMethod(SomeClass someClass);


А вот второй способ немного более общий. Атрибут применяется к контракту целиком.

    public class NetDataContractFormat2Attribute : Attribute, IContractBehavior
    {

        public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) 
        {}

        public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            foreach (OperationDescription operation in contractDescription.Operations)
            {
                ReplaceDataContractSerializerOperationBehavior(operation);
            }
        }

        public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
        {
            foreach (OperationDescription operation in contractDescription.Operations)
            {
                ReplaceDataContractSerializerOperationBehavior(operation);
            }

        }

        public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint) 
        {
            foreach (OperationDescription operationDescription in contractDescription.Operations)
            {
                var dcsOperationBehavior = operationDescription.Behaviors.Find<DataContractSerializerOperationBehavior>();
                if (dcsOperationBehavior != null)
                {
                    var index = operationDescription.Behaviors.IndexOf(dcsOperationBehavior);
                    operationDescription.Behaviors[index] = new NetDataContractSerializerOperationBehavior(operationDescription);

                }
            }
        }

        private static void ReplaceDataContractSerializerOperationBehavior(OperationDescription description)
        {
            DataContractSerializerOperationBehavior dcsob = description.Behaviors.Find<DataContractSerializerOperationBehavior>();
            if (dcsob != null)
            {
                description.Behaviors.Remove(dcsob);
                description.Behaviors.Add(new NetDataContractSerializerOperationBehavior(description));
            }

        }

        public class NetDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior
        {
            public NetDataContractSerializerOperationBehavior(OperationDescription description)
                : base(description) { }

            public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns, IList<Type> knownTypes)
            {
                return new NetDataContractSerializer();
            }

            public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes)
            {
                return new NetDataContractSerializer();
            }
        }
    }


Способ применения:

    [ServiceContract(CallbackContract = typeof(IServiceCallback), SessionMode = SessionMode.Required)]
    [NetDataContractFormat2]
    public interface IService
    {
       void MyMethod(SomeClass someClass);
    }

Единственный минус такого подхода — этим атрибутом необходимо помечать класс (или интерфейс) не только на стороне сервера, но и сгенерированный класс на стороне клиента.

З.Ы.
Каждый из вышеприведенных способов имеет свои недостатки и ограничения.
Декларативное описание KnownTypes в конфигурационном файле утомительно, всегда можно что-то пропустить и в конечном счете ваша служба рухнет в один прекрасный момент, но хотя бы остается возможность добавление этих типов без перекомпиляции службы.
Явное задание с помощью атрибутов ServiceKnownTypeAttribute крайне утомительно и подразумевает неусыпный контроль за всеми типами, которые могут передаваться между службой и клиентом.
Использование механизма reflection тоже имеет ряд ограничений, т.к. в некоторых случай критерий использования некоторого типа в качестве одного из свойств параметра может не совпасть с тем критерием фильтрации, который вы используете.
Использование NetDAtaContractSerializer-а также имеет ограничение: атрибут (приведенный выше) должен применяться к службе как на стороне сервера (что естественно), так и на стороне клиента. Т.е. необходима модификация сгенерированного кода (хотя эту модификацию можно и автомотизировать). Но, с другой стороны, использование этого способа оправдывается тем, что если вы забыли пометить этим атрибутом класс или интерфейс на стороне клиента, то ваше приложение рухнет очень быстро. Оно будет падать гораздо чаще, чем в случае, если вы пропустите один из Known типов и ваше приложение будет падать лишь изредка. В случае использование NetDataContractSerializer-а проблему будет определить гораздо проще и любой, даже не шибко грамотный QA ее найдет.

З.Ы.Ы. Может быть я не знаю всех возможностей и обходных путей этой проблемы, но все что знал я изложил и уже вам самим решать, какой из способов более предпочтителен именно для вашей задачи.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.