Search This Blog

Friday, February 12, 2016

RESTful Web Services with Windows Communication Foundation

What is REST?

I don’t really want to go too deep into REpresentational State Transfer, as there are lots of excellent resourcesalready written that will do a far better job of explaining it, however a brief recap of the basics can’t hurt.
REST is an architectural pattern for accessing resources via HTTP. A resource can represent any type of item a client application may wish to access. A client can be a user or could be a machine calling a web service to get some data.
If you were creating a traditional RPC style service to return the details of a product you may well have created something that is called like this:
In this example we have a verb (get) and a noun (Product). A RESTful version of this service would be something along the lines of:
In this example there is no explicit verb usage. Instead it is the HTTP verb GET that is used. In a RESTful web service it is the HTTP verbs (GET, POST, PUT, DELETE) that are used to interact with the resource.
GET – Used to return a resource in. It may be a simple product, as in the example above or it could be a collection of products.
POST – Used to create a new resource.
PUT – Used to update a resource. Sometimes PUT is also used to create a new resource is the resource is not present to update.
DELETE – Used to delete a resource.
The operations of a RESTful web service should be idempotent where appropriate to build resilience into the operations. It also makes sense that some verbs are not applicable to certain resources, depending on whether the resource is a collection of resources or a single resource (as we will see later).
REST is not a new idea – it was introduced by Roy Fielding in his 2000 doctoral dissataion, but has only recently been gaining serious traction against more widespread web service styles such as SOAP based XML. REST’s lightweight style and potential for smaller payloads have seen its popularity increase and major vendors have sought to add support for REST services into their products, including Microsoft.
A RESTful web service prescribes to the principles of REST, as well as specifying the MIME type of the data payload and the operations supported. REST does not specify the type of payload to be used and could really be any data format. In practice JSON has emerged as the defacto standard for RESTful services due to it’s lightweight nature.

Creating a WCF REST service

The WCF service will provide product information for some generic widgets.
The RESTful api for the service will support the following operations:
/Products
GET – Return a collection of products
POST – Add a new product to the product collection
PUT – Not supported, as we would want to edit the contents of the collection of products, not the collection itself.
DELETE – Not supported, as it would not really make sense to delete an entire collection of products
/Products/{productId} e.g. /products/12345
GET – Get the product represented by the product Id
POST – Not supported, as we cannot add anything at an individual product level
PUT – Update the product details.
DELETE – Delete the product
I am going to move failry quickly when it comes to creating the service. If you are not familiar with WCF (or need a refresher) I recommend that you have a look at the earlier series on creating WCF services.
I am going to make use of some of the new .Net 4.0 WCF features and not use a .svc file for the service. When creating RESTful services this has the advantage of making the URL look a little more ‘RESTful’, so instead ofhttp://website/products.svc/1we can have http://website/products/1.
Lets create a new WCF application solution. I have thrown away the .svc file that the wizard has created for us. Rename the Interface to something sensible. I have called mine ProductService. Remember that this internal naming of the service contract and its operations in not exposed to the service once it is up and running. It makes sense to name the contract and operation in a sensible way for the project and them map it to a more REST friendly URL. We will see how to do this later in the post.
A RESTful service is in many respects similar to any other. It still follows the ABC of WCF. The service has an address at which the operations are invoked, it has a binding that specifies the way the service will communicate, and it also has a Contract that define the operations. The Contract for the ProductService looks like this:
[ServiceContract]
public interface IProductService
{
    [OperationContract]
    [WebInvoke(Method = "GET", UriTemplate = "", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
    List<Product> GetProducts();

    [OperationContract]
    [WebInvoke(Method = "POST", UriTemplate = "", RequestFormat = WebMessageFormat.Json)]
    void CreateProduct(Product product);

    [OperationContract]
    [WebInvoke(Method = "GET", UriTemplate = "/{ProductId}", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
    Product GetProductById(string productId);

    [OperationContract]
    [WebInvoke(Method = "PUT", UriTemplate = "/{ProductId}", RequestFormat = WebMessageFormat.Json)]
    Product UpdateProductDetails(string productId);

    [OperationContract]
    [WebInvoke(Method = "DELETE", UriTemplate = "/{ProductId}", RequestFormat = WebMessageFormat.Json)]
    Product DeleteProduct(string productId);
}
It is the WebInvoke attribute that identifies that a service operation is a available via REST. The method property defines the HTTP verb that is to be used. The UriTemplate property allows a templated URL to be specified for the operation. Parameters for the URL are specified between {}. The RequestFormat and ResponseFormat attributes specify the datatype for the exchange. We are using JSON.
As we are not using .svc files we must supply some routing so WCF knows how to route the requests to the service. Open up the Global.asax (or create one if the project doesn’t already have one) and locate the Application_Start method and add in a new route.
protected void Application_Start(object sender, EventArgs e)
{
    RouteTable.Routes.Add(new ServiceRoute("Products"new WebServiceHostFactory(), typeof(ProductService))); 
}
This route tells WCF that when a request comes in for Products it is routed to the ProductService. As an aside if you were using custom service host factory, for example if you were using an IOC container to to inject dependencies, you could specify it here instead of the standard WebServiceHostFactory.
It is the URI template and the routing that allows us to provide the RESTful web service addresses. Although we have a service operation called GetProductById that takes a Product Id, the resource is accessed via a URL in the form of /Products/12345.
Now the implementation is out of the way we are ready to go. The actual implementation of the service operations is not very interesting as it has no bearing in the REST service, as it is just the same as any implementation of a WCF service contract interface.
“But wait” I hear you all shout, “you haven’t configured the service in the web.Config. You are correct. In this example I am going to use another feature of .Net 4.0 WCF: Default Configuration. This allows WCF to automatically create  default configuration for Bindings, including endpoint, behaviors and protocol mappings without the need to get your hands dirty in the Web.Config. Of course in a production environment it is likely that you would want more control over your configuration and this can be added to the config as required.

Lets try the service

In order to test the service we need to call the service. A simple test harness is required. I created a simple html page using  jQuery to call out to the service:
<!DOCTYPE html>
<html>
<head>
<title>WCF RESTful Web Service Example</title>
</head>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
    <script type="text/javascript">

        var GetProductRequest = function () {
            $.ajax({
                type: "GET",
                url: "http://localhost:52845/products/",
                contentType: "application/json",
                dataType: "text",
                success: function (response) {
                    $("#getProductsResponse").text(response);
                    alert(response);
                },
                error: function (error) {
                    console.log("ERROR:", error);
                },
                complete: function () {
                }
            });
        };

        GetProductRequest();

    </script>

    <body>
        <h1>
        WCF RESTful Web Service Example
        </h1>
        <h2>
        GET Response
        </h2>
        <pre id="getProductsResponse"></pre>
    </body>
</html>
This simply uses jQuery’s ajax functionality to make a call to the service and output the response on the page. Lets run it. As another aside, if you are using ASP.NET Development Server when building and testing your services (and the chances are you probably are) then you will only be able to test GET and PUT methods, as others are not allowed and will return a 405 error. If you want to be able to test the PUT and DELETE methods I would recommend using IIS Express as your development web server.
I am using Chrome 21 and there appears to be a problem. If we look at the developer tools in Chrome (press F12) we can see that a request was made with the OPTIONS method, and it returned a status of 405 Method not allowed.  Also there is a message about the Origin nor being allowed by something called Access-Control-Allow-Origin. This is due to the Same origin policy.

The Same origin policy

The Same origin policy is a browser security concept that  prevent a client-side Web application running from one site (origin) from obtaining data retrieved from another site. This pertains mainly to cross site script requests. For them to be considered to be from the same origin they must have the same domain name, application layer protocol and port number.
Althouth the same origin policy is an important security concept with a lot of valid uses it does not help out legitimate situation. We need to be able to access the RESTful web service from a different origin.

Cross-origin resource sharing

Cross-origin resource sharing is a way of working with the restrictions that the same origin policy applies. In brief, the browser can use a Prefligh request (this is the OPTIONS method request we have already seen) to find out whether a resource is willing to accept a cross origin request from a given origin. The requests and responses are validated to stop man in the middle type attacks. If everything is okay the original request can be performed. Of course there is more to it than that, but that is all we need to know for now. More infomation is available in the
W3C spec for CORS.
As usual with browsers, CORS support is not universal, nor is it implemented in consistently across all browsers. In some browsers it is all but ignored (I am looking at you IE 8/9) We will need to ensure that the CORS support we add to the WCF service works in all situation. ‘When can I use Cross-Origin Resource Sharing‘  is a handy tool to see if your browser suppports CORS.

Dealing with an OPTION Preflight request in WCF

There are a couple of ways of dealing with CORS in WCF.
The first is some custom WCF Behaviours that can be applied to a service operation via an attribute. This approach has the advantage that it is possible to explicitly target which operations you want to be able to accept the cross domain requests, however it has quite an involved implementation.  A good overview can be found here.
I am going to focus on the second method, which is to intercept the incoming requests and check for the presence of the OPTIONS method and responding appropriately. I will show the code and them we can talk about how it works. Back to the Global.asax and the Application_BeginRequest method.
protected void Application_BeginRequest(object sender, EventArgs e)
{
    HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin""*");
    if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
    {
        HttpContext.Current.Response.AddHeader("Cache-Control""no-cache");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods""GET, POST, PUT, DELETE");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers""Content-Type, Accept");
        HttpContext.Current.Response.AddHeader("Access-Control-Max-Age""1728000");
        HttpContext.Current.Response.End();
    }
}
As the method name suggests, this is called when a request is received. The first action is to add a Access-Control-Allow-Origin header to the response. This is used to specify URLs that can access the resource. If you are feeling generous and want any site (or origin) to access the resource you can use the * wildcard. The reason this header is outside of the check for the OPTIONS method is because sometimes CORS request do not send OPTIONS Prefligh requests for ‘simple’ requests (GET and POST). In this case they just send their origin and expect a response to be told if they can access the resource. This is dependent upon Browser CORS implementation. Remember when we first tried to call the service and we got an error saying that the Origin was not allowed by Access-control-Allow-Origin? That was because the response header did not contain this header with the correct entry.
The Cache-Control tell the requester not to cache the resource. The Access-Control-Allow-Methods specifies which http methods are allowed when accessing the resource. Access-Control-Allow-Headers specifies which headers can be used to make a request. Access-Control-Max-Age specifies how long the results of the Preflight response can be cached in the Preflight result cache.
There is a lot more information available about the CORS implementation. If you are looking for more information two of the best places are the W3C CORS Specification and the Mozilla Developer Network.
Lets give it another try.
Success. That’s all there is to it. Hopefully you will see that creating RESTful web services with WCF is easy. If you have any questions please feel free to leave a comment.

No comments:

Post a Comment