第六讲:数据契约
代码
https://yunpan.cn/cPns5DkGnRGNs 密码:3913
再将数据契约之前,我们想说一下 序列化
大部分的系统都是以数据为中心的,功能的实现表现在对相关数据的正确处理方面。一个分布式的互联网系统关注于数据的交换,而数据正常交换的根本前提是参与数据交换的双方对于数据的结构一致性理解,这就为数据的表现提出了要求,为了保证处于不同平台,不同厂商的应用能够正常进行数据交换,交换的数据必须采用一种大家都能够理解的展现方式。在这种方面,XML无疑是最好的选择。所以WCF下的序列化解决的就是如何将数据从对象的变现形式转变成XML的表现形式,以确保数据的正常交换。WCF主要通过数据契约来定义通过服务操作进行交换的数据。WCF 的序列化和反序列化解决的是托管对象与XML之间的转换,以实现与厂商无关,跨平台的数据交换。所以基于XML的数据表示能够实现跨平台的数据交换,在于XML是一种被不同的厂商普遍接受和易于理解的数据表示。WCF以XML的形式进行数据交换,一方传输的XML能够被另一方正确理解,同样需要交换双方就传递的XML结构达成一致的理解。具体采用怎样地实现描述XML的结构呢?XSD无疑是最好的选择。在WCF中,服务契约借助WSDL的形式实现对服务的描述,同理,数据契约通过XSD实现对数据的描述.
数据契约
一个正常的服务调用要求客户端和服务端对服务操作有一致的理解,WCF通过服务契约对服务操作进行抽象,以一种与平台无关的,能够被不同的厂商理解的方式对服务进行描述。同理,客户端和服务端进行有效的数据交换,同样要求交换双方对交换数据的结构达成共识。WCF通过数据契约来对交换的数据进行描述。与数据契约的定义相匹配,WCF采用新的序列化器---数据契约序列化器(DataContractSerializer)进行基于数据契约的序列化与反序列化.数据契约是描述数据的结构,数据交换只有在双方对数据结构具有相同的理解下才能正常进行。对于数据的接受方来讲,当它接收到数据时,只有借助于数据结构的描述,才能理解数据的每个元素所承载的信息。由于数据交换通过XML表示,XSD用于定义XML的结构,所以XSD可以看作是数据契约在SOA下的表现形式.
数据契约的定义
同服务契约类似,WCF采用了基于特性的数据契约定义方式。基于数据契约的自定义特性主要包含以下两个DataContractAttribute和DataMemberAttribute。
1.DataContractAttribute:将目标类型定义成一个数据契约
2.DataMemberAttribute:将字段或属性变成数据契约的数据成员
接下来 我们做个关于 数据契约的 小Demo, 网盘 自己找
首先我们看下 项目的结构
[ 6-01 ]
ContentTypes:数据契约层
GigEntry:客户端
GigManager:服务契约层 ( 这里 把服务契约的 实现 也写到了 这里。当然 也可以把它分开 )
Hosting:服务器寄宿
在看下我们的 服务契约:
[ 6-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(Name = "GigManagerServiceContract", Namespace = "http://www.HulkXu.com/")] 12 public interface IGigManagerService 13 { 14 //注意 如果不实用数据契约,无法传入复杂的对象,就如下面的 SaveGig 方法的 参数 是一个 ListItem 类型的参数 15 [OperationContract] 16 void SaveGig(ListItem item); 17 [OperationContract] 18 ListItem GetGig(); 19 } 20 21 /// <summary> 22 /// 这里为实现了 IGigManagerService 这个服务契约 23 /// </summary> 24 public class GigManagerService : IGigManagerService 25 { 26 private ListItem m_listItem; 27 /// <summary> 28 /// 将我们得到的 对象传给 m_listItem 29 /// </summary> 30 /// <param name="item"></param> 31 public void SaveGig(ListItem item) 32 { 33 m_listItem = item; 34 } 35 36 /// <summary> 37 /// 获取我们上面的 得到的 m_listItem 38 /// </summary> 39 /// <returns></returns> 40 public ListItem GetGig() 41 { 42 return m_listItem; 43 } 44 } 45 }
注意看 上面的 ListItem 参数,这个参数是一个复杂类型的参数,因为是我们自己定义的参数。
在来看我们的 服务器寄宿:
代码 ( 没有什么变化 )
[ 6-03 ]
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的配置
[ 6-04 ]
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <services> 5 <service name="GigManager.GigManagerService" behaviorConfiguration="serviceBehavior"> 6 <!--配置一个TCP协议的相对地址--> 7 <endpoint address="GigManagerService" contract="GigManager.IGigManagerService" binding="netTcpBinding"></endpoint> 8 <!--这里是一个固定的写法,用于发布TCP协议的 服务契约 (使用HTTP协议的方式展现出来)--> 9 <endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex"></endpoint> 10 <host> 11 <baseAddresses> 12 <!--WSDL呈现地址--> 13 <add baseAddress="http://127.0.0.1:8000"/> 14 <add baseAddress="net.tcp://127.0.0.1:9000"/> 15 </baseAddresses> 16 </host> 17 </service> 18 </services> 19 <behaviors> 20 <serviceBehaviors> 21 <behavior name="serviceBehavior"> 22 <serviceMetadata httpGetEnabled="true"/> 23 </behavior> 24 </serviceBehaviors> 25 </behaviors> 26 </system.serviceModel> 27 </configuration>
<endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex"></endpoint> 这个节点呢 是 用于我们 在浏览器中 去查看 元数据(WSDL) 的,注意它是一个固定的写法,也就是看服务契约以WSDL形式发布出来的呈现
它通过我们配置的地址来访问
<add baseAddress="http://127.0.0.1:8000"/>
为什么这么做?因为TCP协议的 方式 你怎么查看 WSDL的文档,只有通过 HTTP方式 在浏览器上 去查看。
再接下来看我们的 数据契约了:
之前我们看到 服务契约 的 SaveGig 方法,需要传入 一个 复杂类型 ListItem,而这个复杂类型就是我们自己定义的类。
这里我们需要将这个复杂类型变成我们的 数据契约
再之前我们通过查看描述数据契约的WSDL文档中的XSD(定义WSDL的文档)发现,每一个数据成员的命名习惯同类型定义中的属性相同。而且,序列中的每一个元素的顺序是字母顺序,与其在类型定义中出现的顺序相反。另一件值得注意的事
情就是,该类型的命名空间同服务契约目标命名空间不匹配。 它使用的是shemas.datacontract.org
这些都可以在数据契约中解决:
DataMember:相应的属性
Name:控制生成的模式元素名称。默认时使用CLR中定义的名称。
IsRequired:为模式元素控制minOccurs属性。默认时所有的元素都是可选的(minOccurs=0)
Order:控制模式中每一个元素的次序。默认时无序的数据成员按照字母顺序排序。
[ 6-05 ]
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 10 /// <summary> 11 /// 这里主要是WCF 序列化 该类 12 /// 这么想,客户端直接引用 ContentTypes 层 然后 直接 调用 服务端的方法传递 这个复杂的参数(ListItem) ,WCF 会根据这里定义好的数据契约 直接序列化成对应的XML传送到服务端。 13 /// </summary> 14 [DataContract(Namespace = "http://www.HulkXu.com/")] 15 //[DataContract] 16 public class ListItem 17 { 18 [DataMember(Name = "Id", IsRequired = false, Order = 0)] 19 //[DataMember] 20 private long m_id; 21 22 public long Id 23 { 24 get { return m_id; } 25 set { m_id = value; } 26 } 27 //[DataMember] 28 [DataMember(Name = "Title", IsRequired = true, Order = 1)] 29 private string m_title; 30 31 public string Title 32 { 33 get { return m_title; } 34 set { m_title = value; } 35 } 36 //[DataMember] 37 [DataMember(Name = "Description", IsRequired = true, Order = 2)] 38 private string m_description; 39 40 public string Description 41 { 42 get { return m_description; } 43 set { m_description = value; } 44 } 45 //[DataMember] 46 [DataMember(Name = "DateStart", IsRequired = true, Order = 3)] 47 private DateTime m_dateStart; 48 49 public DateTime DateStart 50 { 51 get { return m_dateStart; } 52 set { m_dateStart = value; } 53 } 54 //[DataMember] 55 [DataMember(Name = "DateEnd", IsRequired = false, Order = 4)] 56 private DateTime m_dateEnd; 57 58 public DateTime DateEnd 59 { 60 get { return m_dateEnd; } 61 set { m_dateEnd = value; } 62 } 63 //[DataMember] 64 [DataMember(Name = "Url", IsRequired = false, Order = 5)] 65 private string m_url; 66 67 public string Url 68 { 69 get { return m_url; } 70 set { m_url = value; } 71 } 72 73 } 74 }
最后我们看我们的客户端:
客户端是一个 Form 的 应用程序
界面
[ 6-06 ]
代码:
[ 6-07 ]
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 using GigEntry.localhost; 10 11 namespace GigEntry 12 { 13 public partial class Form1 : Form 14 { 15 //创建代理对象 16 localhost.GigManagerServiceContractClient proxy = new GigManagerServiceContractClient(); 17 18 public Form1() 19 { 20 InitializeComponent(); 21 22 } 23 24 /// <summary> 25 /// 保存 26 /// </summary> 27 /// <param name="sender"></param> 28 /// <param name="e"></param> 29 private void cmdSave_Click(object sender, EventArgs e) 30 { 31 //这里WCF不再需要序列化 ListItem 这个参数了,因为 在 ContentTypes 层的 ListItem.cs 类中。我们已经定义好 这个类的序列化规则了 32 ListItem item = new ListItem(); 33 item.Id = int.Parse(this.txtId.Text); 34 item.Title = this.txtTitle.Text; 35 item.Description = this.txtDescription.Text; 36 item.DateStart = this.dtpStart.Value; 37 item.DateEnd = this.dtpEnd.Value; 38 item.Url = this.txtUrl.Text; 39 proxy.SaveGig(item); 40 } 41 42 /// <summary> 43 /// 获取 44 /// </summary> 45 /// <param name="sender"></param> 46 /// <param name="e"></param> 47 private void cmdGet_Click(object sender, EventArgs e) 48 { 49 ListItem item = proxy.GetGig(); 50 if (item != null) 51 { 52 this.txtId.Text = item.Id.ToString(); 53 this.txtTitle.Text = item.Title; 54 this.txtDescription.Text = item.Description; 55 this.dtpStart.Value = item.DateStart; 56 this.dtpEnd.Value = item.DateEnd; 57 this.txtUrl.Text = item.Url; 58 59 } 60 } 61 62 63 } 64 }
appconfig 配置:
这里的配置 是我们 添加了 服务引用 自动生成 的 ,可以不管
[ 06-08 ]
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <system.serviceModel> 4 <bindings> 5 <netTcpBinding> 6 <binding name="NetTcpBinding_GigManagerServiceContract" 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_GigManagerServiceContract" 26 contract="localhost.GigManagerServiceContract" name="NetTcpBinding_GigManagerServiceContract"> 27 <identity> 28 <userPrincipalName value="20080904-1145\Administrator" /> 29 </identity> 30 </endpoint> 31 </client> 32 </system.serviceModel> 33 </configuration>
运行程序后 Hosting 服务器寄宿 然后在浏览器中 输入 http://127.0.0.1:8000 我们配置好的 WSDL呈现地址
[ 06-09 ]
[ 06-10 ]
[ 06-11 ]
[ 06-12 ]
看 06-12 图,我们很清楚的看到了 将 ListItem 类型 序列化成 XML 的形式
根据上面的 代码 ,我们将 数据契约 ListItem类的成员上面的 特性 [DataMember(Name = "Id", IsRequired = false, Order = 0)] 都去掉 ,换成 [DataMember]
[ 6-13 ]
再次 根据 运行 Hosting 服务器寄宿 然后在浏览器中 输入 http://127.0.0.1:8000 从图片 06-09 开始 到 06-12 的操作步奏 进行一次。
仔细观察 与之前 的 是否不太一样?
例如 这里的名字?
[ 6-14 ]
例如 这些字段的排序顺序?
[ 6-15 ]
等等,这里我就不贴 测试的图片了。自己下去试一试。