第二节 设计和实现服务协定
这一节需要知道的几个知识点
一.创建服务协定
二.数据协定
三.Out和Ref参数
一.创建服务协定
1.1WCF术语
• 消息
– 消息是一个独立的数据单元,它可能由几个部分组成,包括消息正文和消息头。
• 服务
– 服务是一个构造,它公开一个或多个终结点,其中每个终结点都公开一个或多个服务操作。
• 终结点
– 终结点是用来发送或接收消息(或执行这两种操作)的构造。终结点包括一个定义消息可以发送到的目的地的位置(地址)、一个描述消息应如何发送的通信机制规范(绑定)以及对于可以在该位置发送或接收(或两者皆可)的一组消息的定义(服务协定)— 该定义还描述了可以发送何种消息。
– WCF 服务作为一个终结点集合向外界公开。
1.2创建服务协定
•类或接口都可以定义服务协定
•建议使用接口,因为接口可以直接对服务协定建模(建模不代表最后的实现)
•服务协定接口具有托管接口的所有优点:
– 服务协定接口可以扩展任何数量的其他服务协定接口。
– 一个类可以通过实现服务协定接口来实现任意数量的服务协定。(类是单继承的,所以使用接口就有好处)
– 可以通过更改接口实现来修改服务协定的实现,而让服务协定保持不变。(接口的定义和具体的定义分开了。)
– 可以通过实现旧接口和新接口来确定服务的版本。老客户端连接到原始版本,而新客户端则可以连接到较新的版本。(接口的扩展吧?)
定义:
• 定义服务协定(终结点)
– 在类或接口上使用ServiceContractAttribute 属性标记
• 定义服务操作(服务方法)
– 在方法上使用OperationContractAttribute 属性对其进行标记
(如果没有用这个属性进行标记,那么就不会公开给客户端)
• 参数和返回值
– 每个操作都有一个返回值和一个参数,即使它们为void。可以使用局部方法将对对象的引用从一个对象传递到另一个对象,但与局部方法不同的是,服务操作不会传递对对象的引用,它们传递的只是对象的副本。
(因为本机编程都是同一个内存,但是wcf是面向互联网,所以传递的是对象的一个拷贝)
– 这一点很重要,这是因为参数或返回值中使用的每个类型都必须是可序列化的,换言之,该类型的对象必须能够转换为字节流,并能够从字节流转换为对象。
– 默认情况下,基元类型是可序列化的,.NET Framework 中的很多类型都是可序列化的。
问题:上面讲到每个操作都有一个返回值和一个参数,即使它们为void。C#当中void没有返回值。为什么会这样呢,就涉及到了WCF服务操作的消息的模式,这里有三种方式。
①请求/应答(默认方式)
②单工
③双工
• 请求/应答
– 通过请求/应答模式,请求发送方(客户端应用程序)将接收与请求相关的答复。这是默认的模式,因为它既支持传入操作(一个或多个参数传递到该操作中),也支持返回操作(该操作将一个或多个输出值传回给调用方)
(下面就是一个请求/应答的模式,不用特殊的处理)
[OperationContract]
string Hello(string greeting);
– 请注意,除非指定其他基础消息模式,否则,即使服务操作返回void(在Visual Basic 中为Nothing),也属于请求/答复消息交换。
– 操作的结果是:除非客户端异步调用操作,否则客户端将停止处理,直到收到返回消息,即使该消息正常情况下为空时也是如此。(优缺点呦,下面)
• 缺点
– 如果执行操作需要很长的时间,则会降低客户端性能和响应能力
• 优点
– 响应消息中可返回SOAP 错误,这表明可能在通信或处理中发生了一些与服务有关的错误状况
• 单向
– 如果WCF服务应用程序的客户端不必等待操作完成,并且不处理SOAP 错误,则该操作可以指定单向消息模式。
– 单向操作是客户端调用操作并在WCF 将消息写入网络后继续进行处理的操作。通常这意味着,除非在出站消息中发送的数据极其庞大,否则客户端几乎立即继续运行(除非发送数据时出错)。此种类型的消息交换模式支持从客户端到服务应用程序的类似于事件的行为。
– 若要为返回void 的操作指定单向消息交换,请将IsOneWay 属性设置为true,默认为false.
[OperationContract(IsOneWay=true)]
void Hello(string greeting);
此方法与前面的请求/应答示例相同,但是,将IsOneWay属性设置为true 意味着尽管方法相同,服务操作也不会发送返回消息,而客户端将在出站消息抵达通道层时立即返回
• 双工
– 双工模式的特点是,无论使用单向消息发送还是请求/答复消息发送方式,服务和客户端均能够独立地向对方发送消息。对于必须直接与客户端通信或向消息交换的任意一方提供异步体验(包括类似于事件的行为)的服务来说,这种双向通信形式非常有用
– 由于存在与客户端通信的附加机制,双向模式比请求/答复或单向模式要略为复杂
– 若要设计双工协定,还必须设计回调协定,并将该回调协定(让服务的方法调用客户端的方法)的类型分配给标记服务协定的ServiceContractAttribute 属性(attribute)的CallbackContract 属性(property)。
– 若要实现双工模式,您必须创建第二个接口,该接口包含在客户端调用的方法声明
1.3 演示
I.请求/应答方式
①创建一个WCF工程
②默认会生成一个接口和一个类文件
③打开接口文件:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Runtime.Serialization; 5 using System.ServiceModel; 6 using System.Text; 7 8 namespace WcfServiceLibrary1 9 { 10 11 [ServiceContract] 12 public interface IService1 13 { 14 [OperationContract] 15 string GetData(int value); 16 17 [OperationContract] 18 CompositeType GetDataUsingDataContract(CompositeType composite); 19 20 // TODO: Add your service operations here 21 } 22 25 [DataContract] 26 public class CompositeType 27 { 28 bool boolValue = true; 29 string stringValue = "Hello "; 30 31 [DataMember] 32 public bool BoolValue 33 { 34 get { return boolValue; } 35 set { boolValue = value; } 36 } 37 38 [DataMember] 39 public string StringValue 40 { 41 get { return stringValue; } 42 set { stringValue = value; } 43 } 44 } 45 }
注意:接口上面使用了[ServiceContract],方法上面使用了 [OperationContract],标记这个是WCF用的接口和方法,否则外面访问不到。
这里定义了两个方法GetData和GetDataUsingDataContract
④打开类文件
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Runtime.Serialization; 5 using System.ServiceModel; 6 using System.Text; 7 8 namespace WcfServiceLibrary1 9 { 11 public class Service1 : IService1 12 { 13 public string GetData(int value) 14 { 16 return string.Format("You entered: {0}", value); 17 } 18 19 public CompositeType GetDataUsingDataContract(CompositeType composite) 20 { 21 if (composite == null) 22 { 23 throw new ArgumentNullException("composite"); 24 } 25 if (composite.BoolValue) 26 { 27 composite.StringValue += "Suffix"; 28 } 29 return composite; 30 } 31 } 32 }
上面就是接口的实现,GetData方法把传入的参数进行了输出,GetDataUsingDataContract方法先判断composite的值,为空抛出异常,composite.BoolValue为true时候,composite.StringValue的后面加上后缀。
以上就是WCF的服务程序了,然后开始了解如何使用。
⑤创建一个WinForm程序
1)拖一个button上去
2)添加web服务的引用
3)点击发现会自动找到同一个solution下的服务,点击OK
然后在button点击事件中添加下面代码:
1 private void button1_Click(object sender, EventArgs e) 2 { 3 ServiceReference1.Service1Client sc = new ServiceReference1.Service1Client(); 4 var result = sc.GetData(1); 5 MessageBox.Show(result); 6 }
再然后运行服务和winform程序
以上就是最简单的请求/应答方式的WCF程序
II.单工方式
修改上面IService1.cs的代码,如下:
1 [ServiceContract] 2 public interface IService1 3 { 4 [OperationContract] 5 string GetData(int value); 6 7 [OperationContract] 8 CompositeType GetDataUsingDataContract(CompositeType composite); 9 10 [OperationContract(IsOneWay=true)] 11 void TestSingle(int value); 12 // TODO: Add your service operations here 13 }
(IsOneWay = true)指定了是单工模式。
修改上面Service1.cs的代码,如下:
添加一个实现: 1 public void TestSingle(int value) 2 { 3 System.Threading.Thread.Sleep(10000); 4 }
编译一下wcf程序
然后修改winform程序,注意由于修改了服务,所以要更新一下ServiceReference
更新好后添加如下代码:
sc.TestSingle(1);
MessageBox.Show("Success");
注意我们在服务当中进行了延时,但是采用单工并不会等到服务端返回消息,所以客户端应该是立即执行messageBox的输出
运行程序:
III.双工方式
这个方式稍微麻烦一点,我们重新建立一个服务端和一个客户端
IService1.cs代码:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Runtime.Serialization; 5 using System.ServiceModel; 6 using System.Text; 7 8 namespace DuplexWCF 9 { 10 [ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples", SessionMode = SessionMode.Required,
11 CallbackContract = typeof(IFeaturesDuplexCallback))] 12 public interface IFeaturesDuplex 13 { 14 [OperationContract(IsOneWay = true)] 15 void GetFeatures(string career); 16 } 17 18 public interface IFeaturesDuplexCallback 19 { 20 [OperationContract(IsOneWay = true)] 21 void Features(string career); 22 } 23 }
解释
①CallbackContract = typeof(IFeaturesDuplexCallback)指定了服务器端要回调的客户端的接口,也就是服务器和客户端之间的回调协定。(也就是服务器和客户端讲:哥们一会我要用这个接口,你把它实现一下。)
②回调的客户端的接口的定义(一会要在客户端实现)
public interface IFeaturesDuplexCallback
{
[OperationContract(IsOneWay = true)]
void Features(string career);
}
Service1.cs代码
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Runtime.Serialization; 5 using System.ServiceModel; 6 using System.Text; 7 8 namespace DuplexWCF 9 { 10 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] 11 public class Service1 : IFeaturesDuplex 12 { 13 IFeaturesDuplexCallback callback = null; 14 15 public Service1() 16 { 17 callback = OperationContext.Current.GetCallbackChannel<IFeaturesDuplexCallback>(); 18 } 19 20 public void GetFeatures(string career) 21 { 22 callback.Features(career); 23 } 24 } 25 }
①OperationContext.Current.GetCallbackChannel<IFeaturesDuplexCallback>();
服务端可以通过OperationContext.Current.GetCallbackChannel<IFeaturesDuplexCallback>()来获得这个回调对象的引用,服务端有了这个回调对象的引用就可以去调用客户端实现的回调方法。
②GetFeatures服务器端的方法,用来返回一个职业的特点。里面调用了客户端的方法。
备注:
这里要注意,要修改配置文件
①修改为(我的原来是BasicHttpBinding)不修改不能用双工模式
<endpoint address="" binding="wsDualHttpBinding" contract="DuplexWCF.IFeaturesDuplex">
②修改服务的名字IFeaturesDuplex,默认创建时候是service1,这个为什么修改一会我会演示
编写客户端代码:创建一个控制台程序,在program.cs中添加如下代码:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using DuplexClient.ServiceReference1; 7 using System.ServiceModel; 8 9 namespace DuplexClient 10 { 11 public class CallbackHandler : IFeaturesDuplexCallback 12 { 13 public void Features(string careerFeatures) 14 { 15 Console.WriteLine(careerFeatures); 16 Console.WriteLine("笑点独特,行為怪異!"); 17 } 18 } 19 class Program 20 { 21 static void Main(string[] args) 22 { 23 InstanceContext instanceContext = new InstanceContext(new CallbackHandler()); 24 ServiceReference1.FeaturesDuplexClient sc = new ServiceReference1.FeaturesDuplexClient(instanceContext); 25 26 sc.GetFeatures("程序員"); 27 Console.ReadLine(); 28 } 29 } 30 }
解释:
①这段代码就是实现了IFeaturesDuplexCallback的方法的类
11 public class CallbackHandler : IFeaturesDuplexCallback
12 {
13 public void Features(string careerFeatures)
14 {
15 Console.WriteLine(careerFeatures);
16 Console.WriteLine("笑点独特,行為怪異!");
17 }
18 }
② InstanceContext instanceContext = new InstanceContext(new CallbackHandler());
将回调的类实例化,传给双工的服务,这样服务在调用回调方法的时候,才能具体知道方法是怎么实现的。就好像说,之前服务器告诉客户端我要回调你的方法,但是服务器只是告诉客户端要哪个接口,并没说具体的实现,所以这里客户端就把这个接口实现了,然后服务器用的时候直接拿。
结果:
以上结果可以看出,我在客户端调用服务器,然后服务器又回调我的客户端方法。
二.创建数据协定
• 面向服务的应用程序(例如Windows Communication Foundation(WCF) 应用程序)设计为与Microsoft 平台和非 Microsoft 平台上的最大可能数量的客户端应用程序进行互操作。
• 为了获得最大可能的互操作性,建议您使用DataContractAttribute 和DataMemberAttribute 属性对您的类型进行标记,以创建数据协定。
• 数据协定是服务协定的一部分,用于描述您的服务操作交换的数据。
• 数据协定是可选的样式协定:除非您显式应用数据协定属性,否则不会序列化任何类型或数据成员
• 数据协定与托管代码的访问范围无关:可以对私有数据成员进行序列化,并将其发送到其他位置,以便可以公开访问它们
• WCF 处理用于启用操作功能的基础 SOAP 消息的定义,并处理数据类型到消息正文的序列化和从消息正文进行的反序列化。数据类型一旦序列化,您就无需在设计操作时考虑基础消息交换基础结构
• 可以使用其他序列化机制。标准ISerializable, SerializableAttribute和IXmlSerializable 机制都可用于处理数据类型到基础SOAP 消息的序列化,这些消息可将数据类型从一个应用程序带到另一个应用程序
我们不用关心wcf底层是怎么实现的,只要在定义数据的时候使用DataContractAttribute 和DataMemberAttribute 就行了
三.Out和Ref参数
• 大部分情况下,您可以使用in参数(Visual Basic 中为ByVal)、out 和 ref 参数(Visual Basic 中为 ByRef)。由于out 和 ref 参数都指示数据是从操作返回的,类似如下的操作签名会指定需要请求/答复操作,即使操作签名返回void 也是如此
• 用out 或ref 参要求操具基础应消息,可以已修改的对象传回。如果操作是单向操作,则将在运行时引发InvalidOperationException 异常
可以利用这个参数可以做很多参数返回出来,但是注意单工的时候,是没有用的。