Автор: Sergey Teplyakov

Инфраструктура 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 { ... }

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

Помогла статья? Оцените её!
0 из 5. Общее количество голосов - 0
 

You have no rights to post comments

Дмитрий Крикунов

Публикую статьи, обучающие курсы и новости по программированию: алгоритмам, языкам (С++, Java), параллельному программированию, паттернам и библиотекам (Qt, boost).