[What's new in WCF4] Routing Service - or: "Look ma: Just one service to talk to!"


[Note: all the information was gathered based on a close-to-Beta 1 build of .NET Framework 4.0 and Visual Studio 2010. Details may vary and change]

The Intermediate Routing pattern is a well understood pattern. Some people apply it to have just one service entry point with a universal contract and then route incoming messages to the appropriate business services.
Others use it to determine the path of a message within a system, which means there can be a cascade of routers each responsible for different aspects of routing messages.
WCF was always a bit short in this respect. Sure, there are numerous implementations based on WCF 3.x (just Google...), just recently Dominick and myself implemented something like this for a customer. But there is nothing that came straight in the core platform. This now changes with WCF4.

There is a generic routing service in WCF4 which enables routing for diiferent message exchange patterns (MEPs). The responsible class is System.ServiceModel.Routing.RoutingService.

WCF4 Routing Service

As we can see this service implements four different contracts to fulfill different MEPs and session semantics. If we want to host a routing service we do not need a lot of work, just host it (like this for a self-hosted case):

ServiceHost serviceHost = new ServiceHost(typeof(RoutingService));

try
{
    serviceHost.Open();
    Console.WriteLine("WCF Routing Service running...");
    Console.WriteLine("Press <ENTER> to terminate router.");

    Console.ReadLine();
    serviceHost.Close();
}
catch (CommunicationException)
{
    serviceHost.Abort();
}

        
OK, easy.
Each of the contracts RoutingService implements represent the universal contract pattern (Message in, Message out, or just Message in for the one-way case). WCF4 always uses the async operation pattern, which totally makes sense as we have bound I/O happening in the routing service:

[ServiceContract(SessionMode=SessionMode.Allowed)]
public interface ISimplexDatagramRouter
{
    [OperationContract(AsyncPattern=true, IsOneWay=true, Action="*")]
    IAsyncResult BeginProcessMessage(Message message, AsyncCallback callback, object state);
    void EndProcessMessage(IAsyncResult result);
}

[ServiceContract(SessionMode=SessionMode.Required)]
public interface ISimplexSessionRouter
{
    [OperationContract(AsyncPattern=true, IsOneWay=true, Action="*")]
    IAsyncResult BeginProcessMessage(Message message, AsyncCallback callback, object state);
    void EndProcessMessage(IAsyncResult result);
}

[ServiceContract(SessionMode=SessionMode.Allowed)]
public interface IRequestReplyRouter
{
    [OperationContract(AsyncPattern=true, IsOneWay=false, Action="*", ReplyAction="*"), GenericTransactionFlow(TransactionFlowOption.Allowed)]
    IAsyncResult BeginProcessRequest(Message message, AsyncCallback callback, object state);
    Message EndProcessRequest(IAsyncResult result);
}

[ServiceContract(SessionMode=SessionMode.Required, CallbackContract=typeof(IDuplexRouterCallback))]
public interface IDuplexSessionRouter
{
    [OperationContract(AsyncPattern=true, IsOneWay=true, Action="*"), GenericTransactionFlow(TransactionFlowOption.Allowed)]
    IAsyncResult BeginProcessMessage(Message message, AsyncCallback callback, object state);
    void EndProcessMessage(IAsyncResult result);
}

[ServiceContract(SessionMode=SessionMode.Allowed)]
internal interface IDuplexRouterCallback
{
    [OperationContract(AsyncPattern=true, IsOneWay=true, Action="*"), GenericTransactionFlow(TransactionFlowOption.Allowed)]
    IAsyncResult BeginProcessMessage(Message message, AsyncCallback callback, object state);
    void EndProcessMessage(IAsyncResult result);
}

Pure love :)

So, that was for the WCF infrastructure geek - but how do we actually route? What do we route?
Enter message filters.
The routing service in WCF4 uses message filters in order to route messages. Message filters have always been in WCF since the first version and just now have a prominent role and also got some new filters. This is the list of available message filters:

WCF4 Message Filters

For a first rendezvous with routing we will just use the MatchAllMessageFilter. This filter just returns true and does not do any logic in the Match() method and thus is a pass-through filter.
Message filter will be used inside entries in a RoutingTable. These entries specify which endpoint to call from the RoutingService when the given MessageFilter returns true. The routing table is configured as a service behavior.
Here is a sample config implementing the just explained stuff:
        
<configuration>
  <system.serviceModel>
    <services>
      <service behaviorConfiguration="routingData"
          name="System.ServiceModel.Routing.RoutingService">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:7777/Services/Universal"/>
          </baseAddresses>
        </host>

        <endpoint address=""
                  binding="basicHttpBinding"
                  name="requestReplyEndpoint"
                  contract="System.ServiceModel.Routing.IRequestReplyRouter" />
      </service>
    </services>
    
    <behaviors>
      <serviceBehaviors>        
        <behavior name="routingData">
          <serviceMetadata httpGetEnabled="True"/>          
          <routing routingTableName="mainRoutingTable" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    
    <client>
      <endpoint name="HelloService" address="http://localhost:7777/Services/Hello" binding="basicHttpBinding" contract="*" />
    </client>
    
    <routing>
      <filters>
        <filter name="MatchAllFilter" filterType="MatchAll" />
      </filters>      
      <routingTables>
        <table name="mainRoutingTable">
          <entries>
            <add filterName="MatchAllFilter" endpointName="HelloService" />
          </entries>
        </table>
      </routingTables>
    </routing>

  </system.serviceModel>
</configuration>

This means we expose an IRequestReplyRouter with basicHttpBinding at http://localhost:7777/Services/Universal to the consumers. As a final target service we just have one service (our beloved HelloService) and its endpoint is defined in the <client> section.
Based on the message filter which is referenced through an entry in the routing table the messages being sent to our routing endpoint will be routed through to our HelloService.
Admittedly, this sample is not really real-worldish :) But it should give a first understanding of the RoutingService in WCF4.

To make the whole picture complete, we will let our client/consumer just talk to the routing endpoint - the client does not know anything about the HelloService (besides the contract of course).

<configuration>
  <system.serviceModel>
    <client>
      <endpoint address="http://localhost:7777/Services/Universal"
                binding="basicHttpBinding"
                contract="IHelloService" />
    </client>   
  </system.serviceModel>
</configuration>

Please note that until now we just do plain routing in the sense of message dispatching. We can not do message transformations (yet). This should be covered in a future post, though.

But let's look at two more message filters, the ActionMessageFilter and the XPathMessageFilter, which both come in very handy for typical routing scenarios.
The ActionMessageFilter checks whether a given action value is fulfilled for the incoming message. Likewise, the XPathMessageFilter checks whether a given XPath expression is true for an incoming message. Usually, the XPath filter needs namespace table entries for managing XML namespaces.
In order to use either of these filters to route our client messages to the HelloService we can change the filters and routing table configuration like this:

<routing>
  <filters>
    <!--<filter name="action1" filterType="Action" filterData="http://tempuri.org/IHelloService/SayIt" />-->
    <filter name="xPath1" filterType="XPath" filterData="/s:Envelope/s:Header/wsa:Action = 'http://tempuri.org/IHelloService/SayIt'" />
  </filters>
  <routingTables>
    <table name="mainRoutingTable">
      <entries>
        <!--<add filterName="action1" endpointName="HelloService" />-->
        <add filterName="xPath1" endpointName="HelloService" />
      </entries>
    </table>
  </routingTables>
  <namespaceTable>
    <add prefix="s" namespace="http://schemas.xmlsoap.org/soap/envelope/" />
    <add prefix="wsa" namespace="http://schemas.microsoft.com/ws/2005/05/addressing/none" />
  </namespaceTable>
  <alternateEndpoints>        
  </alternateEndpoints>
</routing>

The two filter configurations are equivalent in their functionailty based on the given filter data values.
Pretty straight.forward, IMO.

One more thing: if the originally specified endpoint in a routing table entry cannot be reached then the list of alternate endpoints is checked (OK, in our sample it is empty, but you get the idea) and the routing service logic tries to call them.

Honestly, I can see a number of customers embracing this new feature (and in this post I was just scratching the surface).

OK; that's it for now for a first introduction to the routing capabilities in WCF4.
Stay tuned for more.