четверг, 3 сентября 2009 г.

Применение NetDataContractSerializer в WCF

Инфраструктура WCF требует четкого соблюдения контракта между клиентом и сервисом, что запрещает применение типов, чей статический и динамический типы не совпадают. Причем статический и динамический типы должны совпадать не только для аргументов и возвращаемых значений, но и для всех полей и свойств всех объектов, принимающих участие в операциях контрактов данных. Это требование обусловлено тем, что сервис и клиент должны четко следовать установленному протоколу взаимодействия и любое нарушение этого контракта недопустимо.

Частично решить эту проблему можно путем задания известных типов (Known Types), причем сделать это можно самым различным способом (я описывал все эти способы в цикле статей «Известные типы в WCF», часть 1, часть 2, часть 3 и часть 4). Но этот вариант не всегда является удобным решением. Приложение может перейти на WCF с .Net Remoting, в котором используется механизм форматеров для сериализации/десериализации и существует возможность передавать между клиентом и сервером любые сериализируемые сущности. Кроме того, даже в новом приложении, использующем WCF как на стороне сервиса, так и на стороне клиента, могут применяться сложные объекты в качестве аргументов и возвращаемых значений операций контракта. И хотя это не отвечает принципам сервис-ориентированного программирования, разработчики могут сознательно пойти на этот шаг, зная, что их приложение не предусматривает разработку клиентских частей с использованием других технологий.

Невозможность передачи полиморфных объектов связано с тем, что по умолчанию инфраструктура WCF использует DataContractSerializer для сериализации/десериализации объектов. Основной особенностью этого сериализатора является то, что информация о типе не сохраняется при сериализации, а берется из контракта операции. Это вполне естественное поведение, ведь WCF – это реализация промышленного стандарта по созданию сервис-ориентированных приложений, а такие приложения являются нейтральными к технологии, на которых создается сервис или его клиент, а тип параметра относится к специфике CLR, а значит является деталью реализации.

Кроме DataContractSerializer в WCF предусмотрена возможность применения других типов сериализаторов, таких как старый добрый XmlSerializer, NetDataContractSerializer и DataContractJsonSerializer. XmlSerializer для своей работы требует, чтобы сериализируемый класс был публичным, имел конструктор по умолчанию и сериализирует вначале все открытые поля в порядке объявления, а затем свойства (read/write properties) также в порядке объявления. Для применения XmlSerializer вместо DataContractSerializer необходимо пометить отдельный метод сервиса, либо класс сервиса целиком атрибутом XmlSerializerFormatAttribute.

NetDataContractSerializer работает аналогично DataContractSerializer, и имеет прямую и обратную совместимость с DataContractSerializer, что позволяет сериализовать объект одним сериализатором, а десериализовать другим.

NetDataContractSerializer добавляет полную информацию о сериализируемом типе в поток данных, что позволяет загрузить необходимую сборку при десериализации и требует совместного использования типом между сервисом и клиентом. Совместное использование типов (sharing types) нарушает принципы сервис-ориентированного программирования, которые основаны на разделении контрактов (sharing contracts), поэтому разработчики WCF не предусмотрели простого способа использования NetDataContractSerializer в приложениях.

Для возможности использования NetDataContractSerializer c определенной операцией, контрактом или службой целиком, необходимо реализовать один из следующих интерфейсов IOperationBehavior, IContractBehavior и IServiceBehavior соответственно. Каждый из этих интерфейсов имеет одинаковую функциональность и сводится к замене поведения с типом DataContractSerializerOperationBehavior на объект собственного класса, возвращающий необходимый сериализатор. Наиболее простым способ применения является создание класса наследника от System.Attribute и применение созданного атрибута к операции, контракту или сервису целиком.

 
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class |
    AttributeTargets.Interface)]
public class NetDataContractFormatAttribute :
    Attribute, IOperationBehavior,
    IServiceBehavior, IContractBehavior
{
    void IOperationBehavior.AddBindingParameters(
        OperationDescription description, BindingParameterCollection parameters)
    { }
 
    void IOperationBehavior.ApplyClientBehavior(
        OperationDescription description,
        ClientOperation proxy)
    {
        ReplaceDataContractSerializerOperationBehavior(description);
    }
    void IOperationBehavior.ApplyDispatchBehavior(
        OperationDescription description,
        DispatchOperation dispatch)
    {
        ReplaceDataContractSerializerOperationBehavior(description);
    }
    void IOperationBehavior.Validate(
        OperationDescription description)
    { }
 
   void IServiceBehavior.AddBindingParameters(
        ServiceDescription serviceDescription,
        ServiceHostBase serviceHostBase,
        Collection<ServiceEndpoint> endpoints,
        BindingParameterCollection bindingParameters)
    {
        ReplaceDataContractSerializerOperationBehavior(serviceDescription);
    }
 
    void IServiceBehavior.ApplyDispatchBehavior(
        ServiceDescription serviceDescription,
        ServiceHostBase serviceHostBase)
    {
        ReplaceDataContractSerializerOperationBehavior(serviceDescription);
    }
 
    void IServiceBehavior.Validate(
        ServiceDescription serviceDescription,
        ServiceHostBase serviceHostBase)
    { }
 
    void IContractBehavior.AddBindingParameters(
        ContractDescription contractDescription,
        ServiceEndpoint endpoint,
        BindingParameterCollection bindingParameters)
    { }
 
    void IContractBehavior.ApplyClientBehavior(
        ContractDescription contractDescription,
        ServiceEndpoint endpoint,
        ClientRuntime clientRuntime)
    {
        ReplaceDataContractSerializerOperationBehavior(contractDescription);
    }
 
    void IContractBehavior.ApplyDispatchBehavior(
        ContractDescription contractDescription,
        ServiceEndpoint endpoint,
        DispatchRuntime dispatchRuntime)
    {
        ReplaceDataContractSerializerOperationBehavior(contractDescription);
    }
 
    void IContractBehavior.Validate(
        ContractDescription contractDescription,
        ServiceEndpoint endpoint)
    { }
 
 
    private static void ReplaceDataContractSerializerOperationBehavior(
        ServiceDescription description)
    {
        foreach (var endpoint in description.Endpoints)
        {
            ReplaceDataContractSerializerOperationBehavior(endpoint);
        }
    }
 
    private static void ReplaceDataContractSerializerOperationBehavior(
        ContractDescription description)
    {
        foreach (var operation in description.Operations)
        {
            ReplaceDataContractSerializerOperationBehavior(operation);
        }
    }
 
    private static void ReplaceDataContractSerializerOperationBehavior(
        ServiceEndpoint endpoint)
    {
        // ignore mex
        if (endpoint.Contract.ContractType == typeof(IMetadataExchange))
        {
            return;
        }
        ReplaceDataContractSerializerOperationBehavior(endpoint.Contract);
    }
 
    private static void ReplaceDataContractSerializerOperationBehavior(
        OperationDescription description)
    {
        var behavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>();
        if (behavior != null)
        {
            description.Behaviors.Remove(behavior);
            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();
        }
    }
}
 
Пример использования следующий:
 
//Применение к отдельной операции
[OperationContract]
[NetDataContractFormat]
Shape CreateShape(ShapeType shapeType, int id);
 
//Применение к контракту
[ServiceContract(CallbackContract = typeof(IServiceCallback), 
    SessionMode = SessionMode.Required)]
[NetDataContractFormat]
public interface IService { ... }
 
//Применение к сервису
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, 
    ConcurrencyMode = ConcurrencyMode.Multiple, 
    UseSynchronizationContext = false)]
[NetDataContractFormat]
public class Service : IService { ... }
 

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

Комментариев нет:

Отправить комментарий