class A {...}
class B : A {...}
如果以图形表示,这类似于图 1 中的维恩图,每个 B 实例仍然是 A 实例(但不一定每个 A 都是 B)。
图 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 所示。
图 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