以前所看到的Duplex模式都是使用One Way模式,这里我在微软Duplex例子的基础上增加了Request-Replay服务操作,并且介绍一种将SVCUtil命令写在IDE的编译事件中以方便对服务契约的修改的方法。
WCF Service Contract之MEPs
LazyBee
WCF支持的消息交换模式MEPs(Message Exchange Partten)为三种:请求-响应模式(Request-Replay), 单向模式(One Way),双向模式(Duplex).
[ServiceContract(SessionMode=SessionMode.Required)]
public interface ICalculatorSession
{
[OperationContract(IsOneWay = true)]
void AddTo(double n);
[OperationContract]
double Equals();
}
以上AddTo方法就是单向模式,而Equals就是双向模式。(缺省的IsOneWay的值是false)
缺省的消息交换模式就是Request-Replay模式。
注意:如果指定的方法的消息交换模式是One Way,那么这个方法的返回值必须是void,并且不能有out和ref的参数。当WCF服务的客户端不应该等到对应的操作的完成并且也不需要处理SOAP错误时,采用这种单向模式。(这个方法的返回值是void并不代表该方法会返回消息给调用者。)
Duplex模式是客户端和服务之间可以相互独立的使用One Way和Request-Replay进行双向通讯的消息交换模式。
在使用Duplex进行消息交换的时候,并且客户端调用的是定义为Request-Reply的服务操作,而且在这个服务操作中又调用客户端的另外一个Request-Reply的一个回调方法,这时就会遇到如下错误,你可以根据提示进行修复操作。
错误:This operation would deadlock because the reply cannot be received until the current Message completes processing. If you want to allow out-of-order message processing, specify ConcurrencyMode of Reentrant(可重入的并发模式) or Multiple on ServiceBehaviorAttribute.
以下是来自微软的例子中的一个Duplex方式的程序,经过稍加改动(红色的方法是增加的代码):
//服务器端代码
=================================================================
using System;
using System.ServiceModel;
namespace Microsoft.ServiceModel.Samples
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
// Define a duplex service contract.
// A duplex contract consists of two interfaces.
// The primary interface is used to send messages from the client to the service.
// The callback interface is used to send messages from the service back to the client.
// ICalculatorDuplex allows one to perform multiple operations on a running result.
// The result is sent back after each operation on the ICalculatorCallback interface.
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples", SessionMode=SessionMode.Required, CallbackContract=typeof(ICalculatorDuplexCallback))]
public interface ICalculatorDuplex
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
[OperationContract(IsOneWay = true)]
void Clear();
[OperationContract(IsOneWay = true)]
void AddTo(double n);
[OperationContract(IsOneWay = true)]
void SubtractFrom(double n);
[OperationContract(IsOneWay = true)]
void MultiplyBy(double n);
[OperationContract(IsOneWay = true)]
void DivideBy(double n);
![](/Images/OutliningIndicators/InBlock.gif)
[OperationContract]
String Request_ReplayMethodInDuplex(int i);
}
![](/Images/OutliningIndicators/InBlock.gif)
// The callback interface is used to send messages from service back to client.
// The Result operation will return the current result after each operation.
// The Equation opertion will return the complete equation after Clear() is called.
public interface ICalculatorDuplexCallback
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
[OperationContract(IsOneWay = true)]
void Result(double result);
[OperationContract(IsOneWay = true)]
void Equation(string eqn);
[OperationContract(IsOneWay=true)]
void ShowMessage(string s);
}
![](/Images/OutliningIndicators/InBlock.gif)
// Service class which implements a duplex service contract.
// Use an InstanceContextMode of PerSession to store the result
// An instance of the service will be bound to each duplex session
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class CalculatorService : ICalculatorDuplex
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
double result = 0.0D; string equation;
public CalculatorService()
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
equation = result.ToString();
}
![](/Images/OutliningIndicators/InBlock.gif)
public void Clear()
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
Callback.Equation(equation + " = " + result.ToString());
equation = result.ToString();
}
![](/Images/OutliningIndicators/InBlock.gif)
public void AddTo(double n)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
result += n;
equation += " + " + n.ToString();
Callback.Result(result);
}
![](/Images/OutliningIndicators/InBlock.gif)
public void SubtractFrom(double n)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
result -= n;
equation += " - " + n.ToString();
Callback.Result(result);
}
![](/Images/OutliningIndicators/InBlock.gif)
public void MultiplyBy(double n)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
result *= n;
equation += " * " + n.ToString();
Callback.Result(result);
}
![](/Images/OutliningIndicators/InBlock.gif)
public void DivideBy(double n)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
result /= n;
equation += " / " + n.ToString();
Callback.Result(result);
}
![](/Images/OutliningIndicators/InBlock.gif)
ICalculatorDuplexCallback Callback
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
get
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
return OperationContext.Current.GetCallbackChannel<ICalculatorDuplexCallback>();
}
}
public string Request_ReplayMethodInDuplex(int i)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
Callback.ShowMessage("Hello World!" + i);
return "hello world " + i;
}
}
}
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
//Web.Config的内容
===============================================================================================================
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service
name="Microsoft.ServiceModel.Samples.CalculatorService"
behaviorConfiguration="CalculatorServiceBehavior">
<!-- This endpoint is exposed at the base address provided by host: http://localhost/servicemodelsamples/service.svc -->
<endpoint address=""
binding="wsDualHttpBinding"
contract="Microsoft.ServiceModel.Samples.ICalculatorDuplex" />
<!-- the mex endpoint is exposed at http://localhost/servicemodelsamples/service.svc/mex -->
<endpoint address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
</services>
![](/Images/OutliningIndicators/None.gif)
<!--For debugging purposes set the includeExceptionDetailInFaults attribute to true-->
<behaviors>
<serviceBehaviors>
<behavior name="CalculatorServiceBehavior">
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="True" />
</behavior>
</serviceBehaviors>
</behaviors>
![](/Images/OutliningIndicators/None.gif)
</system.serviceModel>
![](/Images/OutliningIndicators/None.gif)
<system.web>
<compilation debug="true"/>
</system.web>
</configuration>
![](/Images/OutliningIndicators/None.gif)
//Service.svc的内容
===================================================================================
<%@ServiceHost language=c# Debug="true" Service="Microsoft.ServiceModel.Samples.CalculatorService" %>
如何运行这个服务?
1 首先在IIS中创建一个http://localhost/servicemodelsamples的虚拟目录。
2 如果你是第一次运行WCF Service的话,需要将.svc扩展名和aspnet_isapi.dll关联,具体操作如下:
1. 打开IIS管理器.
2. 右单击 Web Sites 并且选择属性 Properties.
3. On the Home Directory tab, click 配置Configuration.
4. In the list of application mappings, verify that the .svc file is mapped to the aspnet_isapi.dll. If the file has not been mapped:
a. 单击增加按钮.
b. 在 Add/Edit Application Extension Mapping 对话框中, 单击浏览按钮
c. 找到aspnet_isapi.dll并单击Open.
d. 制定 .svc 扩展名
e. 确保Check that file exists 选项没有被选中.
f. 单击确定, 然后再单击确定回到站点属性窗口.
5. 单击确定关闭站点属性窗口。
3然后就可以在IE窗口中测试时候正常运行了。(http://localhost/servicemodelsamples/service.svc).
如何方便的产生客户端?
由于VS2008自带的WCFTestClient.exe不能测试duplex模式的WCF Service,所以必须手动来产生客户端测试,为了方便应对服务契约的变更,可以在client 的项目属性的Build Events中的Pre-Build event command line的文本框中增加如下命令以帮助自动生成客户端的代理类:
Call "D:\Program Files\Microsoft Visual Studio 9.0\VC\vcvarsall.bat" x86
svcutil /language:cs /noconfig /out:$(ProjectDir)generatedClient.cs /n:*,Microsoft.ServiceModel.Samples http://localhost/servicemodelsamples/service.svc?wsdl
客户端的实现文件如下:
============================================================================================================
using System;
using System.ServiceModel;
![](/Images/OutliningIndicators/None.gif)
namespace Microsoft.ServiceModel.Samples
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
// The service contract is defined in generatedClient.cs, generated from the service by the svcutil tool.
![](/Images/OutliningIndicators/InBlock.gif)
// Define class which implements callback interface of duplex contract
public class CallbackHandler : ICalculatorDuplexCallback
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
public void Result(double result)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
Console.WriteLine("Result({0})", result);
}
public void Equation(string eqn)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
Console.WriteLine("Equation({0})", eqn);
}
public void ShowMessage(string s)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
Console.WriteLine("ZXG:" + s);
}
}
![](/Images/OutliningIndicators/InBlock.gif)
class Client
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
static void Main()
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
// Construct InstanceContext to handle messages on callback interface
InstanceContext instanceContext = new InstanceContext(new CallbackHandler());
![](/Images/OutliningIndicators/InBlock.gif)
// Create a client
CalculatorDuplexClient client = new CalculatorDuplexClient(instanceContext);
![](/Images/OutliningIndicators/InBlock.gif)
Console.WriteLine("Press <ENTER> to terminate client once the output is displayed.");
Console.WriteLine();
![](/Images/OutliningIndicators/InBlock.gif)
// Call the AddTo service operation.
double value = 100.00D;
client.AddTo(value);
// Call the SubtractFrom service operation.
value = 50.00D;
client.SubtractFrom(value);
// Call the MultiplyBy service operation.
value = 17.65D;
client.MultiplyBy(value);
// Call the DivideBy service operation.
value = 2.00D;
client.DivideBy(value);
// Complete equation
client.Clear();
![](/Images/OutliningIndicators/InBlock.gif)
Console.WriteLine(client.Request_ReplayMethodInDuplex(2));
//Closing the client gracefully closes the connection and cleans up resources
client.Close();
Console.ReadLine();
}
}
}
![](/Images/OutliningIndicators/None.gif)
以下是客户端的配置文件:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<client>
<endpoint name=""
address="http://localhost/servicemodelsamples/service.svc"
binding="wsDualHttpBinding"
bindingConfiguration="DuplexBinding"
contract="Microsoft.ServiceModel.Samples.ICalculatorDuplex" />
</client>
<bindings>
<!-- configure a binding that support duplex communication -->
<wsDualHttpBinding>
<binding name="DuplexBinding"
clientBaseAddress="http://localhost:8000/myClient/">
</binding>
</wsDualHttpBinding>
</bindings>
</system.serviceModel>
</configuration>
![](/Images/OutliningIndicators/None.gif)
注意:在客户端代码中Console.WriteLine(client.Request_ReplayMethodInDuplex(2))的这行代码将调用服务端的Request-Reply的Request_ReplayMethodInDuplex服务操作,并且在Request_ReplayMethodInDuplex的实现中又调用了的客户端的One Way的ShowMessage回调方法。这个调用运行的非常正常。如果将ShowMessage回调方法变成Request-Replay方式(去掉OperationContract中的IsOneWay=true,或者将其值更改成false),在执行代码Console.WriteLine(client.Request_ReplayMethodInDuplex(2))的时候将出现前面所说的错误。
This operation would deadlock because the reply cannot be received until the current Message completes processing. If you want to allow out-of-order message processing, specify ConcurrencyMode of Reentrant(可重入的并发模式) or Multiple on ServiceBehaviorAttribute.