среда, 2 сентября 2009 г.

Известные типы в WCF. Часть 3

Использование конструктора класса DataContractSerializer

Очередным способом задания известных типов является использование соответствующего параметра конструктора класса DataContractSerializer. С одной стороны может показаться, что это наиболее простой способ задания известных типов, ведь для этого нужно всего лишь заполнить массив известных типов и передать этот массив в конструктор. Вот пример, который можно найти в MSDN:

public static void Constructor2()
{
    // Create a generic List of types and add the known types
    // to the collection.
    List<Type> knownTypeList = new List<Type>();
    knownTypeList.Add(typeof(PurchaseOrder));
    knownTypeList.Add(typeof(PurchaseOrderV3));
 
    // Create a DatatContractSerializer with the collection.
    DataContractSerializer ser2 = new DataContractSerializer(
        typeof(Orders), knownTypeList);
 
    // Other code not shown.
}
 
Но остается вопрос в том, как этот способ можно применить на практике. Разработчики WCF предусмотрели множество способов настройки и расширения функциональных возможностей операций, контрактов и служб, для этого предусмотрены интерфейсы IOperationBehavior, IContractBehavior и IServiceBehavior соответственно. Каждый из этих интерфейсов имеют одинаковую функциональность и могут применяться как для сервиса, так для клиента. Существует два способа применения этих интерфейсов на практике: путем программного добавления объектов, реализующих соответствующий интерфейс перед открытием хоста (на стороне сервиса) или созданием прокси (на стороне клиента), либо путем использования пользовательских атрибутов (для этого класс, реализующий один из вышеперечисленных интерфейсов должен быть наследником класса System.Attribute). Наиболее простым способом является создания класса, реализующего все вышеперечисленные интерфейсы, а также являющимся наследником от System.Attribute, что позволит применять его любым способом. Идея реализации сводится к замене поведения с типом DataContractSerializerOperationBehavior на объект собственного класса, возвращающий необходимый сериализатор.
 
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | 
                AttributeTargets.Interface)]
public class ShapeKnownTypeAttribute : Attribute, 
       IOperationBehavior, IServiceBehavior, IContractBehavior
{
    void IOperationBehavior.AddBindingParameters(OperationDescription description, 
       BindingParameterCollection parameters)
    {}
 
    void IOperationBehavior.ApplyClientBehavior(OperationDescription description, 
       System.ServiceModel.Dispatcher.ClientOperation proxy)
    {
        ReplaceDataContractSerializerOperationBehavior(description);
    }
    void IOperationBehavior.ApplyDispatchBehavior(OperationDescription description, 
       System.ServiceModel.Dispatcher.DispatchOperation dispatch)
    {
        ReplaceDataContractSerializerOperationBehavior(description);
    }
    void IOperationBehavior.Validate(OperationDescription description)
    {}
 
 
    void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription,
       ServiceHostBase serviceHostBase,
       System.Collections.ObjectModel.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 ShapeDataContractSerializerOperationBehavior(description));
        }
 
    }
 
    public class ShapeDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior
    {
        public ShapeDataContractSerializerOperationBehavior(OperationDescription description)
            : base(description) { }
 
        public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns, IList<Type> knownTypes)
        {
            var shapeKnownTypes = new List<Type> { typeof(Circle), typeof(Square) };
            return new DataContractSerializer(type, name, ns, shapeKnownTypes);
        }
 
        public override XmlObjectSerializer CreateSerializer(Type type, 
XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes)
        {
            var shapeKnownTypes = new List<Type> { typeof(Circle), typeof(Square) };
            return new DataContractSerializer(type, name, ns, shapeKnownTypes);
        }
    }
 
}
Применение созданного класса следующее:
//На стороне сервиса
[ServiceContract()]
[ShapeKnownType]
public interface IService {...}
 
//На стороне клиента
[GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[ServiceContractAttribute(ConfigurationName="ServiceReference.IService"]
[Server.ShapeKnownType] //ДАННЫЙ АТРИБУТ ДОБАВЛЕН В УЖЕ СГЕНЕРИРОВАННЫЙ КОД!
public interface IService { ... }
 
Применение класса ShapeKnownType к операциям является аналогичным.
 
ПРИМЕЧАНИЕ
 
Использование объектов, реализующих один из вышеперечисленных интерфейсов на стороне сервиса не добавляет информацию об известных типах в метаданные сервиса, поэтому необходимо использование общих типов между клиентом и сервисом, и требует симметричного применения соответствующих атрибутов на стороне клиента.
 
Несмотря на то, что данный способ является самым трудоемким и может потребовать модификацию сгенерированного кода на стороне клиента, он показывает, каким образом можно изменять поведение инфраструктуры WCF и настраивать его по своему усмотрению. Именно таким способом осуществляется применение суррогатных типов (Surrogate Types), применение NetDataContractSerializer вместо DataContractSerializer, применение собственных сериализаторов и решение многих других задач расширения функциональности WCF.

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

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