数据协定继承

Juval Lowy

下载代码示例

除此之外,还会介绍一些高级 WCF 编程技术。

按引用

也就是说,进行如下声明后,每个 B 对象仍然是 A 对象:

 
class A {...}
class B : A {...}

如果以图形表示,这类似于图 1 中的维恩图,每个 B 实例仍然是 A 实例(但不一定每个 A 都是 B)。

WCF 已知类型和泛型解析程序 KnownType

图 1 包含关系

这意味着可以逐步发展域实体的建模,同时尽量减小对应用程序的影响。

例如,考虑一个业务联系人管理应用程序,按这种建模方式声明一个基类 Contact 和一个派生类 Customer,派生类通过添加客户的特性来细化联系人:

 
class Contact {
  public string FirstName;
  public string LastName;
}

class Customer : Contact {
  public int OrderNumber;
}

应用程序中原为 Contact 类型编写的所有方法都可以接受 Customer 对象,如图 2 所示。

图 2 交换基类和子类引用

 
interface IContactManager {
  void AddContact(Contact contact);
  Contact[] GetContacts();
}

class AddressBook : IContactManager {
  public void AddContact(Contact contact)
  {...}
  ...
}

IContactManager contacts = new AddressBook();

Contact  contact1 = new Contact();
Contact  contact2 = new Customer();
Customer customer = new Customer();

contacts.AddContact(contact1);
contacts.AddContact(contact2);
contacts.AddContact(customer);

为了支持基类与其子类之间的包含关系,在分配新子类实例时,编译器首先分配对象状态的基类部分,然后直接在其后追加子类部分,如图 3 所示。

WCF 已知类型和泛型解析程序 KnownType

图 3 内存中的对象状态层次结构

对于需要引用 Contact 的方法,如果实际提供的是对 Customer 的引用,该方法仍可正常运行,这是因为 Customer 引用也是对 Contact 的引用。 

参数在 WCF 消息中打包并传输给服务,然后在服务中反序列化为本地引用,供服务操作使用。

服务操作向客户端返回结果时,也会进行这一系列操作:结果(或传出参数或异常)首先序列化到回复消息中,然后在客户端重新反序列化。

例如,考虑以下数据协定:

 
[DataContract]
class Contact {...}

[DataContract]
class Customer : Contact {...}

使用这些数据协定可以定义以下服务协定:

 
[ServiceContract]
interface IContactManager {
  [OperationContract]
  void AddContact(Contact contact);

  [OperationContract]
  Contact[] GetContacts();
}

按值封送还可实现远程调用、互操作性、排队调用和长时间运行的工作流。

原因很简单:如果确实向需要基类引用的服务操作传递了子类引用,WCF 如何知道要将派生类部分序列化到消息中呢?

因此,根据定义,下面的 WCF 代码会失败:

 
class ContactManagerClient : ClientBase<IContactManager> : 
  IContactManager{
  ...
}

IContactManager proxy = new ContactManagerClient();
Contact contact = new Customer();

// This will fail: 
contacts.AddContact(contact);

已知类型支持

对于 .NET Framework 3.0,WCF 能够使用 KnownTypeAttribute 解决将基类引用替换为子类引用的问题,KnownTypeAttribute 定义为:

 
[AttributeUsage(AttributeTargets.Struct|AttributeTargets.Class,
  AllowMultiple = true)]
public sealed class KnownTypeAttribute : Attribute {
  public KnownTypeAttribute(Type type);
  //More members
}

通过 KnownType 特性可以为数据协定指定可接受的子类:

 
[DataContract]
  [KnownType(typeof(Customer))]
  class Contact {...}

  [DataContract]
  class Customer : Contact {...}

如果是,则将参数序列化为子类而不是基类。

此外,它还在元数据中包含子类,以便客户端拥有自己的子类定义并能够传递子类而不是基类。

如果需要多个子类,开发人员必须列出所有子类:

 
[DataContract]
[KnownType(typeof(Customer))]
[KnownType(typeof(Person))]
class Contact {...}

[DataContract]
class Person : Contact {...}

WCF 格式化程序使用反射收集数据协定的所有已知类型,然后检查提供的参数是否为任何已知类型。

添加子类不会添加其基类:

 
[DataContract]
[KnownType(typeof(Customer))]
[KnownType(typeof(Person))]
class Contact {...}

[DataContract]
class Customer : Contact {...}

[DataContract]
class Person : Customer {...}

KnownType 特性可能涉及范围太大,因此,WCF 还提供了 ServiceKnownTypeAttribute,可应用于特定操作或特定协定。

最后,在 .NET Framework 3.0 中,WCF 还允许在应用程序配置文件的 system.runtime.serialization 节中列出所需已知类型。 

实际上,随着应用程序建模的逐步进行,可能遇到目前未知的类型,这样,至少必须重新部署应用程序,更可能还要修改基类。

数据协定解析程序

实际上,这样可以截获操作序列化和反序列化参数的尝试,在客户端和服务端都在运行时解析已知类型。

实现编程解析的第一步是从抽象类 DataContractResolver 派生,DataContractResolver 定义为:

 
public abstract class DataContractResolver {
  protected DataContractResolver();
  
  public abstract bool TryResolveType(
    Type type,Type declaredType,
    DataContractResolver knownTypeResolver, 
    out XmlDictionaryString typeName,
    out XmlDictionaryString typeNamespace);

  public abstract Type ResolveName(
    string typeName,string typeNamespace, 
    Type declaredType,
    DataContractResolver knownTypeResolver);
}

WCF 在反序列化过程中提供这些键,以便与类型进行绑定。

将类型名称和命名空间设置为 typeName 和 typeNamespace 输出参数。

如果 TryResolveType 的实现无法解析类型,则应委托给 knownTypeResolver。

这种情况下,WCF 提供类型名称和命名空间标识符,以便将其映射回已知类型。

例如,再次考虑这两个数据协定:

 
[DataContract]
class Contact {...}

[DataContract]
class Customer : Contact {...}

图 4 列出了用于 Customer 类型的简单解析程序。

图 4 CustomerResolver

 
class CustomerResolver : DataContractResolver {
  string Namespace {
    get {
      return typeof(Customer).Namespace ?? "
global";
    }   
  }

  string Name {
    get {
      return typeof(Customer).Name;
    }   
  }

  public override Type ResolveName(
    string typeName,string typeNamespace,
    Type declaredType,
    DataContractResolver knownTypeResolver) {

    if(typeName == Name && typeNamespace == Namespace) {
      return typeof(Customer);
    }
    else {
      return knownTypeResolver.ResolveName(
        typeName,typeNamespace,declaredType,null);
    }
  }

  public override bool TryResolveType(
    Type type,Type declaredType,
    DataContractResolver knownTypeResolver,
    out XmlDictionaryString typeName,
    out XmlDictionaryString typeNamespace) {

    if(type == typeof(Customer)) {
      XmlDictionary dictionary = new XmlDictionary();
      typeName      = dictionary.Add(Name);
      typeNamespace = dictionary.Add(Namespace);
      return true;
    }
    else {
      return knownTypeResolver.TryResolveType(
        type,declaredType,null,out typeName,out typeNamespace);
    }
  }
}

ServiceEndpoint 类有一个名为 Contract 且类型为 ContractDescription 的属性:

 
public class ServiceEndpoint {
  public ContractDescription Contract
  {get;set;}

  // More members
}

ContractDescription 有一个操作描述集合,协定上每个操作都有一个 OperationDescription 实例:

 
public class ContractDescription {
  public OperationDescriptionCollection Operations
  {get;}

  // More members
}
public class OperationDescriptionCollection : 
  Collection<OperationDescription>
{...}

每个 OperationDescription 都有一个类型为 IOperationBehavior 的操作行为集合:

 
public class OperationDescription {
  public KeyedByTypeCollection<IOperationBehavior> Behaviors
  {get;}
  // More members
}

在其行为集合中,每个操作始终有一个名为 DataContractSerializerOperationBehavior,该行为有一个 DataContractResolver 属性:

 
public class DataContractSerializerOperationBehavior : 
  IOperationBehavior,...
{
  public DataContractResolver DataContractResolver
  {get;set}
  // More members
}

若要在主机端安装解析程序,必须循环访问主机维护的服务描述中的终结点集合:

 
public class ServiceHost : ServiceHostBase {...}

public abstract class ServiceHostBase : ...
{
  public ServiceDescription Description
  {get;}
  // More members
}

public class ServiceDescription {   
  public ServiceEndpointCollection Endpoints
  {get;}
  // More members
}

public class ServiceEndpointCollection : 
  Collection<ServiceEndpoint> {...}

假设有以下服务定义并使用图 4 中的解析程序:

 
[ServiceContract]
interface IContactManager {
  [OperationContract]
  void AddContact(Contact contact);
  ...
}
class AddressBookService : IContactManager {...}

图 5 演示如何在 AddressBookService 的主机上安装解析程序。

图 5 在主机上安装解析程序

 
ServiceHost host = 
  new ServiceHost(typeof(AddressBookService));

foreach(ServiceEndpoint endpoint in 
  host.Description.Endpoints) {
  foreach(OperationDescription operation in 
    endpoint.Contract.Operations) {

    DataContractSerializerOperationBehavior behavior = 
      operation.Behaviors.Find<
        DataContractSerializerOperationBehavior>();
      behavior.DataContractResolver = new CustomerResolver();
  }
}
host.Open();

例如,如果给定以下代理类定义:

 
class ContactManagerClient : ClientBase<IContactManager>,IContactManager
{...}

图 6 演示如何在代理上安装解析程序,以使用已知类型调用图 5 的服务。

图 6 在代理上安装解析程序

 
ContactManagerClient proxy = new ContactManagerClient();

foreach(OperationDescription operation in 
  proxy.Endpoint.Contract.Operations) {

  DataContractSerializerOperationBehavior behavior = 
    operation.Behaviors.Find<
    DataContractSerializerOperationBehavior>();
   
  behavior.DataContractResolver = new CustomerResolver();
}

Customer customer = new Customer();
...
proxy.AddContact(customer);

泛型解析程序

为了自动实现解析程序,我编写了类 GenericResolver,其定义如下:

 
public class GenericResolver : DataContractResolver {
  public Type[] KnownTypes
  {get;}

  public GenericResolver();
  public GenericResolver(Type[] typesToResolve);

  public static GenericResolver Merge(
    GenericResolver resolver1,
    GenericResolver resolver2);
}

它不会添加源自 .NET Framework 引用的程序集中的类型。

图 7 演示 GenericResolver 的相关部分,不反映程序集中的类型(与 WCF 无关)。

图 7 实现 GenericResolver(部分)

 
public class GenericResolver : DataContractResolver {
  const string DefaultNamespace = "global";
   
  readonly Dictionary<Type,Tuple<string,string>> m_TypeToNames;
  readonly Dictionary<string,Dictionary<string,Type>> m_NamesToType;

  public Type[] KnownTypes {
    get {
      return m_TypeToNames.Keys.ToArray();
    }
  }

  // Get all types in calling assembly and referenced assemblies
  static Type[] ReflectTypes() {...}

  public GenericResolver() : this(ReflectTypes()) {}

  public GenericResolver(Type[] typesToResolve) {
    m_TypeToNames = new Dictionary<Type,Tuple<string,string>>();
    m_NamesToType = new Dictionary<string,Dictionary<string,Type>>();

    foreach(Type type in typesToResolve) {
      string typeNamespace = GetNamespace(type);
      string typeName = GetName(type);

      m_TypeToNames[type] = new Tuple<string,string>(typeNamespace,typeName);

      if(m_NamesToType.ContainsKey(typeNamespace) == false) {
        m_NamesToType[typeNamespace] = new Dictionary<string,Type>();
      }

      m_NamesToType[typeNamespace][typeName] = type;
    }
  }

  static string GetNamespace(Type type) {
    return type.Namespace ??
DefaultNamespace;
  }

  static string GetName(Type type) {
    return type.Name;
  }

  public static GenericResolver Merge(
    GenericResolver resolver1, GenericResolver resolver2) {

    if(resolver1 == null) {
      return resolver2;
    }

    if(resolver2 == null) {
      return resolver1;
    }

    List<Type> types = new List<Type>();

    types.AddRange(resolver1.KnownTypes);
    types.AddRange(resolver2.KnownTypes);

    return new GenericResolver(types.ToArray());
  }

  public override Type ResolveName(
    string typeName,string typeNamespace,
    Type declaredType,
    DataContractResolver knownTypeResolver) {

    if(m_NamesToType.ContainsKey(typeNamespace)) {
      if(m_NamesToType[typeNamespace].ContainsKey(typeName)) {
        return m_NamesToType[typeNamespace][typeName];
      }
    }

    return knownTypeResolver.ResolveName(
      typeName,typeNamespace,declaredType,null);
  }

  public override bool TryResolveType(
    Type type,Type declaredType,
    DataContractResolver knownTypeResolver,
    out XmlDictionaryString typeName,
    out XmlDictionaryString typeNamespace) {

    if(m_TypeToNames.ContainsKey(type)) {
      XmlDictionary dictionary = new XmlDictionary();
      typeNamespace = dictionary.Add(m_TypeToNames[type].Item1);
      typeName      = dictionary.Add(m_TypeToNames[type].Item2);
      return true;
    }
    else {
      return knownTypeResolver.TryResolveType(
      type,declaredType,null,out typeName,
      out typeNamespace);
    }
  }
}

ResolveName 方法使用提供的命名空间和名称作为 m_NamesToType 字典中的键,以返回解析后的类型。

为此,可以使用 GenericResolverInstaller 的 AddGenericResolver 方法,这些方法定义如下:

 
public static class GenericResolverInstaller {
  public static void AddGenericResolver(
    this ServiceHost host, params Type[] typesToResolve);

  public static void AddGenericResolver<T>(
    this ClientBase<T> proxy, 
    params Type[] typesToResolve) where T : class;

  public static void AddGenericResolver<T>(
    this ChannelFactory<T> factory,
    params Type[] typesToResolve) where T : class;
}

例如,考虑以下已知类型:

 
[DataContract]
class Contact {...}

[DataContract]
class Customer : Contact {...}

[DataContract]
class Employee : Contact {...}

图 8 演示几个示例说明如何使用 AddGenericResolver 扩展方法处理这些类型。

图 8 安装 GenericResolver

 
// Host side

ServiceHost host1 = new ServiceHost(typeof(AddressBookService));
// Resolve all types in this and referenced assemblies
host1.AddGenericResolver();
host1.Open();

ServiceHost host2 = new ServiceHost(typeof(AddressBookService));
// Resolve only Customer and Employee
host2.AddGenericResolver(typeof(Customer),typeof(Employee));
host2.Open();

ServiceHost host3 = new ServiceHost(typeof(AddressBookService));
// Can call AddGenericResolver() multiple times
host3.AddGenericResolver(typeof(Customer));
host3.AddGenericResolver(typeof(Employee));
host3.Open();

// Client side

ContactManagerClient proxy = new ContactManagerClient();
// Resolve all types in this and referenced assemblies
proxy.AddGenericResolver();

Customer customer = new Customer();
...
proxy.AddContact(customer);

这在添加受限的泛型类型时十分方便:

 
[DataContract]
class Customer<T> : Contact {...}

ServiceHost host = new ServiceHost(typeof(AddressBookService));

// Add all non-generic known types
host.AddGenericResolver();

// Add the generic types 
host.AddGenericResolver(typeof(Customer<int>,Customer<string>));

host.Open();

图 9 演示 GenericResolverInstaller 的部分实现。

图 9 实现 GenericResolverInstaller

 
public static class GenericResolverInstaller {
  public static void AddGenericResolver(
    this ServiceHost host, params Type[] typesToResolve) {

    foreach(ServiceEndpoint endpoint in 
      host.Description.Endpoints) {

      AddGenericResolver(endpoint,typesToResolve);
    }
  }

  static void AddGenericResolver(
    ServiceEndpoint endpoint,Type[] typesToResolve) {

    foreach(OperationDescription operation in 
      endpoint.Contract.Operations) {

      DataContractSerializerOperationBehavior behavior = 
        operation.Behaviors.Find<
        DataContractSerializerOperationBehavior>();

      GenericResolver newResolver;

      if(typesToResolve == null || 
        typesToResolve.Any() == false) {

        newResolver = new GenericResolver();
      }
      else {
        newResolver = new GenericResolver(typesToResolve);
      }

      GenericResolver oldResolver = 
        behavior.DataContractResolver as GenericResolver;
      behavior.DataContractResolver = 
        GenericResolver.Merge(oldResolver,newResolver);
    }
  }
}

请注意与旧解析程序(如果存在)的合并。

泛型解析程序特性

为此,我编写了 GenericResolverBehaviorAttribute:

 
[AttributeUsage(AttributeTargets.Class)]
public class GenericResolverBehaviorAttribute : 
  Attribute,IServiceBehavior {

  void IServiceBehavior.Validate(
    ServiceDescription serviceDescription,
    ServiceHostBase serviceHostBase) {

    ServiceHost host = serviceHostBase as ServiceHost;
    host.AddGenericResolver();
  }
  // More members
}

这一简洁的特性使服务独立于主机:

 

对于 GenericResolverBehaviorAttribute,它会将泛型解析程序添加到主机。

您可以在下一个 WCF 项目中采用这种方法。

 

idesign.net 与 Lowy 联系。

衷心感谢以下技术专家对本文的审阅:Glenn Block 和 Amadeo Casas Cuadrado

相关文章:

  • 2022-12-23
  • 2021-04-26
  • 2021-07-10
  • 2021-07-03
  • 2021-11-10
  • 2022-02-09
猜你喜欢
  • 2022-12-23
  • 2021-06-23
  • 2021-05-22
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案