Demystifying The Code

REST in WCF – Part XI (Tunneling PUT through POST)

A common scenario you may encounter when designing your RESTful services is supporting clients that only work with GET and POST.  Two such clients on our stack are Silverlight 2 and our ASP.NET AJAX Client Libraries (out of the box).  This brings about a quandary: should I design a HI-REST service interface (support GET, PUT, POST, DELETE) and not support these clients or should I design a LO-REST service interface (only support GET and POST) and support clients such as these.  Fortunately, for you, these are not the only answers to this problem. 

In this post, I will illustrate how to "tunnel" PUT, DELETE or any other HTTP Method over POST.  What I mean by tunneling over POST is that you actually use an HTTP POST, but you pass additional information that allows your call to be routed to the appropriate service operation that supports other Methods.  In this case, we will support passing the "real" method we want to call in an X-HTTP-Method-Override HTTP Header.  This would have been a chore prior to the release of the WCF REST Starter Kit.  Now, however, it is quite simple.  Let’s take a look…

 

Would You Rather See The Screencast?

TunnelingPUTThroughPOST_large_ch9

The PUT Service Operation

[OperationContract]
[WebInvoke(Method="PUT",
    UriTemplate = "wine/{wineID}",
    ResponseFormat = WebMessageFormat.Json,
    RequestFormat=WebMessageFormat.Json)]
public WineData PutWine(string wineID, WineData wineData)
{
    OutgoingWebResponseContext outResponse =
        WebOperationContext.Current.OutgoingResponse;
    IncomingWebRequestContext inRequest =
        WebOperationContext.Current.IncomingRequest; 

    //Cast the wineID to an integer 
    int iWineID = -1;
    bool res = Int32.TryParse(wineID, out iWineID);
    if (!res)
        throw new WebProtocolException(
            HttpStatusCode.BadRequest,
            String.Format("Wine ID ({0}) Not Valid", wineID),
            null); 

    bool isNew;
    WineData wine = CohoDB.PutWine(iWineID, wineData, out isNew); 

    if (isNew)
    {
        //Calculate the Uri of the new item.  Use BindByPosition 
        //method on the UriTemplate, passing the Uri of the 
        //incoming request and the wine id 
        UriTemplate template = new UriTemplate("wine/{wineID}");
        Uri newUri = template.BindByPosition(
            inRequest.UriTemplateMatch.BaseUri,
            wine.WineID.ToString());
        outResponse.SetStatusAsCreated(newUri);
    } 

    return wine;
}

The PUT Service Operation Attributes Described

The OperationContract attribute simply alerts the WCF infrastructure that the operation is part of the service contract.  The more interesting attribute is the WebInvoke.

The WebInvoke attribute indicates to WCF that the operation can be called by the Web programming model, as well as that the HTTP Method should be anything but GET (I say should be in that you can actually set the HTTP verb to GET, but you shouldn’t – use WebGet instead).  You indicate the HTTP Verb by setting the Method property (the Method is POST by default).  In this case, we clearly map this operation to the PUT HTTP Method.  We further set the Request and Response formats to Json.  This is because our Silverlight code will use the DataContractJsonSerializer to deserialize the response from this service (stay tuned).

The PUT Service Operation Implementation Described

The real work done here is by the call to the CohoDB library that encapsulates my database logic.  This library has a static method called PutWine that performs what my colleague Steve Maine referred to as an Upsert (an update if the item exists, otherwise an insert).  How this Upsert is accomplished is not meaningful to this post.  Suffice it to say that PutWine sets an output parameter that indicates if the record was inserted or updated (seen above as the out isNew).

The first 2 lines of code in this operation simply gets references to the outgoing response and the incoming request from the WebOperationContext helper class.   These will be used later in the method.  The next 4 lines of code simply cast the wineID that was passed to the method as a string to an integer.  It is interesting to note that all parameters that are exposed as segments (and not QueryString name-value pairs) must be passed as strings.  As it turns out, we will do simple type conversions for QueryString parameters, but not template segments.  There is no real reason for this and the WCF team is considering implementing simple type checks for both in future releases.  The following 2 lines of code call into our database layer, passing in a variable to store whether the wineData was inserted or updated.

The final if block is actually quite interesting.  It checks to see if the Upsert resulted in an insert.  If so, the code calculates the URI to the new wine.  It then calls a helper method on the OutgoingWebResponseContext to indicate that the item was "Created".  As we will see in a moment, this means that the resulting HTTP status code will be 201 instead of 200 and an additional Location HTTP Header will be returned with the URI to the newly created item.  An interesting facet to this code is that we are using the same UriTemplate object to calculate the new URI as we use in the WebGet to match requests to the service operation.

The final line simply returns the updated/inserted wineData.

 

The svc file (configurationless implementation)

image Figure 1

I pointed out the important part of the svc file in the above screenshot.  As you can see we are using the configurationless implementation.  If you are not familiar with what this is, essentially, as opposed to adding all of the necessary configuration settings under the system.serviceModel (see below).

image  Figure 2

The WebServiceHostFactory is simply a factory that creates an instance of the WebServiceHost in response to incoming requests.  The WebServiceHost will automatically create a properly configured endpoint at the service’s base address for HTTP and HTTPS.  By "properly configured", I mean that it set the binding to the webHttpBinding and it adds a webHttp endpointBehavior and associates it with the endpoint.  Essentially, setting up the configuration above automagically.

However, as you see here (look at the svc file above(2 images up)), we are not setting up the WebServiceHostFactory from System.ServiceModel.Web.  Rather, we are setting up the WebServiceHost2Factory from the WCF REST Starter Kit.  The starter kit contains an assemble named Microsoft.ServiceModel.Web.dll.  This assembly contains this new WebServiceHost2Factory.  We will see a few of the new helpful features of this factory in the rest of this post (i.e. more about this later…).

 

Testing The Service in Fiddler

If you haven’t tried it, Fiddler is a great tool to test RESTful services.  Below is a screenshot where I used the Request Builder in Fiddler to test an HTTP GET to an operation that has the same UriTemplate as the PUT we built earlier in this post. 

image Figure 3

I will only need to do 3 things in order to test my PUT: 1) I will need to change the HTTP Method to PUT, 2) I will need to add the request body and 3) I will need to add an HTTP header to indicate the content type of the request body.  Let’s take a look at what that looks like:

image Figure 4

When I execute this I get an HTTP 200 for Success as the response and the update (in this case it was an update because a wine with ID of 22 exists) occurred.  You may be asking yourself where I got the Json formatted data for the Request Body.  There are 2 easy ways to do this.  When doing an update, it is easiest to write a GET that returns the same type and copy it from the response.  That is exactly what I did.  I copied the response from the GET request you see above in Figure 3.  Here is where I copied it from:

image Figure 5

It turns out, it is quite a bit easier to test an insert thanks to the WCF REST Starter Kit.  Because we set our Factory to the WebServiceHost2Factory (shown earlier in this post), we are now able to access some metadata for our service.  All we need to do is to append a "/help" to the URI to our svc file.  What we get back is some ATOM formatted metadata, including the schema for the Request and an example we can work with.  Take a look at the following:

image Figure 6

image Figure 7

You can then use the sample Json to construct a test payload for your test.

 

The Test Results in Fiddler

Let me show you the result from a test to insert an item.  I simply changed the URI to a wine id that does not exist (73).  I used the payload I got from the metadata, with some updates to make the data more interesting.  Here is what the request and response looked like:

image Figure 8

image Figure 9

 

Calling the Service from Silverlight

Well, as I mentioned, Silverlight only supports HTTP GET and POST.  But clearly, we exposed our Upsert operation over PUT.  I don’t want to change the HTTP verb I’m using to expose this functionality.  I like the universal API of HTTP.  It lets me use the HTTP method to define the intent of my service, while the URI defines the context.  As I mentioned at the beginning of this post, what I want to do is to tunnel a PUT (in this case) over HTTP.  We do this by adding an additional HTTP header called X-HTTP-Method-Override (by convention) with the actual HTTP Method name that I want to call (again, in this case PUT).  Let’s take a look at the Silverlight application.  Below is a screenshot of the UI:

image 

When you press save, the event handler shown below is called:

private void SaveBtn_Click(object sender, RoutedEventArgs e)
{
    //Get the URI 
    Uri uri = composeUri(); 

    //Get the WineData Type 
    WineData wine = GetUpdatedWineData(); 

    //Convert the wine to a json string 
    string json = ConvertObjectToJson(typeof(WineData), wine); 

    WebClient client = new WebClient();
      client.UploadStringCompleted +=
          new UploadStringCompletedEventHandler(client_UploadStringCompleted); 

      client.Headers["Content-Type"] = "application/json";
      client.Headers["X-HTTP-Method-Override"] = "PUT";
      client.UploadStringAsync(uri, "POST", json);
}

The important thing to notice here is that when I call UploadStringAsync, I am actually issuing an HTTP POST.  However, in the line before, you can see that I am setting the X-HTTP-Method-Override to PUT.  (composeUri is a helper method that gets the URI to wine that I am updating, GetUpdatedWineData simply gets a WineData instance with the data updated from the textboxes, while ConvertObjectToJson is a helper method to serialize the object to Json).

 

The RequestInterceptor

That is all well and good.  However, how does the server know to intercept the call, look for the X-HTTP-Method-Override HTTP header and if it finds one, set the method to that value?  The answer is that we need to implement that functionality.  Thank goodness that Microsoft.ServiceModel.Web.dll from the WCF REST Starter Kit provides us with a new type called the RequestInterceptor.  All I need to do is to create a class that implements RequestInterceptor and add that functionality into it.  Take a look below:

public class XHttpMethodOverrideInterceptor : RequestInterceptor
{
    public XHttpMethodOverrideInterceptor(): base(true){ } 

    public override void ProcessRequest(ref RequestContext requestContext)
    {
        if (requestContext == null ||
            requestContext.RequestMessage == null)
        {
            return;
        } 

        Message message = requestContext.RequestMessage;
        HttpRequestMessageProperty reqProp =
            (HttpRequestMessageProperty)message.Properties[HttpRequestMessageProperty.Name]; 

        string overrideVal = reqProp.Headers["X-HTTP-Method-Override"]; 

        if (!string.IsNullOrEmpty(overrideVal))
        {
            reqProp.Method = overrideVal;
        }
    }
}

As you can see, we simply override ProcessRequest, which allows us to participate in the call pipeline.  We start off by getting a reference to the HTTP Request.  Once we have that valid reference, we check to see if the X-HTTP-Method-Override header was passed.  If so, we set the Method of the request to that value.  Simple enough.

 

Add the RequestInterceptor to the Interceptors collection of our Service Host.

Now that we have the RequestInterceptor, we need to make our service host aware of it.  The WCF REST Starter Kit shipped with a new service host called WebServiceHost2.  This is the service host that the WebServiceHost2Factory creates to service requests.  The easiest way to create this type of service host and to add the interceptor into its collection is to create a class that implements ServiceHost, override CreateServiceHost, create an instance of the WebServiceHost2 and add the interceptor in.  See below:

public class CustomServiceHostFactory : ServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(
        Type serviceType,
        Uri[] baseAddresses)
    {
        WebServiceHost2 result = new WebServiceHost2(serviceType,
            true,
            baseAddresses); 

        result.Interceptors.Add(new XHttpMethodOverrideInterceptor()); 

        return result;
    }
}

Running the Silverlight Sample

Take a look at the HTTP traffic in Fiddler from pressing the save:

image

The Request was made over HTTP POST, but our service that is exposed over PUT was invoked.  Very Cool!

Regards

Comments

One Response to “REST in WCF – Part XI (Tunneling PUT through POST)”
  1. DS says:

    Great article! We got it working in the cloud with Azure service and storage.

    One note: after creating CustomServiceHostFactory, make sure you go back and change ServiceHost/@Factory to use the new class. The article doesn’t call that out, though its probably self-explanatory… it took me a while to realize my mistake

Speak Your Mind

Tell us what you're thinking...
and oh, if you want a pic to show with your comment, go get a gravatar!

Demystifying The Code