首页 > 代码库 > 再说WCF Data Contract KnownTypeAttribute
再说WCF Data Contract KnownTypeAttribute
WCF 中的序列化是用DataContractSerializer,所有被[DataContract]和[DataMemeber]标记的类和属性会被DataContractSerializer序列化。在WCF中使用Contract模式来分辨和指定序列化/反序列化的类型,它是通过http://xmlns/Class这样的命名空间来标识这个序列化的对象的,一旦在序列化过程中无法找到这样的标识(比如某个字段,或者子对象)时,序列化就会失败。KnownTypeAttribute就提供了为我们通知序列化器去寻找未知对象的映射的途径。在Remoting中这样的问题不会存在,因为Remoting实际上是通过将一个类型传递给双方来进行类型匹配的。
那么KnowTypeAttribute到底用在什么地方呢?上边说了,当前类的未知类型。那什么又是当前类的未知类型呢?或许说未知类型有些不恰当,但下边的实际应用可能会让你更清楚这到底是指什么。
1). 序列化对象是从期望返回的类型继承;
2). 无法确定当前所使用类型的。例如Object类型,或者接口类型,你需要告诉序列化器去寻找确切的类来进行序列化。
3). 使用泛型类型作为期望返回类型的;
4). 使用像ArrayList等以object为基础类型存储对象的;
下边我们以第一种类型为例说明KnownTypeAttribute的用法。序列化对象一般是参与到在服务端和客户端传递的数据。在面向对象的设计中,继承可以很好的解决很多业务问题,并简化处理。而在下边的例子中我们可以看出KnownType到底能够做什么。
namespace WcfServiceDemo { [DataContract] public class Address { public Address() { …… }
[DataMember] public string AddressCategory { get; set; }
[DataMember] public string AddressTitle { get; set; }
[DataMember] public string AddressDetail { get; set; } }
[DataContract] public class BusinessAddress : Address { …… }
[DataContract] public class HomeAddress : Address { …… } }
public class Service1 : IService1 { public Address GetAddress(bool isHome) { if (isHome) return new HomeAddress(); else return new BusinessAddress(); } } |
上边的代码中简单的生命了三个数据契约类型,Address作为基类,HomeAddress和BusinessAddress继承于Address类。在Service实现中,简单的通过一个参数来判断到底返回哪个子类。我们可以简单的写个客户端来调用一下这个服务,但很快你会发现浏览器(客户端)返回给你了错误:
Server Error in ‘/‘ Application.
The underlying connection was closed: The connection was closed unexpectedly.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Net.WebException: The underlying connection was closed: The connection was closed unexpectedly.
很显然,这个错误并不能清楚的反应具体发生了什么,如果你跟进服务你会发现,所有的操作都正常,只是在返回数据的时候发生了错误。如果你愿意,你可以用DataContractSerializer类的一些方法来将一个HomeAddress序列化并反序列化为一个Address类型,你就会看到这个过程中发生了错误,序列化都无法进行。为什么呢?原因是HomeAddress/BusinessAddress的类型标识(如http://www.demo.com/BusinessAddress)无法序列化Address类型时匹配,它就不知道该把它序列化成哪个确切的类型。
解决方法,给Address添加KnownTypeAttribute标识,当一个HomeAddress对象或者BusinessAddress对象被传递到Address进行序列化时,序列化器认识这个对象并能根据契约来进行序列化。
[DataContract] [KnownType(typeof(BusinessAddress))] [KnownType(typeof(HomeAddress))] public class Address { …… } |
再次调用客户端(注意:如果你是通过Service Reference来引用服务的,那你必须在编译完服务端后选择Update Service Reference来更新服务引用,否则你的变化不会反应到客户端调用),现在你应该可以看到结果了。对于KnownTypeAttribute它还有一个可以替换的选择ServiceKnownTypeAttribute,你可以将它应用到一个Service或者一个Operation(他们的区别是:当把ServiceKnownType标记给以个Service,那么在这个Service内KnownType都发生作用;而对于Operation则只在这个Operation时有效,即作用域不同。)。以下代码同样解决了上述问题:
[OperationContract] [ServiceKnownType(typeof(BusinessAddress))] [ServiceKnownType(typeof(HomeAddress))] Address GetAddress(bool isHome); |
KnownType的另外一种标识方式是不用加代码的,在配置文件中通过声明型编程实现:
<system.runtime.serialization> <dataContractSerializer> <declaredTypes> <add type="WcfServiceDemo.Address, MyAssembly, Version=2.0.0.0, Culture=neutral,PublicKeyToken=null, processorArchitecture=MSIL"> <knownType type="MyCompany.Library.Circle, MyAssembly, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null, processorArchitecture=MSIL"/> </add> </declaredTypes> </dataContractSerializer> </system.runtime.serialization> |
而对于泛型来说,KnownType是不能被直接应用的,如果你写成[KnownType(typeof(BusinessAddress<>))]这样是不允许,但CLR给我们提供了另外一种方法来实现对序列化的通知:
[KnownType("GetKnownTypes")] public class Address<T> { static Type[] GetKnownTypes() { return new Type[] { typeof(HomeAddress<T>) ,typeof(BusinessAddress)}; } } |
我们指定寻找KnownType时到GetKnownTypes方法去找,GetKnownTypes方法返回一个告知序列化器映射类型的数组。既然我们可以用方法,那是否意味着我们也可以动态的来定义GetKnownTypes返回的类型集合呢?当然,在上边的例子中你可以通过扩展GetKnownTypes方法来实现。但为了DataContract类型定义的简约和独立性,我们不妨将这个函数摘出来,或许更有利于程序结构:
[ServiceContract] [ServiceKnownType("GetKnownTypes", typeof(KnownTypesProvider))] public interface IService1 { …… }
static class KnownTypesProvider { static Type[] GetKnownTypes(ICustomAttributeProvider knownTypeAttributeTarget) { Type contractType = (Type)knownTypeAttributeTarget; return contractType.GetGenericArguments() ; } } |
我们将类型通知提供者定义到函数外部,并通过利用ServiceKnownType的重载构造函数传入需要去寻找的通知类提供者及其方法。这样你很容易扩展这个函数并动态加载通知类型。
你仍然可以通过使用配置文件的方式来达到同样的功能:
<system.runtime.serialization> <dataContractSerializer> <declaredTypes> <add type="MyCompany.Library.Shape, MyAssembly, Version=2.0.0.0, Culture=neutral, PublicKeyToken=XXXXXX, processorArchitecture=MSIL"> <knownType type="MyCompany.Library.Circle, MyAssembly, Version=2.0.0.0, Culture=neutral, PublicKeyToken=XXXXXX, processorArchitecture=MSIL"> <parameter type="System.Collections.Generic.Dictionary"> <parameter type="System.String"/> <parameter index="0"/> </parameter> </knownType> </add> </declaredTypes> </dataContractSerializer> </system.runtime.serialization> |
以上代码就指定了将Circle<Dictionary<string, T>>作为Shape<T>的一个knownType。注意,当你指定某个knownType可能用到多于一个参数为泛型对象的参数时,通过index来指定与期望类型T不同的另外一个类型。例如上边的代码中指定了第一个参数为String类型(通过index指定),第二个参数T与Shape<T>的T相同,不用特别指定。
再说WCF Data Contract KnownTypeAttribute