数据契约层级(DataContract Hierarchy)
- WCF要求类层级的每一级数据契约都必须标记DataContract特性,因为该特性是不可继承的
- WCF允许开发者在类层级中混合使用Serializable和DataContract特性
- Serializable特性通常会被应用到类层级的基类上,而对于新增的类,则应该使用DataContract特性。导出一个数据契约层级时,元数据会维持它的层级体系。在使用
服务契约的子类时,类层级的每级数据契约定义都会被导出
已知类型
大多数情况下,WCF不能使用数据契约的子类去替换基类
在传统的面向对象编程中,对子类的引用同样也是其基类的引用,因此子类维持了与基类之间的Is-A关系。例如C#这样的语言允许开发者采取这种方式用子类替换基类,但这却不适用于WCF操作
。
原因在于我们并没有实际传递对象的引用,而是传递了对象的状态。例如:
[DataContract]
class Customer : Contact
{
[DataMember]
public int OrderNumber;
}
以下代码能够成功通过编译,但在运行时却会失败:
Contact contact = new Customer()
{
...
};
ContactManagerClient proxy = new ContactManagerClient();
//服务调用失败:
proxy.AddContact(contact);
proxy.Close( );
当我们传递Customer对象而不是Contact对象时,服务并不知道它应该反序列化状态的
Customer部分。同样,如果操作返回Customer对象而不是Contact对象,客户端也不知道该如何反序列化它,因为它能识别的类型是Contact,而不是Customer:
解决上述问题——KnownTypeAttribute
[DataContract]
[KnownType(typeof(Customer))]
class Contact
{...}
[DataContract]
class Customer : Contact
{...}
在宿主端,如果将KnownType特性应用在基类上,会影响所有的契约与操作。它能跨越所
有的服务和终结点,允许服务接收子类,而不是基类。此外,应用KnwonType特性就可以
在元数据中包含子类,这样,客户端就具有了子类的定义,能够传递子类而不是基类。如
果客户端同样将KnownType特性应用在基类的副本对象上,那么它就能够依次接收服务返
回的已知子类对象。
服务已知类型
弥补KnownTypeAttribute的缺陷——ServiceKnownTypeAttribute
使用KnownType特性的缺陷在于它使得范围过于宽泛了。因此,WCF还提供了ServiceKnownTypeAttribute特性。
- 将ServiceKnownType应用在指定的操作上,其他操作不能接收子类。(只有这样的操作(包括所有支持该契约的服务)才能够接收已知的子类):
[DataContract]
class Contact
{...}
[DataContract]
class Customer : Contact
{...}
[ServiceContract]
interface IContactManager
{
[OperationContract]
[ServiceKnownType(typeof(Customer))]
void AddContact(Contact contact);
[OperationContract]
Contact[] GetContacts();
}
- 将ServiceKnownType应用在契约上(该契约以及实现该契约的所有服务包含的
所有操作都能够接收已知的子类)
[ServiceContract]
[ServiceKnownType(typeof(Customer))]
interface IContactManager
{
[OperationContract]
void AddContact(Contact contact);
[OperationContract]
Contact[] GetContacts();
}
不要将ServiceKnownType特性应用到服务类自身。虽然代码可以通过编译,但它只有在该服务契约没有被定义为接口时才有效(我们应尽量避免这样的用法)。如果服务拥有单独的契约定义,那么ServiceKnownType特性对于该服务无效。
无论ServiceKnownType特性是被应用到操作上,还是契约上,导出的元数据和生成的
代理都不会包含它的内容,而只会包含应用KnownType特性的基类。如给出下列服务定义:
[ServiceContract]
[ServiceKnownType(typeof(Customer))]
interface IContactManager
{...}
则输出的定义如下:
[DataContract]
[KnownType(typeof(Customer))]
class Contact
{...}
[DataContract]
class Customer : Contact
{...}
[ServiceContract]
interface IContactManager
{...}
我们可以手动修改客户端的代理类, 通过删去基类的KnownType特性, 然后将ServiceKnownType特性应用到契约的相应层级上,从而准确的反映服务端的语义。
多个已知类型——同时应用KnownType和ServiceKnownType
必须明确地将数据契约的所有类层级添加到ServiceKnownType中,但基类并不包括在内
[DataContract]
class Contact
{...}
[DataContract]
class Customer : Contact
{...}
[DataContract]
class Person : Contact
{...}
[ServiceContract]
[ServiceKnownType(typeof(Customer))]
[ServiceKnownType(typeof(Person))]
interface IContactManager
{...}
配置已知类型
已知类型的相关特性存在的主要缺陷是它们要求服务或者客户端必须事先知道哪些子类可
能会被其他调用方调用。添加一个新的子类必然要求修改代码、重新编译和重新部署。要
解决这一问题,WCF允许开发者在服务或客户端的配置文件中配置已知类型,
我们不仅需要提供类型名,还要提供所在程序集名。
<system.runtime.serialization>
<dataContractSerializer>
<declaredTypes>
<add type = "Contact,Host,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null">
<knownType type = "Customer,MyClassLibrary,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null"/>
</add>
</declaredTypes>
</dataContractSerializer>
</system.runtime.serialization>
若是不依赖于程序集名或版本,则可以使用程序集的友好名称:
<add type = "Contact,Host">
<knownType type = "Customer,MyClassLibrary"/>
</add>
在配置文件中包含已知类型,与将KnownType特性应用在数据契约的作用相同,发布的元
数据将包含已知类型的定义。
如果已知类型对于另一个程序集而言是内部(internal)类型,要添加一个已知类型,只有使用配置文件声明它。
数据契约解析器
数据契约解析器就是使用代码处理已知类型(完全自动化处理)。它使用WCF 4.0中的DataContractResolver。从本质上说,可以同时在客户端和服务端通过拦截操作的参数来解析已知类型。·
[DataContract]
class Contact
{ }
[DataContract]
class Customer : Contact
{ }
public class CustomerResolver : DataContractResolver
{
string Namespace => typeof(Customer).Namespace ?? "global";
string Name => typeof(Customer).Name;
/// <summary>
/// WCF尝试从消息反序列化一个类型就会调用ResolveName()
/// </summary>
/// <param name="typeName"></param>
/// <param name="typeNamespace"></param>
/// <param name="declaredType"></param>
/// <param name="knownTypeResolver"></param>
/// <returns></returns>
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);
}
}
/// <summary>
/// WCF尝试把类型序列化进消息时,会调用TryResolveType()
/// 如果想序列化类型,就要提供一些唯一的表示作为key放到字典里,这个字典维护了标识符和类型之间的映射关系。
/// WCF会在反序列化期间把这些key提供给你,你可以根据类型来绑定标识符。
/// 命名空间key不能为空,推荐使用CLR类型名和命名空间。
///
/// 返回true,只要标记了KnowType,就可以当做解析成功
/// </summary>
/// <param name="type"></param>
/// <param name="declaredType"></param>
/// <param name="knownTypeResolver"></param>
/// <param name="typeName"></param>
/// <param name="typeNamespace"></param>
/// <returns></returns>
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);
}
}
}
安装数据契约解析器
解析器必须作为行为附加到代理或服务终结点的操作上。(行为是服务的局部属性,不会影响消息和通道)
有以下服务契约
[ServiceContract]
interface IContactManager
{
[OperationContract]
void AddContact(Contact contact);
}
class ContactManager : IContactManager
{
public void AddContact(Contact contact)
{...}
}
在宿主上安装解析器
ServiceHost host = new ServiceHost(typeof(ContactManager));
//需要遍历所有终结点
foreach (ServiceEndpoint endpoint in host.Description.Endpoints)
{
//每个ServiceEndpoint.Contract.Operations有多个OperationDescription。
foreach (OperationDescription operation in endpoint.Contract.Operations)
{
DataContractSerializerOperationBehavior behavior = operation.Behaviors.Find<DataContractSerializerOperationBehavior>();
behavior.DataContractResolver = new CustomerResolver();
}
}
在代理上安装解析器
ContactManagerClient proxy = new ContactManagerClient();
//需要遍历所有终结点
//每个ServiceEndpoint.Contract.Operations有多个OperationDescription。
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);
泛型解析器(juval Lowy)——部分
public class GenericResolver : DataContractResolver
{
const string DefaultNamespace = "global";
/// <summary>
/// 映射类型到名字和命名空间上
/// </summary>
readonly Dictionary<Type, Tuple<string, string>> m_TypeToNames;
/// <summary>
/// 映射类型命名空间和名字到实际的类型上
/// </summary>
readonly Dictionary<string, Dictionary<string, Type>> m_NamesToType;
public Type[] KnowTypes
{
get { return m_TypeToNames.Keys.ToArray(); }
}
//获取程序集里的所有类型
static Type[] ReflectTypes()
{
//示例
return new Type[4];
}
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 = type.Namespace;
string typeName = type.Name;
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) => type.Namespace ?? DefaultNamespace;
static string GetName(Type type) => type.Name;
/// <summary>
/// 使用KnowTypes,合并两个解析器
/// </summary>
/// <param name="resolver1"></param>
/// <param name="resolver2"></param>
/// <returns></returns>
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.KnowTypes);
types.AddRange(resolver2.KnowTypes);
return new GenericResolver(types.ToArray());
}
/// <summary>
/// 使用提供的命名空间和名字作为key到m_NamesToType读取解析出的类型
/// </summary>
/// <param name="typeName"></param>
/// <param name="typeNamespace"></param>
/// <param name="declaredType"></param>
/// <param name="knownTypeResolver"></param>
/// <returns></returns>
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);
}
/// <summary>
/// 使用提供类型作为key到字典m_TypeToNames中读取类型名和命名空间
/// </summary>
/// <param name="type"></param>
/// <param name="declaredType"></param>
/// <param name="knownTypeResolver"></param>
/// <param name="typeName"></param>
/// <param name="typeNamespace"></param>
/// <returns></returns>
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();
typeName = dictionary.Add(m_TypeToNames[type].Item1);
typeNamespace = dictionary.Add(m_TypeToNames[type].Item2);
return true;
}
else {
return knownTypeResolver.TryResolveType(type, declaredType, null, out typeName, out typeNamespace);
}
}
}
安装泛型解析器
实现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, params Type[] typesToResolve)
{
foreach (OperationDescription operation in endpoint.Contract.Operations)
{
DataContractSerializerOperationBehavior behavior = operation.Behaviors.Find<DataContractSerializerOperationBehavior>();
GenericResolver newResolver;
// 如果没有提供类型,AddGenericResolver()会使用GenericResolver()的无参构造函数。
// 否则,他会通过调用其他构造函数而只使用指定的类型。(如果有旧的解析器,就会合并)
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);
}
}
}
安装GenericResolver
//宿主端
ServiceHost host1 = new ServiceHost(typeof(ContactManager));
//解析引用程序集的所有类型
host1.AddGenericResolver();
host1.Open();
ServiceHost host2 = new ServiceHost(typeof(ContactManager));
//只解析Customer和Employee
host2.AddGenericResolver(typeof(Customer),typeof(Employee));
host2.Open();
ServiceHost host3 = new ServiceHost(typeof(ContactManager));
//可以多次调用AddGenericResolver
host3.AddGenericResolver(typeof(Customer));
host3.AddGenericResolver(typeof(Employee));
host3.Open();
//客户端
ContactManagerClient proxy = new ContactManagerClient();
proxy.AddGenericResolver();
Customer customer = new Customer();
proxy.AddContact(customer);
GenericResolverInstaller不仅安装GenericResolver,而且还会尝试与旧的泛型解析器合并(如果有)。这意味着可以多次调用AddGenericResolver()方法。
GenericResolver和ServiceHost<T>
系统无法事先知道系统所有类型的情况下,使用泛型解析器是最佳的选择。如
public class ServiceHost<T>:ServiceHost
{
protected override void OnOpening()
{
this.AddGenericResolver();
}
}
泛型解析器属性
如果服务在设计上依赖于泛型解析器,那么最好不要受制于宿主,在设计时应该把你的需求告诉泛型解析器。
[AbbributeUsage(AttributeTargets.Class)]
public class GenericResolverBehaviorAttribute : Attribute, IServiceBehavior
{
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
throw new NotImplementedException();
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
throw new NotImplementedException();
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
ServiceHost host = serviceHostBase as ServiceHost;
host.AddGenericResolver();
}
}
使用
[GenericResolverBehavior]
class ContactManager:IContactManager
{
}
当宿主加载服务时,它使用反射来确定服务类是否支持IServiceBehavior接口。如果支持,宿主会调用IServiceBehavior方法,特别是Validate()方法,此方法会让属性与宿主交互。当遇到GenericResolverBehaviorAttribute属性时,他就会为宿主添加泛型解析器。
Object与接口
数据契约类或数据契约结构的基类型可以是接口:
interface IContact
{
string FirstName
{get;set;}
string LastName
{get;set;}
}
[DataContract]
class Contact : IContact
{...}
只要我们使用ServiceKnownType特性指定了确切的数据类型,就可以在服务契约中使用
基接口类型,或者在数据契约中定义基接口类型的数据成员:
[ServiceContract]
[ServiceKnownType(typeof(Contact))]
interface IContactManager
{
[OperationContract]
void AddContact(IContact contact);
[OperationContract]
IContact[] GetContacts( );
}
不能将KnownType特性应用到基接口上,因为导出的元数据无法包含接口本身。相反,导入的服务契约则是基于object的,同时还包括了数据契约子类或者没有继承关系的结构。
//导入的定义:
[DataContract]
class Contact
{...}
[ServiceContract]
interface IContactManager
{
[OperationContract]
[ServiceKnownType(typeof(Contact))]
[ServiceKnownType(typeof(object[]))]
void AddContact(object contact);
[OperationContract]
[ServiceKnownType(typeof(Contact))]
[ServiceKnownType(typeof(object[]))]
object[] GetContacts();
}
即使ServiceKnownType特性最初的定义范围是契约,导入的定义仍然可以将它应用到操作层级上。而且,每个操作都会包括一组所有操作都需要的ServiceKnownType特性,包括针对数组的冗长的服务已知类型的特性。在WCF的预先发布版本中需要这些定义,如今仍然被保留了下来。
我们可以手动修改导入的定义,让它只包含必需的ServiceKnownType特性:
[DataContract]
class Contact
{...}
[ServiceContract]
interface IContactManager
{
[OperationContract]
[ServiceKnownType(typeof(Contact))]
void AddContact(object contact);
[OperationContract]
[ServiceKnownType(typeof(Contact))]
object[] GetContacts( );
}
或者更好的做法是,如果客户端包含了基接口的定义,又或者对定义进行了分解,就可以使用基接口,而不是object类型。只要添加了从接口到数据契约的继承关系,就会给开发者带来一定程度的类型安全:
[DataContract]
class Contact : IContact
{...}
[ServiceContract]
interface IContactManager
{
[OperationContract]
[ServiceKnownType(typeof(Contact))]
void AddContact(IContact contact);
[OperationContract]
[ServiceKnownType(typeof(Contact))]
IContact[] GetContacts( );
}
但是,我们不能将导入契约中的object类型替换为具体的数据契约类型,因为两者是不兼容的:
//无效的客户端契约
[ServiceContract]
interface IContactManager
{
[OperationContract]
void AddContact(Contact contact);
[OperationContract]
Contact[] GetContacts( );
}