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


{
// 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

{
[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);

[OperationContract]
String Request_ReplayMethodInDuplex(int i);
}

// 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

{
[OperationContract(IsOneWay = true)]
void Result(double result);
[OperationContract(IsOneWay = true)]
void Equation(string eqn);
[OperationContract(IsOneWay=true)]
void ShowMessage(string s);
}

// 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

{
double result = 0.0D; string equation;
public CalculatorService()

{
equation = result.ToString();
}

public void Clear()

{
Callback.Equation(equation + " = " + result.ToString());
equation = result.ToString();
}

public void AddTo(double n)

{
result += n;
equation += " + " + n.ToString();
Callback.Result(result);
}

public void SubtractFrom(double n)

{
result -= n;
equation += " - " + n.ToString();
Callback.Result(result);
}

public void MultiplyBy(double n)

{
result *= n;
equation += " * " + n.ToString();
Callback.Result(result);
}

public void DivideBy(double n)

{
result /= n;
equation += " / " + n.ToString();
Callback.Result(result);
}

ICalculatorDuplexCallback Callback

{
get

{
return OperationContext.Current.GetCallbackChannel<ICalculatorDuplexCallback>();
}
}
public string Request_ReplayMethodInDuplex(int i)

{
Callback.ShowMessage("Hello World!" + i);
return "hello world " + i;
}
}
}


//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>

<!--For debugging purposes set the includeExceptionDetailInFaults attribute to true-->
<behaviors>
<serviceBehaviors>
<behavior name="CalculatorServiceBehavior">
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="True" />
</behavior>
</serviceBehaviors>
</behaviors>

</system.serviceModel>

<system.web>
<compilation debug="true"/>
</system.web>
</configuration>

//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;

namespace Microsoft.ServiceModel.Samples


{
// The service contract is defined in generatedClient.cs, generated from the service by the svcutil tool.

// Define class which implements callback interface of duplex contract
public class CallbackHandler : ICalculatorDuplexCallback

{
public void Result(double result)

{
Console.WriteLine("Result({0})", result);
}
public void Equation(string eqn)

{
Console.WriteLine("Equation({0})", eqn);
}
public void ShowMessage(string s)

{
Console.WriteLine("ZXG:" + s);
}
}

class Client

{
static void Main()

{
// Construct InstanceContext to handle messages on callback interface
InstanceContext instanceContext = new InstanceContext(new CallbackHandler());

// Create a client
CalculatorDuplexClient client = new CalculatorDuplexClient(instanceContext);

Console.WriteLine("Press <ENTER> to terminate client once the output is displayed.");
Console.WriteLine();

// 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();

Console.WriteLine(client.Request_ReplayMethodInDuplex(2));
//Closing the client gracefully closes the connection and cleans up resources
client.Close();
Console.ReadLine();
}
}
}

以下是客户端的配置文件:
<?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>

注意:在客户端代码中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.