TransactionMessagePropertyUDPTransport 示例基于 Windows Communication Foundation (WCF)传输扩展性中的 UDP 示例。 它扩展 UDP 传输示例以支持自定义事务流,并演示属性 TransactionMessageProperty 的使用。
UDP 传输示例中的代码更改
为了演示事务流,示例对 ICalculatorContract
的服务协定进行了更改,用于为 CalculatorService.Add()
设定一个事务范围。 示例还向System.Guid
操作的契约中添加了额外的Add
参数。 此参数用于将客户端事务的标识符传递给服务。
class CalculatorService : IDatagramContract, ICalculatorContract
{
[OperationBehavior(TransactionScopeRequired=true)]
public int Add(int x, int y, Guid clientTransactionId)
{
if(Transaction.Current.TransactionInformation.DistributedIdentifier == clientTransactionId)
{
Console.WriteLine("The client transaction has flowed to the service");
}
else
{
Console.WriteLine("The client transaction has NOT flowed to the service");
}
Console.WriteLine(" adding {0} + {1}", x, y);
return (x + y);
}
[...]
}
传输:UDP 示例使用 UDP 数据包在客户端和服务之间传递消息。 传输:自定义传输示例使用相同的机制来传输消息,但当事务流式传输时,它会与编码的消息一起插入 UDP 数据包中。
byte[] txmsgBuffer = TransactionMessageBuffer.WriteTransactionMessageBuffer(txPropToken, messageBuffer);
int bytesSent = this.socket.SendTo(txmsgBuffer, 0, txmsgBuffer.Length, SocketFlags.None, this.remoteEndPoint);
TransactionMessageBuffer.WriteTransactionMessageBuffer
是一种帮助程序方法,其中包含将当前事务的传播令牌与消息实体合并并放入缓冲区的新功能。
对于自定义事务流传输,客户端实现必须知道哪些服务作需要事务流并将此信息传递给 WCF。 还应有一种机制,用于将用户事务传输到传输层。 此示例使用“WCF 消息检查器”来获取此信息。 此处实现的客户端消息检查器(称为“ TransactionFlowInspector
客户端消息检查器”)执行以下任务:
确定对于给定的消息操作,事务是否必须进行流处理(这发生在
IsTxFlowRequiredForThisOperation()
中)。如果需要对事务进行流处理(这在
TransactionFlowProperty
中完成),则使用BeforeSendRequest()
将当前环境事务附加到消息。
public class TransactionFlowInspector : IClientMessageInspector
{
void IClientMessageInspector.AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
}
public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
// obtain the tx propagation token
byte[] propToken = null;
if (Transaction.Current != null && IsTxFlowRequiredForThisOperation(request.Headers.Action))
{
try
{
propToken = TransactionInterop.GetTransmitterPropagationToken(Transaction.Current);
}
catch (TransactionException e)
{
throw new CommunicationException("TransactionInterop.GetTransmitterPropagationToken failed.", e);
}
}
// set the propToken on the message in a TransactionFlowProperty
TransactionFlowProperty.Set(propToken, request);
return null;
}
}
static bool IsTxFlowRequiredForThisOperation(String action)
{
// In general, this should contain logic to identify which operations (actions) require transaction flow.
[...]
}
}
使用自定义行为 TransactionFlowInspector
将 TransactionFlowBehavior
本身传递到框架。
public class TransactionFlowBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
TransactionFlowInspector inspector = new TransactionFlowInspector();
clientRuntime.MessageInspectors.Add(inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
在上述机制的基础上,用户代码会在调用服务操作之前创建一个 TransactionScope
。 消息检查器确保将事务传递到传输,以防需要将事务流动到服务操作。
CalculatorContractClient calculatorClient = new CalculatorContractClient("SampleProfileUdpBinding_ICalculatorContract");
calculatorClient.Endpoint.Behaviors.Add(new TransactionFlowBehavior());
try
{
for (int i = 0; i < 5; ++i)
{
// call the 'Add' service operation under a transaction scope
using (TransactionScope ts = new TransactionScope())
{
[...]
Console.WriteLine(calculatorClient.Add(i, i * 2));
}
}
calculatorClient.Close();
}
catch (TimeoutException)
{
calculatorClient.Abort();
}
catch (CommunicationException)
{
calculatorClient.Abort();
}
catch (Exception)
{
calculatorClient.Abort();
throw;
}
从客户端接收 UDP 数据包时,服务可对数据包进行反序列化,从而提取消息和可能存在的事务。
count = listenSocket.EndReceiveFrom(result, ref dummy);
// read the transaction and message TransactionMessageBuffer.ReadTransactionMessageBuffer(buffer, count, out transaction, out msg);
TransactionMessageBuffer.ReadTransactionMessageBuffer()
是反向执行 TransactionMessageBuffer.WriteTransactionMessageBuffer()
序列化过程的帮助程序方法。
如果事务已流入,它会通过 TransactionMessageProperty
追加到消息。
message = MessageEncoderFactory.Encoder.ReadMessage(msg, bufferManager);
if (transaction != null)
{
TransactionMessageProperty.Set(transaction, message);
}
这可以确保调度程序在调度时选择事务,并在调用由消息处理的服务操作时使用该事务。
设置、生成和运行示例
要生成解决方案,请按照生成 Windows Communication Foundation 示例中的说明进行操作。
当前示例应类似于 传输:UDP 示例。 若要运行它,请使用 UdpTestService.exe启动服务。 如果运行的是 Windows Vista,则必须使用提升的权限启动服务。 为此,请在文件资源管理器中右键单击 UdpTestService.exe,然后单击“ 以管理员身份运行”。
这会生成以下输出。
Testing Udp From Code. Service is started from code... Press <ENTER> to terminate the service and start service from config...
此时,可以通过运行 UdpTestClient.exe来启动客户端。 客户端生成的输出如下所示。
0 3 6 9 12 Press <ENTER> to complete test.
服务输出如下所示。
Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! The client transaction has flowed to the service adding 0 + 0 The client transaction has flowed to the service adding 1 + 2 The client transaction has flowed to the service adding 2 + 4 The client transaction has flowed to the service adding 3 + 6 The client transaction has flowed to the service adding 4 + 8
如果服务应用程序可以将客户端在
The client transaction has flowed to the service
操作的clientTransactionId
参数中发送的事务标识符与服务事务的标识符匹配,则会显示消息CalculatorService.Add()
。 只有当客户端事务流向服务时,才会获得匹配。若要针对使用配置发布的终结点运行客户端应用程序,请在服务应用程序窗口中按 Enter,然后再次运行测试客户端。 应该会在服务中看到以下输出。
Testing Udp From Config. Service is started from config... Press <ENTER> to terminate the service and exit...
现在在服务上运行客户端会产生与以前类似的输出。
若要使用 Svcutil.exe重新生成客户端代码和配置,请启动服务应用程序,然后从示例的根目录运行以下 Svcutil.exe 命令。
svcutil http://localhost:8000/udpsample/ /reference:UdpTransport\bin\UdpTransport.dll /svcutilConfig:svcutil.exe.config
请注意,Svcutil.exe 不会为
sampleProfileUdpBinding
生成绑定扩展的配置,必须手动添加它。<configuration> <system.serviceModel> … <extensions> <!-- This was added manually because svcutil.exe does not add this extension to the file --> <bindingExtensions> <add name="sampleProfileUdpBinding" type="Microsoft.ServiceModel.Samples.SampleProfileUdpBindingCollectionElement, UdpTransport" /> </bindingExtensions> </extensions> </system.serviceModel> </configuration>