Roll Your Own REST-ful WCF Router

Download the code for this post here.

Recently I’ve been tasked with building a WCF routing service and faced the choice of whether to go with the built-in router that ships with WCF 4.0, or to build one from scratch.  The built-in router is great for a lot of different scenarios – it provides content-based routing, multicasting, protocol bridging, and failover-based routing. However, as the MSDN documentation for the WCF Router states, “The Routing Service does not currently support routing of WCF REST services.”  The reason is fairly simple: the WCF Routing Service performs routing of messages in a way that is independent of the underlying transport, whereas a REST-based architecture is deeply rooted in the HTTP protocol and relies on the URI for delivery semantics.  In fact, using the BasicHttpBinding with AspNetCompatibility enabled on the built-in WCF router results in a somewhat cryptic error: “Shouldn’t allocate SessionChannels if session-less and impersonating.”

Nevertheless, there are times when it might make sense to build a router that can talk to clients who don’t know anything about Soap, for example, an AJAX web application.  You can also achieve a more compact data representation with plain old XML (POX) or Javascript Object Notation (JSON), which could result in greater throughput.  While the ASP.NET MVC or the new ASP.NET Web API might seems like attractive options, WCF is the way to go if you need to accommodate both Soap-based and Rest-ful clients.  WCF offers a unified programming model with the neutral Message type, which makes it easier to avoid serialization of the message body and the cost that could carry.

Here is the universal service contract for a routing service that can accept requests that are formatted as SOAP, POX or JSON.

[ServiceContract(Namespace = "urn:example:routing")]
public interface IRoutingService
{
    [WebInvoke(UriTemplate = "")]
    [OperationContract(AsyncPattern = true, Action = "*", ReplyAction = "*")]
    IAsyncResult BeginProcessRequest(Message requestMessage, AsyncCallback asyncCallback, object asyncState);

    Message EndProcessRequest(IAsyncResult asyncResult);
}

What makes this suitable for routing is that the Action and ReplyAction parameters of the OperationContract are set to “*” – allowing it to accept any request regardless of the Action.  The contract also uses a request-response message exchange pattern, which is suitable for HTTP clients that generally follow this pattern when communicating with services.

Another thing you’ll notice is the AsyncPattern layout, with the Begin and End methods tied together by the IAsyncResult call object.  This is a very important requirement for performance and scalability.  WCF executes asynchronous contracts using the IO Completion Port Thread Pool, which economizes on server resources by exchanging a 100 byte IO request packet for a 1 MB thread stack.  This makes sense only if you initiate async IO in the Begin method.  An Async IO operation can be things like Socket.Begin[Send|Receive], NetworkStream.Begin[Read|Write], FileStream.Begin[Read|Write] (if created asynchronously), SqlCommand.BeginExecute[Reader|NonQuery|XmlReader], or invoking a WCF service asynchronously, which is precisely what a router is designed to do.

Here is the implementation of the IRoutingService interface.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall,
    AddressFilterMode = AddressFilterMode.Any, ValidateMustUnderstand = false)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class RoutingService : IRoutingService, IDisposable
{
    private IRoutingService _client;

    public IAsyncResult BeginProcessRequest(Message requestMessage, AsyncCallback asyncCallback, object asyncState)
    {
        // Select soap or rest client endpoint
        string endpoint = "service-basic";
        if (requestMessage.Version == MessageVersion.None)
            endpoint = "service-web";

        // Create channel factory
        var factory = new ChannelFactory<IRoutingService>(endpoint);

        // Set message address
        requestMessage.Headers.To = new Uri(factory.Endpoint.Address);

        // Create client channel
        _client = factory.CreateChannel();

        // Begin request
        return _client.BeginProcessRequest(requestMessage, asyncCallback, asyncState);
    }

    public Message EndProcessRequest(IAsyncResult asyncResult)
    {
        return _client.EndProcessRequest(asyncResult);
    }

    public void Dispose()
    {
        if (_client != null)
        {
            var channel = (IClientChannel)_client;
            if (channel.State != CommunicationState.Closed)
            {
                try
                    channel.Close();
                catch
                    channel.Abort();
            }
        }
    }
}

First, notice that the InstanceContextMode is set to PerCall, so that an instance of the RoutingService is created upon each request.  This allows the service to be entirely stateless and function easily in a web farm environment without maintaining client state between method calls.  Another thing to notice is that the client proxy (IRoutingService) is declared as a member variable and shared between the Begin and End methods.

When I first wrote the service implementation, I noticed something strange happen when the service was invoked by a non-Soap client.  The call to _client.BeginProcessRequest was coming right back into the service, instead of invoking the remote service, even though the factory’s endpoint address pointed to the remote service.  What I didn’t realize at the time is that the Http transport channel will use the “To” message header when manual addressing is set to true, which is the case with the WebHttpBinding.  The “To” message header is naturally pointing to the routing service, so that’s where the message is directed.  To correct this behavior, all you need to do is manually set the “To” message header to match the factory endpoint address.

Probably the most important task of a router is to, well, route messages.  But you need to decide how to instruct the router to accomplish this task.  The trick is to do it in a way that avoids having to look at the contents of the message.  Messages in WCF consist of two parts: headers and a body. Headers are always deserialized and buffered, whereas the body comes in as a stream.  If you can avoid creating a message buffer and deserializing the message stream, the router will perform more efficiently.

For soap-based messages, the natural place for routing instructions is the header.  Here is part of a method that reads routing instructions from the incoming message header.

public Dictionary<string, string> GetRoutingHeaders(Message requestMessage)
{
    // Set routing namespace
    var routingHeaders = new Dictionary<string, string>();

    // Get soap routing headers
    if (requestMessage.Version != MessageVersion.None)
    {
        foreach (var header in requestMessage.Headers)
        {
            if (header.Namespace.ToLower() == _routingNamespace)
            {
                int headerIndex = requestMessage.Headers.FindHeader
                    (header.Name, _routingNamespace);
                if (headerIndex != -1)
                {
                    var headerValue = requestMessage.Headers.GetHeader<string>
                        (header.Name, _routingNamespace);
                    requestMessage.Headers.RemoveAt(headerIndex);
                    if (!string.IsNullOrWhiteSpace(headerValue))
                        routingHeaders.Add(header.Name, headerValue);
                }
            }
        }
    }

For non-Soap clients using HTTP, you have basically two choices: custom HTTP headers, or you can incorporate routing instructions into the URI.  Personally, I like the second approach better, because it makes use of the URI in a more REST-like fashion.  Here is some code that demonstrates both these approaches.  It first looks at the HTTP headers for routing instructions, but if there aren’t any, it then gets them from query parameters in the URI.

WebHeaderCollection httpHeaders = WebOperationContext.Current.IncomingRequest.Headers;
foreach (string headerName in httpHeaders)
{
    if (headerName.ToLower().StartsWith(routingNamespace))
    {
        string name = headerName.Substring(routingNamespace.Length + 1);
        string value = httpHeaders.Get(headerName);
        routingHeaders.Add(name, value);
    }
}
if (routingHeaders.Count == 0)
{
    var queryParams = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters;
    foreach (string paramKey in queryParams.AllKeys)
    {
        string name = paramKey.Substring(_routingNamespace.Length + 1);
        string value = queryParams[paramKey];
        routingHeaders.Add(name, value);
    }
}

Armed with routing metadata, you can look up the destination’s address in a routing table of some kind.  This method selects GreetingService2 if the routing instructions specify the “western” region.

public string GetServiceAddress(Dictionary<string, string> routingHeaders,
    string defaultServiceAddress)
{
    // Select service address based on region
    string serviceAddress = defaultServiceAddress;
    var region = (from rh in routingHeaders
                    where rh.Key.ToLower() == "region"
                    select rh.Value).FirstOrDefault();
    if (region != null)
    {
        if (region.ToLower() == "western")
            serviceAddress = defaultServiceAddress
                .Replace("GreetingService1", "GreetingService2");
    }
    return serviceAddress;
}

If the router isn’t going to read the message body or alter it in any way, then clients will need to send messages that can be understood by the eventual recipient of the message.  So if both Soap and non-Soap messages will be sent to the router, the downstream services will need to expose both soap and rest endpoints.  Furthermore, if clients are going to want to transmit messages as Json, services will need to know how to understand and respond in kind.  For example, here is the app.config file of the GreetingService.  The webHttp endpoint behavior allows for a Json-formatted response if the Accept or ContentType header is set to “application/json”.

<system.serviceModel>
  <services>
    <service name="RoutingPrototype.Services.GreetingService1">
      <endpoint address="Soap"
                binding="basicHttpBinding"
                contract="RoutingPrototype.Interfaces.IGreetingService"
                name="service-basic"/>
      <endpoint address="Rest"
                binding="webHttpBinding"
                behaviorConfiguration="web"
                contract="RoutingPrototype.Interfaces.IGreetingService"
                name="service-web"/>
      <host>
        <baseAddresses>
          <add baseAddress="http://localhost:8000/GreetingService1" />
        </baseAddresses>
      </host>
    </service>
    <behaviors>
      <endpointBehaviors>
        <behavior name="web">
          <webHttp helpEnabled="true"
                    automaticFormatSelectionEnabled="true"
                    faultExceptionEnabled="true"/>
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>

The client-side code for sending Soap-based messages looks like this:

private static string SendSoapMessage(string name, string addressType, string region, bool useFiddler)
{
    var factory = new ChannelFactory<IGreetingService>(addressType);
    string address = factory.Endpoint.Address.ToString()
        .Replace("localhost", ConfigurationManager.AppSettings["MachineName"]);
    if (useFiddler) factory.Endpoint.Address = new EndpointAddress(address);
            
    IGreetingService client = factory.CreateChannel();
    using ((IDisposable)client)
    {
        using (var contextScope = new OperationContextScope((IContextChannel)client))
        {
            if (region != null)
            {
                MessageHeader regionHeader = MessageHeader
                    .CreateHeader("region", _routingNamespace, region);
                OperationContext.Current.OutgoingMessageHeaders.Add(regionHeader);
            }
            return client.Hello(name);
        }
    }
}

The client-side code for sending Rest-ful messages looks like this:

private static string SendRestMessage(string name, string addressType, string region, bool useFiddler)
{
    string address = ConfigurationManager.AppSettings[addressType];
    if (useFiddler) address = address.Replace("localhost", ConfigurationManager.AppSettings["MachineName"]);
    var client = new WebClient {BaseAddress = address};

    // Set format
    string format = GetFormat();
    if (format == null) return null;
    string requestString;
    if (format == "xml")
    {
        requestString = SerializationHelper.SerializeXml(name);
        client.Headers.Add(HttpRequestHeader.ContentType, "application/xml");
    }
    else if (format == "json")
    {
        requestString = SerializationHelper.SerializeJson(name);
        client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
    }

    // Set region header
    string addressParameters = string.Empty;
    if (region != null)
    {
        bool? useHeaders = UseHttpHeaders();
        if (useHeaders == null) return null;
        if ((bool)useHeaders)
        {
            string regionHeader = string.Format("{0}:{1}", _routingNamespace, "region");
            regionHeader = regionHeader.Replace(":", "-");
            client.Headers.Add(regionHeader, region);
        }
        else
        {
            addressParameters = string.Format
                ("?{0}:region={1}", _routingNamespace, region);
        }
    }

    // Send message
    string responseString = client.UploadString(addressParameters, requestString);

    // Deserialize response
    string response = null;
    if (format == "xml")
        response = SerializationHelper.DeserializeXml<string>(responseString);
    else if (format == "json")
        response = SerializationHelper.DeserializeJson<string>(responseString);
    return response;
}

If you look at the message received by the either the router or the downstream service, you won’t see a hint of Json, even when the message sent by the client is clearly Json.  (SerializationHelper is a class I wrote to serialize Xml and Json using WCF’s data contract serializer.)  The reason is that the translation from and to Json is performed by the webHttp endpoint behavior. If you want to see what is actually sent across the wire, you’ll need to employ an HTTP sniffer such as Fiddler.  However, configuring it for use with .NET clients can be a joy (sarcasm intended).  The easiest approach I found was to substitute “localhost” in client endpoint addresses with the actual machine name, which you can store as a setting in app.config.

fiddler-json

Here you can see an HTTP POST message transmitted with a custom routing header, urn-example-routing-region, with a value of “western”.  Both the request and response are formatted as a simple Json string.

WCF will give you all the tools you need to write a scalable, high-performance router with a minimal amount of code.  Making it play nice with Rest, however, requires some effort, as well as familiarity with how WCF deals with Rest-based messages under the covers.  Here are some resources I found helpful in getting my head around WCF addressing and message-handling and the mechanics of building a WCF routing service:

WCF Addressing In Depth (MSDN Magazine June 2007)
WCF Messaging Fundamentals (MSDN Magazine April 2007)
Building a WCF Router, Part 1 (MSDN Magazine April 2008)
Building a WCF Router, Part 2 (MSDN Magazine June 2008)

You can download the code for this post here. Enjoy.

About Tony Sneed

Married with three children.
This entry was posted in Technical and tagged . Bookmark the permalink.

18 Responses to Roll Your Own REST-ful WCF Router

  1. Yann says:

    Very nice article, covering all aspects of custom routing! Way to go, Tony!

  2. Pingback: Distributed Weekly 152 — Scott Banwart's Blog

  3. Can you comment on the threading / multi-processing a little more. I wrote a router based on your code which I plan to use to “swizzle” the URL before passing it on since the client puts params in the header and server apps expects them in the URL. I thought I was done when it workes as expected when I send a request using a browser as my client but when I hooked up the real client and it made multiple simultaneous requests, my responses are getting “mixed up”.

    Is it possible the the WebOperationContext from one request is being used in the a second request or something similar with a different state variable?

  4. Pingback: Build a RESTful Router using WCF (Browse Azure Table Storage with Excel) « Kloud Blog

  5. Peter Reid says:

    Great Blog,
    I took the original Router idea and raised it with some additional behaviours

    http://blog.kloud.com.au/2012/11/09/build-a-restful-router-using-wcf-browse-azure-table-storage-with-excel/

  6. Pingback: Twiddler – Playing Catchup « IT Batman!

  7. Tony – within the REST client, webclient.UploadString uses POST to create the message. Using a GET request, the router fails with the message Protocol Violation: Cannot send a content-body with this verb-type. Any help would be appreciated via: http://stackoverflow.com/questions/15238733/protocol-violation-verb-does-not-allow-content-type

    My research is saying that GET should not have a request body, but the SB relay may be introducing something… again, very much appreciated and communicated with my work on Twidder via https://github.com/ITBatman/Twiddler

    Thanks!

    • Tony Sneed says:

      Yes, you’ve made a good observation that only HTTP POST is supported by the REST contract on the Router. Although I haven’t tried it, I would suggest adding additional operations for other HTTP verbs, such as GET, PUT, DELETE, etc. The attribute WebGet is for GET, and the other verbs are specified by passing a parameter to WebInvoke.

      • richard reukema says:

        So why do GET,s get routed in? Should they not drop at the endpoint?

      • Tony Sneed says:

        I’m afraid I don’t understand your question. Can you please expand or clarify?

      • IT Batman! says:

        This is getting interesting, I’m using the router to receive requests from a ServiceBus endpoint that is relaying in the requests. When I receive it, I’m thinking it’s a SOAP message that has wrapped the HTTP request. As a result WebInvoke/WebGet does not come into play. If that assumption is correct, I’m not sure why I’m getting a Protocol exception, as the message does indeed get processed by the BeginProcessRequest (whether or not the HTTP request is a GET or a POST). The error is generated after I create a new channel to forward the request to a WebAPI interface that is hosted locally. I’m leveraging the public endpoint of SB as a means to route requests to my local workstation. This is the main purpose for Twiddler, and why I posted the same question on stackoverflow (which you also have commented on, just in case you didn’t connect the two).

  8. Narasimha says:

    Hi Tony, Thanks for the article, I am interested in adding an extra parameter(created a datacontract) to the service, it works but when comes to REST it fails mentioning “Internal Server Error”. Could you please help.

    • Tony Sneed says:

      I suspect you have an error in your data contract. See if you can hit the service without the router, but first enable metadata on the service and try to show it in a browser. Usually the data contract errors show up there.

      • Narasimha says:

        Hi Tony,

        Thanks for the response. No error is showing upon the browser as well. I have changed the interface as below and made necessary changes according to it in the GreetingClient to accomodate the same. Options 2 & 4 aren’t working – service-rest & router-rest. Could you suggest what might be wrong. Thanks in Advance.
        ==================
        [ServiceContract(Namespace = "urn:example:services")]
        public interface IGreetingService
        {
        // Web operation uri maps to ‘Hello’ method name
        [WebInvoke(BodyStyle=WebMessageBodyStyle.Wrapped)]
        [OperationContract]
        string Hello(FullName fullname);
        }

        [DataContractFormat]
        public class FullName
        {
        public string Name;
        public string LastName;
        }

      • Tony Sneed says:

        What specific error are you receiving?

      • Tony Sneed says:

        Why are you using the wrapped body style?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s