WCF Routing Service and custom filters

WCF 4 now has a built-in way for routing WCF service calls, the System.ServiceModel.Routing.RoutingService, found in System.ServiceModel.Routing.dll. There are several reasons you may want to use routing, for example exposing just a single endpoint even though you have several services or routing messages throught multiple routers within a system.
The routing is done using message filters. Message filters are added to routing tables and specifies the endoints that the RoutingService should route messages to when the message filter condition is matched.

In this post, I’ll show an example of using custom message filters in order to route messages to two WCF services from a Silverlight application using the RoutingService.

1) Create a new Silverlight application and host it in a web site. Lets call it ServiceRouting.
2) Add a new Silverlight-enabled WCF Service to the web site, lets cal it DataService.svc
3) Open DataService.svc and set the Namespace property of the the ServiceContract attribute to some url, for example like this: 

[ServiceContract(Namespace = "http://services.company.com/myapp/processingservice")]

Note that setting the Namespace does not have anything to do with routing directly, but in this exaple I’ll be using Namespace in my custom filter to do the routing

4) Add a reference to System.ServiceModel.Routing.dll and add a routing service to your web site. To do this, simply add a new text file and call it RouterService.svc

Edit RouterService.svc so tha it looks like this.

<%@ ServiceHost Language="C#" Debug="true" Service="System.ServiceModel.Routing.RoutingService,
     System.ServiceModel.Routing, version=4.0.0.0, Culture=neutral,
     PublicKeyToken=31bf3856ad364e35" Factory="ServiceRouting.RoutingServiceHostFactory, ServiceRouting" %>

Note that I have specified a Factory. I’m using .NET 4.0 Beta 2 which requires using a service factory to add the

AspNetCompatibilityRequiremts attribute. The RoutingService in the release version of .NET 4 should contains that attrbute by default: Here is the factory class used to add the AspNetCompatibilityRequirements attribute. Add this class to your web site project.

public class RoutingServiceHostFactory : ServiceHostFactory
    {
        protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
        {
            ServiceHost host = base.CreateServiceHost(serviceType, baseAddresses);
            AspNetCompatibilityRequirementsAttribute attrib = host.Description.Behaviors.Find<AspNetCompatibilityRequirementsAttribute>();
            if (attrib == null)
            {
                attrib = new AspNetCompatibilityRequirementsAttribute();
                host.Description.Behaviors.Add(attrib);
            }
            attrib.RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed;
            return host;
        }
    }

5) Next, we need to configure the routing service so that it can route messages to our Data Service.This is done in web.config.

a) Add the service configuration for the routing service

<services>
  <service behaviorConfiguration="routingServiceBehavior" name="System.ServiceModel.Routing.RoutingService">
    <endpoint address="" binding="basicHttpBinding" name="reqReplyEndpoint"
      contract="System.ServiceModel.Routing.IRequestReplyRouter" />
    <host>
      <baseAddresses>
        <add baseAddress="http://localhost:42001/RouterService.svc" />
      </baseAddresses>
    </host>
  </service>
</services>


b) Add a service behavior for the routing service. The important part here is the routing element which specifies the routing table.

<behaviors>
  <serviceBehaviors>
    <behavior name="routingServiceBehavior">
      <serviceMetadata httpGetEnabled="true" />
      <serviceDebug includeExceptionDetailInFaults="true"/>
      <routing filterTableName="routingTable"/>
    </behavior>
  </serviceBehaviors>
</behaviors>

c) Add the routing table and message filters. These are the settings used by the router service to do the actual routing of the messages.

This is also the section where you specify your custom filter.

<system.serviceModel>
  <routing>
    <filterTables>
      <filterTable name="routingTable">
        <add filterName="dataServiceFilter" endpointName="DataServiceClient"/>
      </filterTable>
    </filterTables>
    <filters>
      <filter name="dataServiceFilter"
              filterType="Custom"
              customType="ServiceRouting.CustomMessageFilter, ServiceRouting"
              filterData="http://services.company.com/myapp/dataservice"/>
    </filters>
  </routing>
</system.serviceModel>

d) Finally, we need to specify the endpoint to call when a message passes the message filter.

<system.serviceModel>
  <client>
    <endpoint name="DataServiceClient"
              address="http://localhost:42001/DataService.svc"
              binding="basicHttpBinding"
              contract="*"/>
  </client>
</system.serviceModel>
Note! I have also changed the DataService configuration so that basicHttpBinding is used instead of the defaultcustomBinding.
<service name="ServiceRouting.DataService">
        <endpoint address="" binding="basicHttpBinding" contract="ServiceRouting.DataService" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>

 

So now that we have our config and services in place, all we need to do is create the custom filter. To create a custom filter, you need to create a class that inherits from MessageFilter and has a constructor taking a string.

public class CustomMessageFilter : MessageFilter
    {
        private string _matchData;
        public CustomMessageFilter(string matchData)
        {
            this._matchData = matchData;
        }        

        public override bool Match(System.ServiceModel.Channels.Message message)
        {
            return message.Headers.Action.IndexOf(this._matchData) > -1;
        }

        public override bool Match(System.ServiceModel.Channels.MessageBuffer buffer)
        {
            throw new NotImplementedException();
        }
    }

The value specified in the filterData attribute is passed to CustomMessageFilter at creation time. The value can then be used to evaluate whether a message should pass the filter.

If there is a match, the DataServiceClient endpoint will be called.

To test the routing service, go to the Silverlight application and add a service reference to the DataService. Then in ServiceReferences.ClientConfig,  change the address to point to the routing service, http://localhost:42001/RouterService.svc.