第七讲:数据契约(2)
代码
https://yunpan.cn/cPns5DkGnRGNs 密码:3913
前面不止一次地强调,WCF下的序列化与反序列化解决的是数据在两种状态之间 相互 转化的问题:托管类型对象转换成XML 。
由于类型定义了对象的数据结构,所以无论是对于序列化还是反序列化,都必须事先确定对象的类型。如果被序列化对象或被反序列化生成对象包含不可知的类型,序列化和反序列化将会失败。为了确保DataContractSerializer的正常序列化和饭序列化,需要将“未知”类型加入DataContractSerializer“已知”类型列表中。
例如 :
我们服务端的数据类型 A 和 B(A 类上面 打了 DataContract 特性的标签,将A变成了 数据契约, B 类并没有做任何描述的操作,也就是说B类只是一个普通类型 ), B继承了A ,所以 正常的情况下 如果 某个方法的 参数 是 A类型,那么 传入 B类型是不应该有错误的。因为 B是A的子类。 可这里 WCF 并不认识B,尽管 方法的 参数 是A
这里WCF会报错的,(.Net的类型可以分为两种:声明类型和真实类型。我们提倡面向接口的编程,对象的真实类型往往需要在运行时才能确定,在编程的时候只需要指明类型的声明类型,比如类型实现的接口或抽象类。)当我们使用基于接口或抽象类(类)创建
的DataContractSerializer(数据契约)去序列化一个实现了该接口或继承该抽象类的实例时,往往会因为无法识别对象的真实类型造成不能正常的序列化。
所以客户端 去调用方法并传入A类型的子类 类型 作为参数 也是会报错的。 因为 B 类型并不是 已知的类型。
具体应该怎么做呢?那这种情况下 我们就应该 将 " 未知 " 类型加入DataContractSerializer " 已知 " 类型列表中。
那现在 就是 应该我们的 KnownTypeAttribute 与ServiceKnowTypeAttribute 两个特性出场的时候了。
1.KnownTypeAttribute :
KnownTypeAttribute应用于数据契约中,用于设置继承于该数据契约类型的子数据契约类型,或者引用其他潜在的类型。
2.ServiceKnowTypeAttribute :
ServiceKnowTypeAttribute既可以应用于服务契约的接口和方法上,也可以应用在服务实现的类和方法上。如果应用在服务契约类型上,未知类型的这个类型 在所有实现了该契约的服务操作中有效,如果应用于服务契约的操作方法上,则定义的未知类型在所有实现了该契约的服务对应的操作方法中有效.
上面的两种方法,二选一,两种都可以做到 将未知类型加入到 已知类型当中。
我们再次看Demo,云盘上有
[ 7-01 ]
Client:客户端
ContentTypes:数据契约层
GigManager:服务契约层 ( 这里 把服务契约的 实现 也写到了 这里。当然 也可以把它分开 )
Hosting:服务器寄宿
首先看 GigManager:服务契约层
[ 7-02 ]
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 using ContentTypes; 7 8 namespace GigManager 9 { 10 11 [ServiceContract] 12 public interface IGigManagerService 13 { 14 15 [OperationContract] 16 //[ServiceKnownType(typeof(Order))] 17 void Save(OrderBase order); 18 } 19 20 public class GigManagerService : IGigManagerService 21 { 22 public void Save(OrderBase order) 23 { 24 25 } 26 } 27 28 }
然后 看 ContentTypes:数据契约层
[ 7-03 ]
[ 7-04 ]
OrderBase.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 using System.Runtime.Serialization; 7 namespace ContentTypes 8 { 9 10 [DataContract] 11 //[KnownType(typeof(Order))] 12 public class OrderBase 13 { 14 [DataMember] 15 public Guid ID { get; set; } 16 17 [DataMember] 18 public DateTime Date { get; set; } 19 20 [DataMember] 21 public string Customer { get; set; } 22 23 [DataMember] 24 public string ShipAddress { get; set; } 25 } 26 }
[ 7-05 ]
Order.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Runtime.Serialization; 6 7 namespace ContentTypes 8 { 9 [DataContract] 10 public class Order : OrderBase 11 { 12 [DataMember] 13 public double TotalPrice { get; set; } 14 } 15 16 }
可以看到数据契约层中有 两个 类,一个OrderBase 为父类, 一个 Order 为 OrderBase 的子类
接下来看 Hosting:服务器寄宿( 这里的代码没有什么改变与第六讲一样 )
代码
[ 7-06 ]
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ServiceModel; 6 7 namespace Hosting 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 using (ServiceHost host = new ServiceHost(typeof(GigManager.GigManagerService))) 14 { 15 host.Open(); 16 Console.WriteLine(); 17 Console.WriteLine("服务已经启动"); 18 Console.ReadLine(); 19 } 20 } 21 } 22 }
appconfig 配置:
[ 7-07 ]
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <services> 5 <service name="GigManager.GigManagerService" behaviorConfiguration="serviceBehavior"> 6 <endpoint address="GigManagerService" contract="GigManager.IGigManagerService" binding="netTcpBinding"></endpoint> 7 <endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex"></endpoint> 8 <host> 9 <baseAddresses> 10 <add baseAddress="http://127.0.0.1:8000"/> 11 <add baseAddress="net.tcp://127.0.0.1:9000"/> 12 </baseAddresses> 13 </host> 14 </service> 15 </services> 16 <behaviors> 17 <serviceBehaviors> 18 <behavior name="serviceBehavior"> 19 <serviceMetadata httpGetEnabled="true"/> 20 </behavior> 21 </serviceBehaviors> 22 </behaviors> 23 </system.serviceModel> 24 </configuration>
最后看 Client:客户端
代码
[ 7-08 ]
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using Client.localhost; 6 using ContentTypes; 7 8 namespace Client 9 { 10 class Program 11 { 12 static void Main(string[] args) 13 { 14 using (localhost.GigManagerServiceClient proxy = new Client.localhost.GigManagerServiceClient()) 15 { 16 Order order = new Order(); 17 order.ID = Guid.NewGuid(); 18 order.Customer = "NCS"; 19 order.Date = DateTime.Today; 20 order.ShipAddress = "sssssssssss"; 21 order.TotalPrice = 888.8; 22 proxy.Save(order); 23 Console.WriteLine(order.ID.ToString()); 24 Console.WriteLine(order.Customer); 25 Console.Read(); 26 } 27 } 28 } 29 }
appconfig 配置: ( 这里的代码没有什么改变与第六讲一样 )
这里的配置 是我们 添加了 服务引用 自动生成 的 ,可以不管
[ 7-09 ]
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <bindings> 5 <netTcpBinding> 6 <binding name="NetTcpBinding_IGigManagerService" closeTimeout="00:01:00" 7 openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" 8 transactionFlow="false" transferMode="Buffered" transactionProtocol="OleTransactions" 9 hostNameComparisonMode="StrongWildcard" listenBacklog="10" 10 maxBufferPoolSize="524288" maxBufferSize="65536" maxConnections="10" 11 maxReceivedMessageSize="65536"> 12 <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" 13 maxBytesPerRead="4096" maxNameTableCharCount="16384" /> 14 <reliableSession ordered="true" inactivityTimeout="00:10:00" 15 enabled="false" /> 16 <security mode="Transport"> 17 <transport clientCredentialType="Windows" protectionLevel="EncryptAndSign" /> 18 <message clientCredentialType="Windows" /> 19 </security> 20 </binding> 21 </netTcpBinding> 22 </bindings> 23 <client> 24 <endpoint address="net.tcp://127.0.0.1:9000/GigManagerService" 25 binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IGigManagerService" 26 contract="localhost.IGigManagerService" name="NetTcpBinding_IGigManagerService"> 27 <identity> 28 <userPrincipalName value="20080904-1145\Administrator" /> 29 </identity> 30 </endpoint> 31 </client> 32 </system.serviceModel> 33 </configuration>
我们仔细看 这里 ,这里的 需要的参数是OrderBase类型,而我们传入的是OrderBase 的 子类Order 类型,所以这里一定会出错。
[ 7-10 ]
运行后 的报错
[ 7-11 ]
错误消息:
格式化程序尝试对消息反序列化时引发异常: 尝试对参数 http://tempuri.org/ 进行反序列化时出错: order。InnerException 消息是“元素“http://tempuri.org/:order”含有“http://schemas.datacontract.org/2004/07/ContentTypes:Order”数据协定的数据。反序列化程序不知道映射到此协定的类型。请将与“Order”对应的类型添加到已知类型的列表中,例如,通过使用 KnownTypeAttribute 属性或通过将其添加到传递给 DataContractSerializer 的已知类型的列表等方法。”。有关详细信息,请参阅 InnerException。
这里很清楚的告诉了 我们 不认识 Order 类型。
如何代码实现?
1.使用KnownTypeAttribute
在父类的 数据契约上 加上 [KnownType(typeof(Order))] 特性 , 将 Order 这个 " 未知 " 类型 加入 " 已知 " 类型列表中。
[ 7-12 ]
2.使用ServiceKnownType
在需要用到该未知类型 操作的服务契约上或者服务契约中的方法上 打上 [ServiceKnownType(typeof(Order))] 特性标签 ,则定义的 未知类型 在所有实现了该契约的服务对应的操作中有效.
特性打在 服务契约 上面
[ 7-13 ]
或者 特性打在 服务契约中的方法上
[ 7-14 ]
好 , 打上 特性之后 ,我们再试一试 ,这样就可以了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步