Инфраструктура 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 { ... }
При использовании этого класса нужно помнить о двух вещах: во-первых, необходимо совместное использование типов между сервисом и клиентом, и во-вторых, необходимо изменять сгенерированный код на клиентской стороне и помечать атрибутами соответствующие методы или интерфейс целиком.
Комментариев нет:
Отправить комментарий