一、初识契约(Contract)
契约简单讲就是服务端和客户端进行消息交换定义的一种交换协议。在wcf中服务契约的定义涉及到System.ServiceModel.ServiceContractAttribute和
System.ServiceModel.OperationContractAttribute这两个自定义特性。其中应用在ServiceContractAttribute服务契约特性上的特性[AttributeUsage(AttributeTargets.
Class)]表示服务契约特性只能应用于类或接口;应用在OperationContractAttribute服务契约特性上的特性[AttributeUsage(AttributeTargets.Method)]表示操作契约特性只能应用于方法成员。
二、ServiceContractAttribute和OperationContractAttribute的属性成员
1、 ServiceContractAttribute特性具有以下一些成员:
1: public sealed class ServiceContractAttribute : Attribute
2: {
6: public Type CallbackContract { get; set; }
7:
8: public string ConfigurationName { get; set; }
9:
10: public bool HasProtectionLevel { get; }
11:
12: public string Namespace { get; set; }
13:
14: public ProtectionLevel ProtectionLevel { get; set; }
15:
16: public SessionMode SessionMode { get; set; }
17: }
我们在定义一个类或接口时为该类型应用[ServiceContract]特性,同时还可以指定服务契约的一个或多个属性,最常用的就是指定命名空间和名称,比如:[ServiceContract(Name="CalculatorService",Namespace=”http://www.ainote.cn”)]。
服务契约在终结点配置项的contract属性中所表示的值,也是服务契约的ConfigurationName属性的值,默认是服务契约类型的完全名称(服务契约的CLR类型名称),我们也可以配置 ServiceContract 特性的ConfigurationName属性值来指定一个契约配置名称。
服务契约的默认配置Stone.Contract.Contract.ICalculator:
1: <endpoint address="http://127.0.0.1:126/CalculatorService"
2: binding="ws2007HttpBinding"
3: contract="Stone.Contract.Contract.ICalculator"></endpoint>
通过ConfigurationName属性指定一个配置值CalculatorCatract:
1: <endpoint address="http://127.0.0.1:99/CalculatorService"
2: contract="CalculatorCatract"
3: binding="wsHttpBinding"></endpoint>
在双工通信模式下面还需要在ServiceContract特性应用中指定CallbackContract类型typeof(ICalculatorCallback):
1: [ServiceContract(Name="CalculatorService",Namespace="http://www.ainote.cn",
2: ConfigurationName="CalculatorCatract",CallbackContract=typeof(ICalculatorCallback))]
3: public interface ICalculator
4: {
5: }
2、 OperationContractAttribute特性具有以下一些成员:
1: public sealed class OperationContractAttribute : Attribute
2: {
3: public string Action { get; set; }
4: public bool AsyncPattern { get; set; }
5: public bool HasProtectionLevel { get; }
6: public bool IsOneWay { get; set; }
7: public string Name { get; set; }
8: public ProtectionLevel ProtectionLevel { get; set; }
9: public string ReplyAction { get; set; }
10: }
在我们定义的服务类型里面需要指定一组操作方法,这些操作方法要作为契约的一部分必须应用[OperationContract],同时还可以指定操作契约的一个或多个属性,最常用的就是指定Name、Action和ReplyAction。因为服务契约里面的每个操作都要有一个唯一的名称,所以面向对象编程的重载要素在这儿就不适用,不过我们可以通过为重名的每个操作方法的操作契约的名称属性设置一个唯一的名称来表示这个操作方法的重载,如以下设置:
1: [ServiceContract(Name = "CalculatorService", Namespace = "http://www.ainote.cn")]
2: public interface ICalculator1
3: {
4: [OperationContract(Name = "AddDouble")]
5: double Add(double x, double y);
6: [OperationContract(Name = "AddInt")]
7: int Add(int x, int y);
8: }
[OperationContract]的Action和ReplyAction属性,默认情况下Action和ReplyAction属性的值是有服务契约命名空间、服务契约的名称以及操作名称三者共同组成,比如:http://www.ainote.cn/CalculatorService/Add;ReplyAction属性的值则是在Action属性值的基础上加上Response后缀。我们也可以在服务契约里面为这两个属性指定一个唯一的值。如:
1: [ServiceContract(Name="CalculatorService",Namespace="http://www.ainote.cn",
2: ConfigurationName="CalculatorCatract",CallbackContract=typeof(ICalculatorCallback))]
3: public interface ICalculator
4: {
5: [OperationContract(Action = "http://www.ainote.cn/CalculatorService/AddDouble",
6: ReplyAction = "http://www.ainote.cn/CalculatorService/AddDoubleReply")]
7: double Add(double x, double y);
8: [OperationContract(Name = "AddInt", Action = "http://www.ainote.cn/CalculatorService/Add",
9: ReplyAction = "http://www.ainote.cn/CalculatorService/AddReply")]
10: int Add(int x, int y);
11: }
三、服务契约的继承
服务契约本质上是一个类或接口,所以他具有面向对象的一些特征,但是ServiceContractAttribute特性所应用的AttributeUsage的Inherited属性设置为false所以服务契约不具有继承性,OperationContractAttribute特性所应用的AttributeUsage的Inherited属性设置为true,他就具有继承性。不过为了支持服务契约的继承性我们必须通过在派生类型上应用ServiceContractAttribute特性。
1: [ServiceContract]
2: public interface IBase
3: {
4: [OperationContract]
5: void BaseMethod(string str);
6: }
7: [ServiceContract]
8: public interface IDerived
9: {
10: [OperationContract]
11: void DerivedMethod(string str);
12: }
终结点的配置如下:
1: <service name="Stone.Contract.Service.DerivedService" >
2: <endpoint address="http://127.0.0.1:126/DerivedService"
3: binding="ws2007HttpBinding"
4: contract="Stone.Contract.Contract.IDerived"></endpoint>
5: </service>
6: <client>
7: <endpoint name="DerivedService_Client" address="http://127.0.0.1:126/DerivedService"
8: binding="ws2007HttpBinding"
9: contract="Stone.Contract.Contract.IDerived"></endpoint>
10: <endpoint name="BaseService_Client" address="http://127.0.0.1:126/DerivedService"
11: binding="ws2007HttpBinding"
12: contract="Stone.Contract.Contract.IBase"></endpoint>
13: </client>
客户端就可以分别创建IDerived和IBase的代理,调用各自契约里面的操作方法。
四、消息交换模式
WCF 支持单向、请求-回复、双工三种消息交换模式,其中请求-回复模式是服务操作的默认消息交换模式,单向模式也是最基本的交换模式,通过操作契约就可以直接指定单向和请求-回复模式,双工模式的消息交换需要服务端对客户端的回调。
请求-回复模式
对于任何一个服务操作默认就是采用的这种消息交换模式,输入参数列表决定请求消息的格式。对于有返回值的操作方法则由返回值类型决定回复消息的格式;对没有返回值的操作方法(也就是返回值为void的方法)它的回复消息的主体就是为空;对于参数上标注了ref和out修饰符的操作方法对应回复消息由返回类型、out和ref参数的类型共同决定。
1: [ServiceContract(Name="CalculatorService",Namespace="http://www.ainote.cn",
2: ConfigurationName="CalculatorCatract")]
3: public interface ICalculator
4: {
5: [OperationContract]
6: double Add(double x, double y);
7: }
当把服务的元素数据以WSDL的方式发布出来时,服务契约对应WSDL中的<portType>节点,该节点具有一系列的<operations>子元素,这些<operations>子元素 表示服务契约中定义的操作契约,表示操作契约的<operations>元素通过<input>元素和<output>元素的有序组合来描述消息的交换模式。 <input>和<output> 元素的message属性指定WSDL中的<message>元素所对应的名称,而表示消息组成部分的<part>子元素则通过element引用的以XSD定义的XML元素所表示相应的元素节点结构。在<types> 元素节点的XSD定义中名称为Add和AddResponse的xml元素分别定义请求消息和回复消息的结构。
用XSD定义的XML元素所表示的<portType>节点:
1: <wsdl:portType name="CalculatorService">
2: <wsdl:operation name="Add">
3: <wsdl:input wsaw:Action="http://www.ainote.cn/CalculatorService/Add" message="tns:CalculatorService_Add_InputMessage" />
4: <wsdl:output wsaw:Action="http://www.ainote.cn/CalculatorService/AddResponse" message="tns:CalculatorService_Add_OutputMessage" />
5: </wsdl:operation>
6: <wsdl:operation name="Substract">
7: <wsdl:input wsaw:Action="http://www.ainote.cn/CalculatorService/Substract" message="tns:CalculatorService_Substract_InputMessage" />
8: </wsdl:operation>
9: </wsdl:portType>
用XSD定义的XML元素所表示的<message>节点:
1: <wsdl:message name="CalculatorService_Add_InputMessage">
2: <wsdl:part name="parameters" element="tns:Add" />
3: </wsdl:message>
4: <wsdl:message name="CalculatorService_Add_OutputMessage">
5: <wsdl:part name="parameters" element="tns:AddResponse" />
6: </wsdl:message>
用XSD定义的XML元素所表示的<types>节点:
1: <wsdl:types>
2: <xsd:schema targetNamespace="http://www.ainote.cn/Imports">
3: <xsd:import schemaLocation="http://127.0.0.1:99/CalculatorService/Metadata?xsd=xsd0" namespace="http://www.ainote.cn" />
4: <xsd:import schemaLocation="http://127.0.0.1:99/CalculatorService/Metadata?xsd=xsd1" namespace="http://schemas.microsoft.com/2003/10/Serialization/" />
5: </xsd:schema>
6: </wsdl:types>
单向模式
如果我们调用某个服务时不需要有返回结果,并且也不关心服务调用是否成功执行,在这种情况下我们就可以采用单向的消息交换模式。在采用单向模式的服务调用时,一旦消息进入网络传输层,就马上返回,此后的过程完全与客户端无关了。
如果需要将某个方法定义成单向服务操作,需要将应用在该服务操作上的 OperationContractAttribute 特性的IsOneWay 显式的设置成true,并且单向服务操作不允许有返回值,也不允许参数有任何的ref和out修饰符,所以只有返回类型为void 和没有任何引用参数 (就是任何参数不带ref和out修饰符)的方法才能定义成单向服务操作。
1: [ServiceContract(Name="CalculatorService",Namespace="http://www.ainote.cn",
2: ConfigurationName="CalculatorCatract")]
3: public interface ICalculator
4: {
5: [OperationContract(IsOneWay = true)]
6: void Substract(double x, double y);
7: }
在基于单向的消息交换模式下的服务操作只有输入消息没有输出消息,所以单向服务操作在WSDL中用XSD定义的XML元素结构在<operations> 元素下只有输入消息<input>元素。如下XML片段:
1: <wsdl:operation name="Substract">
2: <soap12:operation soapAction="http://www.ainote.cn/CalculatorService/Substract" style="document" />
3: <wsdl:input>
4: <wsp:PolicyReference URI="#MyBinding_CalculatorService_Substract_Input_policy" />
5: <soap12:body use="literal" />
6: </wsdl:input>
7: </wsdl:operation>
双工模式
双工模式的消息交换,使参与消息交换的双方均可以向对方发送消息,使服务能回调客户端,同时还可以帮助我们实现基于发布-订阅的方式进行通信。定义双工通信的服务契约时还需要定义一个回调契约,并且服务终结点的绑定必须采用具有双工通信能力的NetTcpBinding或WSDualHttpBinding。就传输协议本身来说,TCP 是支持双向通信的,而HTTP 不支持,所以WSDualHttpBinding 提供的双向通信本质上是通过在服务端和客户端各创建一个HTTP 通道以实现双向通信的回调。
1: [ServiceContract(Name="CalculatorService",Namespace="http://www.ainote.cn",
2: ConfigurationName="CalculatorCatract",CallbackContract=typeof(ICalculatorCallback))]
3: public interface ICalculator
4: {
5: [OperationContract(IsOneWay = true)]
6: double Add(double x, double y);
7: [OperationContract(IsOneWay = true)]
8: void Substract(double x, double y);
9: }
服务端配置:
1: <configuration>
2: <system.serviceModel>
3: <services>
4: <service name="Stone.Contract.Service.CalculatorService" behaviorConfiguration="CalculatorServiceBehavior">
5: <!--netTcpBinding实现双工通信的回调-->
6: <endpoint address="net.tcp://127.0.0.1:99/CalculatorService"
7: binding="netTcpBinding"
8: contract="CalculatorCatract"></endpoint>
9:
10: <!--wsDualHttpBinding实现双工通信回调-->
11: <endpoint address="http://127.0.0.1:98/CalculatorService"
12: binding="wsDualHttpBinding"
13: contract="CalculatorCatract"></endpoint>
14: </service>
15: <behaviors>
16: <serviceBehaviors>
17: <behavior name="CalculatorServiceBehavior">
18: <serviceMetadata httpGetEnabled="true" httpGetUrl="http://127.0.0.1:999/CalculatorService/Metadata" />
19: <serviceDebug includeExceptionDetailInFaults="true"/>
20: </behavior>
21: </serviceBehaviors>
22: </behaviors>
23: </system.serviceModel>
24: </configuration>
客户端配置:
1: <configuration>
2: <system.serviceModel>
3: <client>
4: <endpoint name="CalculatorService_NetTcpClient" address="net.tcp://127.0.0.1:99/CalculatorService"
5: binding="netTcpBinding"
6: contract="CalculatorCatract"></endpoint>
7:
8: <!--wsDualHttpBinding实现双工通信回调-->
9: <endpoint name="CalculatorService_WsDaulClient" address="http://127.0.0.1:98/CalculatorService"
10: binding="wsDualHttpBinding"
11: contract="CalculatorCatract"></endpoint>
12: </client>
13: </system.serviceModel>
14: </configuration>
客户端调用的时候需要通过 DuplexChannelFactory 创建服务代理。
1: static void CalculatorServiceNetTcpClient()
2: {
3: InstanceContext callback = new InstanceContext(new CalculatorCallbackService());
4: using (DuplexChannelFactory<ICalculator> channel = new DuplexChannelFactory<ICalculator>(callback, "CalculatorService_NetTcpClient"))
5: {
6: ICalculator proxy = channel.CreateChannel();
7: proxy.Substract(10,5);
8: }
9: }
WCF服务学习笔记之契约就总结到这儿,有关契约的其他信息后续再补充。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。