Demystifying The Code

REST in WCF – Part VII (HI-REST – Implementing Insert and Update)

Introduction

In parts I – VI, I illustrated exposing fetch functionality in a LO-REST, AJAX-Friendly manner, as well as in a HI-REST manner.  I further illustrated how to consume both via an AJAX client.  In this post I am going to discuss and illustrate how to implement insert and update functionality in a HI-REST manner.  If you remember back to the point I made in Part I of this series, I suggested that there are many definitions of REST and put forth the idea of a REST continuum.  This is a healthy manner to discuss REST, in that it allows for many interpretations.  If I have learned anything over my years of coding is that there are always many ways to solve a business problem with code.  Not only are there many ways, but there are many correct ways.

I re-introduced the REST continuum in the last paragraph because I am now about to go down a certain path in my RESTful implementation.  If you ardently disagree with my implementation and thought process, I trust that you will at least respect that it does fall on the continuum (as always, I would love your feedback, as well).

 

Modeling Insert and Update the HTTP way

Most folks I talk to view the HTTP POST as in insert and PUT as an update.  However, I have met more than a few that see things the other way around.  I have even seen some folks pull out the verb PATCH.  Part of the challenge with this seemingly unanswerable debate is that HTTP was developed to deal with documents and when we discuss these verbs we are usually thinking about data (rows and columns).  At least that is my thought.

Well, if I am going to provide an implementation in this post, I am going to have to make a decision on how I want to handle the HTTP verbs.  To start, I am going to need a bit of help from the HTTP specs.  Here are some excerpts from POST and PUT:

PUT- "The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI. If a new resource is created, the origin server MUST inform the user agent via the 201 (Created) response. If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent to indicate successful completion of the request." (1)

POST – "The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line. POST is designed to allow a uniform method to cover the following functions:
       – Annotation of existing resources;
      – Posting a message to a bulletin board, newsgroup, mailing list,
        or similar group of articles;
      – Providing a block of data, such as the result of submitting a
        form, to a data-handling process;
      – Extending a database through an append operation."
(1)

The way I read this, in the event that a resource does not exist, a PUT will perform an insert.  In the event that a resource already exists at the designated URI, the resource is modified (or updated).  With regards to a POST, it is designed for operations that I would describe as append.  Again, by simply writing this passage, I have opened myself up to endless debate.  There are those that will state that if the resource exists, what really is happening is a delete and an insert and that is != to an Update.  In my (probably naive) opinion, as long as you have a logical basis for your API design and you are consistent, you will be able to defend its efficacy.  So, given this explanation, I have decided that I will use PUT for both insert and update in my implementation.

The Service Contract

[OperationContract]
[WebInvoke(Method = "PUT",
           UriTemplate = "product/{productName}",
           RequestFormat = WebMessageFormat.Json)]
void PutProduct(string productName, ProductData productData); 

This service contract is quite similar to the service contract for our HI-REST GET.  There are, however, a few differences:

WebInvoke

As opposed to decorating our service operation with a WebGetAttribute, we decorate it with a WebInvokeAttribute.  The default HTTP VERB associated with this attribute is POST.  As I pointed out, I want to use a PUT.  The WebInvokeAttribute is the attribute we use for every action other than GET.

Method="PUT"

The WebInvokeAttribute takes a Method parameter that allows you to explicitly set the HTTP verb.  As I stated before, if you do not set anything, the default is POST.  We want to use a PUT, so it is set accordingly.

No ResponseFormat

I have a void return type, so there is no need to set the response format.

RequestFormat=WebMessageFormat.Json

We are sending a payload with the request.  Here I am setting the format to JSON or JavaScript Object Notation.  If you look back to Part VI of this series, you will see that the restInvoke AJAX function I wrote assumes JSON serialization, so it is in line with this decision.

 

The Implementation

public void PutProduct(string productName, ProductData productData)
{
    WebOperationContext ctx = WebOperationContext.Current;
    System.Net.HttpStatusCode status = System.Net.HttpStatusCode.OK;

    try
    {
        using (CatalogDataContext catalogCtx = new CatalogDataContext())
        {
            Product product = catalogCtx.Products.SingleOrDefault(
                prod => prod.ProductName == productName);

            if (product == null)
            {
                product = new Product();
                catalogCtx.Products.InsertOnSubmit(product);
                status = System.Net.HttpStatusCode.Created;
            }

            product.ProductDescription = productData.Description;
            product.Price = productData.Price;
            product.ProductImage = productData.ProductImage;
            product.ProductName = productData.ProductName;

            catalogCtx.SubmitChanges();
            ctx.OutgoingResponse.StatusCode = status;

            return;
        }
    }
    catch
    {
        ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.BadRequest;
        return;
    }
}

 

As you can see, we again get a reference to the WebOperationContext.  This helper class allows us to get a reference to the outgoing response.  Using this object, we can set the response code appropriately.  I defaulted the status code to 200 or OK.  Just like in the GET implementation, I use the SingleOrDefault extension method to return the single product.  Remember that this extension method returns the single match for the condition or the default value which is null for value types.  If more than one match exists, an exception is thrown.  If product is null (doesn’t exist), I assume that this in an insert operation.  If it does, I assume an update.

In the case of the insert, I simply new up a Product.  I then call InsertOnSubmit on the Products collection.  Lastly, I set the status code to 201 or Created.  If you are wondering why not 200, take another look at the excerpt from the HTTP specs for PUT above.  Regardless of insert or update, I set the properties on the product instance, call SubmitChanges on the data context and set the status code.  In the event of an unknown error, I simply set the status code to 400 or Bad Request.

 

Consuming our Service with an AJAX Client

You may remember that I have a simple management page that I want to add the fetch/insert/update/delete functionality to.  We have already added the fetch, so, after I am done here, we will have everything by delete implemented.  As a reminder the page looks like this:

image_thumb43

When the ‘Save’ button is pressed, we will create an instance of an object that is the JavaScript representation of our .NET type (ProductData).  This object will use the values from the html text boxes to set the properties.  We will also create the url that will match the UriTemplate, complete with the productName parameter.  If this is an existing product, we source the productName in the URI from the list box where the product was selected.  If it is a ‘new’ product, we use the product name from the text box to form the URI.  Lastly, we call restInvoke the same as in our GET scenario.  However, notice here that we are passing a ‘PUT’ as the HTTP verb parameter.  Here is the code in the event hander:

function saveProduct_clicked() {
    var selectedProductName = $get('productList').value;
    var productName = $get('productName').value;
    var description = $get('description').value;
    var price = $get('price').value;
    var image = $get('image').value;

    var productData = {
        ProductName: productName,
        Description: description,
        Price: price,
        ProductImage: image
    };

    if (selectedProductName == "new")
        selectedProductName = productName;

    var proxy = new Sys.Net.WebServiceProxy();
    var url = "../RESTCatalogService.svc/product/" + selectedProductName;

    proxy.restInvoke(url, "PUT", productData, "saveProduct_clicked", ProductSavedEventHandler, ErrorEventHandler);
}

function ProductSavedEventHandler(result) {
    clearSelect();

    loadProducts();

    alert('Product Saved');
}

Please see the Starter Solution for the implementations of clearSelect, loadProducts, etc.  That just about does it for this example.   In the next post, I will implement the DELETE functionality.

Regards …

(1) Hypertext Transport Protocol, http://www.w3.org/Protocols/rfc2616/rfc2616.html

Comments

2 Responses to “REST in WCF – Part VII (HI-REST – Implementing Insert and Update)”
  1. rajeshwari says:

    i want sample code solution for rest-in-wcf-part-vii-hi-rest-implementing-insert-and-update/

  2. admin says:

    Sorry, at one point, both my VM at my ISP and their backup server died. I lost everything. I actually rebuilt this site from old caches of the posts in livewriter or on google cached pages. The code, however was not recoverable.

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