WCF初探-15:WCF操作协定
前言:
- 在前面的文章中,我们定义服务协定时,在它的操作方法上都会加上OperationContract特性,此特性属于OperationContractAttribute 类,将OperationContract应用于方法,以指示该方法实现作为服务协定(由 ServiceContractAttribute 属性指定)一部分的服务操作。OperationContractAttribute 属性声明方法是服务协定中的操作。 只有具有 OperationContractAttribute 属性的方法可作为服务操作公开。 不含有 OperationContractAttribute 标记的所有方法的服务协定不公开任何操作。公开的操作方法我们称之为操作协定。
操作协定的属性:
- Action: 获取或设置请求消息的 WS-Addressing 操作。 WCF 根据请求消息的操作将它们调度至方法。
- 使用 Action 属性控制方法的输入消息的操作。 由于 WCF 使用该操作将传入消息调度至相应方法,因此在协定操作中使用的消息必须具有唯一的操作。 默认操作值由以下几项组成:协定命名空间(默认值为“http://tempuri.org/”)、协定名称(如果没有使用显式服务接口,则为接口名称或类名)、操作名称,并且如果该消息是一个相关的响应,则还有一个附加字符串(“Response”)。 您可以使用 Action 属性重写该默认值。
- 若要指示服务操作可处理该服务接收的所有消息,但又不能定向到服务操作,请指定值“*”(星号)。 这种类型的操作(称为不匹配的消息处理程序)必须具有下列方法签名之一,否则会引发 InvalidOperationException :该服务操作只能接受一个 Message 对象,并且返回一个 Message 对象;该服务操作只能接受一个 Message 对象,并且不返回任何内容(即返回 void)。
- 服务协定只能有一个 Action 属性设置为“*”的服务操作。 当 IsInitiating 属性设置为 false 时,服务类实现的相同 listenUri 所承载的任何服务协定组可具有多个 Action 属性设置为“*”的服务操作。 然而,其中只有一个服务操作可将 Action 属性设置为“*”,并将 IsInitiating 属性设置为 true。
- AsyncPattern :属性指示使用 Begin/End 方法对可以实现或异步调用该操作。
- 使用 AsyncPattern 属性生成可在服务器和/或客户端异步调用的服务操作。 AsyncPattern 属性通知运行库 Begin 方法有一个符合 .NET Framework 异步方法设计模式的匹配的 End 方法。 生成用以实现服务操作的服务器异步方法可增强服务器的可伸缩性和性能,而不会影响服务的客户端。如果服务操作在执行完可异步执行的较长操作后,必须将某些内容返回至客户端,建议使用此方法。
- 这不会影响客户端,因为服务器上的异步方法对是实现详细信息,该信息不会影响操作的基础 Web 服务描述语言 (WSDL) 描述。 此类方法在客户端显示为包含 <input> 和相关 <output> 消息的单个操作。 WCF 自动将入站消息路由至 Begin<methodName> method and routes the results of the End<methodName> 调用的结果路由至出站消息。 因此,客户端信道可将方法对表示为单个同步操作或一个异步操作对。 客户端表示形式在任何情况下都不会以任何方式影响服务器上的异步实现。
- 客户端协定可使用 AsyncPattern 属性指示异步方法对,即客户端可使用该方法对异步调用操作。 通常,客户端应用程序使用 ServiceModel 元数据实用工具 (Svcutil.exe) 工具和 /async 选项生成客户端可以调用使用异步操作的 Begin<methodName> 和 End<methodName> 方法对。
- 如果服务操作具有异步和同步两个版本,则服务上的默认行为是调用同步版本。
- IsOneWay :获取或设置一个值,该值指示操作是否返回答复消息。
- 使用 IsOneWay 属性可以指示操作不返回答复消息。 这种类型的操作对通知或事件样式通信十分有用,特别是双向通信。 如果不等待基础响应消息,则单向操作的调用方在处理请求消息时无法直接检测错误。
- 在面向双工(或双向)服务的应用程序中,客户端和服务器相互独立地通信,并且客户端信道可使用其方法中的 IsOneWay 属性指示服务可单向调用客户端(该客户端可作为事件处理)。 这不会返回调用或生成消息,因为该服务不需要任何响应消息。
- 如果 IsOneWay 属性设置为 false(默认值),即使返回 void 的方法也会生成答复消息。 在此种情况下,基础结构将创建并发送一条空消息,以向调用方指示该方法已返回内容。(通过此方法使基础结构可以将 SOAP 错误发送回客户端。)将 IsOneWay 设置为 true 是取消创建和调度响应消息的唯一方法。
- 单向方法不得返回一个值或具有 ref 或 out 参数;否则将引发 System.InvalidOperationException 异常。
- 指定操作是单向操作,只表示它没有响应消息。 如果无法建立连接、出站消息非常大或该服务无法足够快地读取入站信息,则可能会阻止。 如果客户端要求非阻止调用,则会生成 AsyncPattern 操作。
- IsInitiating: 获取或设置一个值,该值指示方法是否实现可在服务器上启动会话(如果存在会话)的操作。
- ServiceContractAttribute.SessionMode 的值必须为 Allowed 或 Required 且使用的绑定必须要求或允许会话,IsInitiating 属性才能正常工作。
- 默认为 true,这意味着操作可以是通道上调用的第一个操作。 除了调用该方法之外,后续的调用对于启动方法无效。 不会创建其他任何会话。 如果协定不使用会话,则将 IsInitiating 设置为 false 会被忽略。通常,将 IsInitiating 设置为 false 可强制客户端在调用此方法之前,调用服务上的另一个方法。
- IsInitiating 和 Action 属性之间存在交互操作。 服务协定只能有一个 Action 属性设置为“*”的服务操作。 当 IsInitiating 属性设置为 false 时,服务类实现的相同侦听 URI 所承载的任何服务协定组可具有多个 Action 属性设置为“*”的服务操作。 然而,其中只有一个服务方法可将 Action 属性设置为“*”,并将 IsInitiating 属性设置为 true。
- 如果服务收到非启动操作的消息,则该服务返回 ActionNotSupported SOAP 错误。 客户端将这种情况当作 FaultException。 如果客户端首先调用非启动操作,则客户端运行库会引发 System.InvalidOperationException。
- IsTerminating: 获取或设置一个值,该值指示服务操作在发送答复消息(如果存在)后,是否会导致服务器关闭会话。
- 使用 IsTerminating 属性指示调用服务操作可终止通信会话。
- 在客户端应用程序中,将 IsTerminating 值设置为 true 以指示 WCF 在答复到达后,关闭信道。
- 在服务中,如果客户端在该期间内不关闭信道,则将会设置计时器并中止信道。
- 如果调用方侦听的是 OperationContractAttribute.IsTerminating 操作的 OperationContext.OperationCompleted 事件,则在收到响应时可能会阻塞。 处理这种情况的正确方法是,当引发 OperationCompleted 时在其他线程上调度工作,然后从该事件处理程序立即返回。
- ProtectionLevel 获取或设置一个值,该值指定是否必须对操作的消息进行加密和/或签名。
- 运行时的保护行为是在下列属性中设置的保护级别值的组合,这一点很重要。 这些属性具有层次结构。 除非已为较窄范围显式设置了某个不同的值,否则设置最外层的值将为所有较窄的范围确定默认设置。 在这种情况下,外层的值将保持所有较窄的范围的默认设置,但特定的设置除外。例如,如果将 ServiceContractAttribute.ProtectionLevel 设置为 ProtectionLevel.EncryptAndSign,并且其他较窄范围都没有设置保护级别,则会对操作协定中的所有消息进行加密和签名。 但是,如果其中一个操作将 ProtectionLevel 设置为 ProtectionLevel.Sign,那么对此操作的消息只进行签名,而对协定中的所有其他消息进行加密和签名。
- 当协定上没有显式指定保护级别并且基础绑定支持安全性时(无论处于传输级别还是处于消息级别),整个协定的有效保护级别将为 ProtectionLevel.EncryptAndSign。 如果绑定不支持安全性(如 BasicHttpBinding),则整个协定的有效 System.Net.Security.ProtectionLevel 为 ProtectionLevel.None。 因此,根据终结点绑定,即使协定指定了 ProtectionLevel.None,客户端也可以要求不同的消息或传输级别安全保护。
- 消息保护级别的层级结构:
- ReplyAction:获取或设置用于该操作答复消息的 SOAP 操作的值。
- 除指定答复消息操作标头的特定值以外,还可以指定字符串“*”(星号)。 在服务中指定星号可指示 WCF 不向消息中添加答复操作,如果您是直接对消息进行编程会十分有用。 在客户端应用程序中指定星号可指示 WCF 不验证答复操作。
WCF操作协定示例:
- 解决方案如下:
- 工程结构说明:
- Service:服务契约定义和实现。在服务契约接口中,我们定义了MethodOne、MethodTwo、MethodThree三个操作契约,其中MethodThree使用异步实现,这个示例也说明了异步服务的实现。在服务契约中,我们启用会话要求,将SessionMode设置为Required,目的是为了验证IsInitiating(启动服务器会话)和IsTerminating(服务操作在发送答复消息后,关闭会话)。
设置了操作契约Action(设置请求消息的 WS-Addressing 操作)和ReplyAction(设置用于该操作答复消息的 SOAP 操作的值)
ISampleMethod.cs的代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; namespace Service { [ServiceContract(Name = "SampleMethodContract", Namespace = "http://wangweimutou.SampleMethodContract", SessionMode=SessionMode.Required)] public interface ISampleMethod { /// <summary> /// IsInitiating默认值为true,IsTerminating默认值为false /// </summary> /// <param name="msg"></param> [OperationContract(Name="OCMethodOne", AsyncPattern=false, IsInitiating=true, IsTerminating=false, Action = "http://wangweimutou.SampleMethodContract/RequestMethodOne", ReplyAction = "http://wangweimutou.SampleMethodContract/ResponseMethodOne")] string MethodOne(string msg); [OperationContract(Name = "OCMethodTwo", AsyncPattern = false, IsInitiating = true, IsTerminating = false, Action = "http://wangweimutou.SampleMethodContract/RequestMethodTwo", ReplyAction = "http://wangweimutou.SampleMethodContract/ResponseMethodTwo")] string MethodTwo(string msg); [OperationContract(Name = "OCMethodThree", AsyncPattern = true, IsInitiating = true, IsTerminating = false, Action = "http://wangweimutou.SampleMethodContract/RequestMethodThree", ReplyAction = "http://wangweimutou.SampleMethodContract/ResponseMethodThree")] IAsyncResult BeginMethodThree(string msg, AsyncCallback callback, object asyncState); string EndMethodThree(IAsyncResult result); } }
SampleMethod.cs的代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.ServiceModel; namespace Service { public class SampleMethod :ISampleMethod { public string MethodOne(string msg) { return "You called MethodOne return message is: " + msg; } public string MethodTwo(string msg) { return "You called MethodTwo return message is: " + msg; } public IAsyncResult BeginMethodThree(string msg, AsyncCallback callback, object asyncState) { return new CompletedAsyncResult<string>(msg); } public string EndMethodThree(IAsyncResult r) { CompletedAsyncResult<string> result = r as CompletedAsyncResult<string>; return "You called MethodThree return message is: " + result.Data; } } class CompletedAsyncResult<T> : IAsyncResult { T data; public CompletedAsyncResult(T data) { this.data = data; } public T Data { get { return data; } } #region IAsyncResult Members public object AsyncState { get { return (object)data; } } public WaitHandle AsyncWaitHandle { get { throw new Exception("The method or operation is not implemented."); } } public bool CompletedSynchronously { get { return true; } } public bool IsCompleted { get { return true; } } #endregion } }
2. Host:控制台应用程序,服务承载程序。添加对Service程序集的引用,实现以下代码。Program.cs的代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Service; using System.ServiceModel; namespace Host { class Program { static void Main(string[] args) { using (ServiceHost host = new ServiceHost(typeof(SampleMethod))) { host.Opened += delegate { Console.WriteLine("服务已经启动,按任意键终止!"); }; host.Open(); Console.Read(); } } } }
App.config代码如下:
<?xml version="1.0"?> <configuration> <system.serviceModel> <services> <service name="Service.SampleMethod" behaviorConfiguration="mexBehavior"> <host> <baseAddresses> <add baseAddress="http://localhost:1234/SampleMethod/"/> </baseAddresses> </host> <endpoint address="" binding="wsHttpBinding" contract="Service.ISampleMethod" /> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> </service> </services> <behaviors> <serviceBehaviors> <behavior name="mexBehavior"> <serviceMetadata httpGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
3. Client:控制台应用程序。客户端应用程序,启动Host服务承载程序,添加对服务地址http://localhost:1234/SampleMethod/引用后,将命名空间设置为ServiceRef,
勾选生成异步操作复选框,生成异步客户端代理类(参照WCF初探-11:WCF客户端异步调用服务),我们就可以对程序进行同步和异步的调用了。Program.cs代码如下:
运行程序,结果显示如下:
从结果中,我们可以看到,程序停止了对MethodTwo和MethodThree的调用,这是因为MethodOne的IsTerminating设置为true,所以客户端代理在调用完MethodOne
后就关闭了对服务器会话的支持。接下来,我们将调用MethodOne的代码注释掉,编译Client后再次运行,会得到一下结果:
由于我们将MethodTwo的IsInitiating设置为了false,导致服务器会话没有启动,所以服务调用失败。接下来,我们将MethodOne、MethodTwo、MethodThree三个操作
契约的IsInitiating和IsTerminating分别设置为true和false,也就是设置为默认值,重新编译程序后,运行客户端我们可以看到如下结果:
接下来,我们再来查看一下操作契约设置的属性值,从上面的客户端程序代码可以看出,服务契约和操作契约的方法和名称都改成了设定值,如MethodOne变成了OCMethodOne。
打开客户端测试程序,添加对服务地址的引用后,我们就可以看到消息的请求和响应,如观察到MethodOne调用的结果如下:
总结:
- 在上面的示例中,我们修改了操作契约的部分属性,也从运行结果和交换的消息中验证了这些修改的属性。并且还完成了异步服务的实现。关于消息保护级别属性将在以后的博文中做解析。
- 关于OperationContract的IsOneWay可以查看以下博文:
WCF初探-5:WCF消息交换模式之双工通讯(Duplex)
- 关于客户端异步调用服务可以查看以下博文: