Greg Beech's Website

Enabling WS-AtomicTransaction in WCF services and clients

Support for all the WS-* standards is built into WCF and doesn't require any complex code to get working, but like all things in the WCF world does require a fair bit of attribution and configuration before it all hangs together. It took me a while to work out exactly which combination of bindings and behaviours and attributes would get transaction flow working between two WCF endpoints, so to save you some time (and me in the future when I forget) here's exactly what you need to do.

Note that this isn't the only way to get transactions working as you can set the services up entirely using code or mostly using configuration, but it's a reasonable starting point that demonstrates the use of both.

For an example service I've chosen a very basic messaging interface, as message delivery between machines is the sort of thing you might want to make transactional. It's attributed in the same way as any other service contract, with the addition of a TransactionFlowAttribute indicating that transactions are Mandatory for the service interface; you could also use Allowed to permit it to be called without a transaction. This attribute must be applied to the contract because without it the transaction flow option defaults to NotAllowed.

[ServiceContract(Namespace = "http://services.company.com/v1/messagingservice/")]
public interface IMessagingService
{
    [OperationContract]
    [TransactionFlow(TransactionFlowOption.Mandatory)]
    void SubmitMessage(
        [MessageParameter(Name = "MessageData")] byte[] messageData,
        [MessageParameter(Name = "Priority")] int priority);
}

You might think that interface attribute is all that's necessary in the code, but in fact you also need to add an OperationBehaviourAttribute to the implementation to ensure that the transaction is flowed. The TransactionScopeRequired property must be true for the transaction to flow successfully, while the TransactionAutoComplete one is just useful because it automatically commits the transaction if no exception is thrown by the method, which is usually what you want.

[ServiceBehavior(
    Namespace = "http://services.company.com/v1/messagingservice/",
    TransactionIsolationLevel = IsolationLevel.ReadCommitted)]
public sealed class MessagingService : IMessagingService
{
    [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
    public void SubmitMessage(byte[] messageData, int priority)
    {
        // ...
    }
}

The final step on the server side is to configure the behaviour and bindings for the service in the application configuration file. Although the behaviour section isn't strictly necessary for transaction flow, I've left it in as you usually want to enable metadata exchange (at least during the development phase) and it makes it much easier to add a service reference from the client later. The key line in the configuration is the custom wsHttpBinding which permits transaction flow.

<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior name="MessagingServiceBehavior">
        <serviceMetadata httpGetEnabled="true" />
      </behavior>
    </serviceBehaviors>
  </behaviors>
  <bindings>
    <wsHttpBinding>
      <binding name="TransactionalWsHttpBinding" transactionFlow="true"/>
    </wsHttpBinding>
  </bindings>
  <services>
    <service name="MessagingService" behaviorConfiguration="MessagingServiceBehavior">
      <endpoint
        address=""
        binding="wsHttpBinding"
        bindingConfiguration="TransactionalWsHttpBinding"
        contract="MessagingServer.IMessagingService"
        bindingNamespace="http://services.company.com/v1/messagingservice/"/>
      <endpoint
        address="mex"
        binding="mexHttpBinding"
        contract="IMetadataExchange" />
    </service>
  </services>
</system.serviceModel>

With the server ready, we can build it and then add a WCF service reference from the client. This will generate the required proxy classes and automatically add a configuration section to the client's application configuration file, but it won't be quite correct to allow transaction flow. In the same way as the server, we need to add a custom wsHttpBinding that matches the one on the server.

<system.serviceModel>
  <bindings>
    <wsHttpBinding>
      <binding name="TransactionalWsHttpBinding" transactionFlow="true"/>
    </wsHttpBinding>
  </bindings>
  <client>
    <endpoint
      name="WSHttpBinding_IMessagingService"
      address="http://localhost/MessagingService.svc"
      binding="wsHttpBinding"
      bindingConfiguration="TransactionalWsHttpBinding"
      contract="MessagingClient.IMessagingService"/>
  </client>
</system.serviceModel>

And with that, we have transaction flow over a WS-AtomicTransaction web service. It does take a few steps to set up, but it's all declarative and quite prescriptive once you work out what combination of attributes and configuration is needed. Anybody who has worked with WSE in the past will appreciate how much easier this is! And the great thing is that it works with TransactionScope so you don't need to change your programming model.

Note that if you add more services then you can re-use the behaviours and bindings for them too; just delete the binding/behaviour elements that the WCF designer puts in the config files and point them at the existing ones. This makes it much easier to manage the services and keep their behaviour consistent.


Posted Jun 19 2008, 12:33 AM by Greg Beech
Filed under: ,

Comments

Greg Beech's Tech Blog wrote Ivory tower protocol wars
on 06-26-2008 1:32 AM

There seem to be a lot of blog entries at the moment arguing over the relative merits of XML, JSON, YAML

Add a Comment

(required)  
(optional)
(required)  
Remember Me?

Enter the numbers above:
Copyright (C) Greg Beech. All rights reserved.
Powered by Community Server (Non-Commercial Edition), by Telligent Systems