Transport: Custom Transactions over UDP Sample
This sample is based on the Transport: UDP sample in the Windows Communication Foundation (WCF) Transport Extensibility Samples. It extends the UDP Transport sample to support custom transaction flow and demonstrates the use of the TransactionMessageProperty property.
Note
This sample requires that .NET Framework version 3.5 is installed to build and run. Visual Studio 2008 is required to open the project and solution files.
Code Changes in the UDP Transport Sample
To demonstrate transaction flow, the sample changes the service contract for ICalculatorContract
to require a transaction scope for CalculatorService.Add()
. The sample also adds an extra System.Guid parameter to the contract of the Add
operation. This parameter is used to pass the identifier of the client transaction to the service.
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);
}
[...]
}
The Transport: UDP sample uses UDP packets to pass messages between a client and a service. The Transport: Custom Transactions over UDP Sample uses the same mechanism to transport messages, but when a transaction is flowed, it is inserted into the UDP packet along with the encoded message.
byte[] txmsgBuffer = TransactionMessageBuffer.WriteTransactionMessageBuffer(txPropToken, messageBuffer);
int bytesSent = this.socket.SendTo(txmsgBuffer, 0, txmsgBuffer.Length, SocketFlags.None, this.remoteEndPoint);
TransactionMessageBuffer.WriteTransactionMessageBuffer
is a helper method that contains new functionality to merge the propagation token for the current transaction with the message entity and place it into a buffer.
For custom transaction flow transport, the client implementation must know what service operations require transaction flow and to pass this information to WCF. There should also be a mechanism for transmitting the user transaction to the transport layer. This sample uses "WCF message inspectors" to obtain this information. The client message inspector implemented here, which is called TransactionFlowInspector
, performs the following tasks:
Determines whether a transaction must be flowed for a given message action (this takes place in
IsTxFlowRequiredForThisOperation()
).Attaches the current ambient transaction to the message using
TransactionFlowProperty
, if a transaction is required to be flowed (this is done inBeforeSendRequest()
).
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.
[...]
}
}
The TransactionFlowInspector
itself is passed to the framework using a custom behavior: the 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)
{
}
}
With the preceding mechanism in place, the user code creates a TransactionScope
before calling the service operation. The message inspector ensures that the transaction is passed to the transport in case it is required to be flowed to the service operation.
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;
}
Upon receiving a UDP packet from the client, the service deserializes it to extract the message and possibly a transaction.
count = listenSocket.EndReceiveFrom(result, ref dummy);
// read the transaction and message TransactionMessageBuffer.ReadTransactionMessageBuffer(buffer, count, out transaction, out msg);
TransactionMessageBuffer.ReadTransactionMessageBuffer()
is the helper method that reverses the serialization process performed by TransactionMessageBuffer.WriteTransactionMessageBuffer()
.
If a transaction was flowed in, it is appended to the message in the TransactionMessageProperty
.
message = MessageEncoderFactory.Encoder.ReadMessage(msg, bufferManager);
if (transaction != null)
{
TransactionMessageProperty.Set(transaction, message);
}
This ensures that the dispatcher picks up the transaction at dispatch time and uses it when calling the service operation addressed by the message.
To set up, build, and run the sample
To build the solution, follow the instructions in Building the Windows Communication Foundation Samples.
The current sample should be run similarly to the Transport: UDP sample. To run it, start the service with UdpTestService.exe. If you are running Windows Vista, you must start the service with elevated privileges. To do so, right-click UdpTestService.exe in Windows Explorer and click Run as administrator.
This produces the following output.
Testing Udp From Code. Service is started from code... Press <ENTER> to terminate the service and start service from config...
At this time, you can start the client by running UdpTestClient.exe. The output produced by the client is as follows.
0 3 6 9 12 Press <ENTER> to complete test.
The service output is as follows.
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 service application displays the message
The client transaction has flowed to the service
if it can match the transaction identifier sent by the client, in theclientTransactionId
parameter of theCalculatorService.Add()
operation, to the identifier of the service transaction. A match is obtained only if the client transaction has flowed to the service.To run the client application against endpoints published using configuration, press ENTER on the service application window and then run the test client again. You should see the following output on the service.
Testing Udp From Config. Service is started from config... Press <ENTER> to terminate the service and exit...
Running the client against the service now produces similar output as before.
To regenerate the client code and configuration using Svcutil.exe, start the service application and then run the following Svcutil.exe command from the root directory of the sample.
svcutil https://localhost:8000/udpsample/ /reference:UdpTranport\bin\UdpTransport.dll /svcutilConfig:svcutil.exe.config
Note that Svcutil.exe does not generate the binding extension configuration for the
sampleProfileUdpBinding
; you must add it manually.<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>
See Also
Other Resources
© 2007 Microsoft Corporation. All rights reserved.