Creating a RESTful Web Service Using ASP.Net MVC Part 8 – Deleting a Resource

November 7, 2008 01:45 by admin

Our web service has changed over this series of posts until now it supports a more complex resource… but its still a read only web service. In this post we’ll add support for the DELETE HTTP verb. This should be the simplest of the remaining verbs to implement… but it does highlight some more issues to be dealt with.

Basic Deletion

The first step to supporting the DELETE verb was to create a suitable method on the controller. Here is a quick reminder of the operations we are looking to support:

Operation HTTP Action
1 List all products GET /products
2 Create a new product POST /products
3 View any product GET /products/{id}
4 Update any product PUT /products/{id}
5 Delete any product DELETE /products/{id}
6 List all products in a category GET /products/{category}
7 Create a new product in a category POST /products/{category}
8 View product in a category GET /products/{category}/{id}
9 Update product in a category PUT /products/{category}/{id}
10 Delete product in a category DELETE /products/{category}/{id}

DELETE is only required to operate against individual product items, not against lists. So after a little refactoring of the code already used to support GET for an item, I arrived at this method:

[AcceptVerbs(HttpVerbs.Delete)]

[ActionName("Item")]

public ActionResult ItemDelete(string categoryString, string idString)

{

    ActionResult actionResult;

    Product product = FindProductItem(categoryString, idString, out actionResult);

 

    if ((actionResult == null) && (product != null))

    {

        if (!Products.Delete(product.Id))

        {

            actionResult = SelectActionResult("ErrorDetail", … );

        }

    }

     return actionResult;

}

It shares code with ItemGet to identify the Product being targeted. If it is found, it will delete the Product and not return a representation. If deletion fails, a 404 Not Found is returned.

A RESTful web service’s support for the DELETE HTTP verb should be an “idempotent,” meaning that it can be safely called more than once. If a client fails to get a response from a DELETE request, it should be safe for that client to make the request a second time. With the above code, if the failure had been due to a server issue, a second call should perform the actual deletion. If the server had actually deleted the resource but the response had been lost, a second request will receive a 404 Not Found, but no harm will be done. Therefore, our web service is correctly supporting DELETE.

Overloaded POST

The above code works well, provided the client is capable of making a DELETE request. However, not all clients will be able to make a DELETE request, most browsers only support GET and POST. DELETE requests may also fail to reach the web service due to infrastructure restrictions, for instance a firewall may be configured to only permit GET and POST requests through.

One common solution to this issue is to “Overload” POST requests. A de-facto standard seems to be growing out of Ruby on Rails whereby a POST request is made with an additional query string parameter in the URI (or possibly in the entity body). The additional parameter is often _method=delete.

I decided to support DELETE through the query string. This led to this method being written to handle any POST actions:

[AcceptVerbs(HttpVerbs.Post)]

[ActionName("Item")]

public ActionResult ItemPost(string categoryString, string idString, string _method)

{

    ActionResult actionResult = null;

    bool handled = false;

 

    // If this is an overloaded POST, pass control to another Action

    if (!string.IsNullOrEmpty(_method))

    {

        switch (_method.ToLower())

        {

            case "delete":

                actionResult = ItemDelete(categoryString, idString);

                handled = true;

                break;

        }

    }

 

    // NB we cannot just check for (actionResult == null) as some verbs

    // don't return an ActionResult (e.g. DELETE)

    if (!handled)

    {

        actionResult = SelectActionResult("ErrorDetail", … );

    }

 

    return actionResult;

}

This method checks to see if a _method value has been passed in and if necessary delegates to the ItemDelete method. With this method in place using POST to the following URI causes a deletion:

    /Products/Sport/FFD862F4-B04C-40c4-8507-57F0E63F98DB?_method=DELETE

This approach to handling the _method variable is not ideal… I can envisage this code for testing for the presence of the variable being copied, pasted and slightly altered throughout the web service. Ideally we would refactor this code somehow. I can think of three approaches:

  1. Implement my own version of MvcHandler that wraps the HttpContext in a custom version of HttpContextWrapper. My version of HttpContextWrapper would use a custom version of HttpRequestBase that would allow us to set the HttMethod property;
  2. Have some code in the BaseController that accepted delegates to deal with a true POST or an overloaded POST / DELETE or POST / PUT. This function would then call the appropriate delegate dependent on the _method parameter.
  3. Implement a variant of AcceptVerbsAttribute that also took into account the _method parameter;

I think the first option would be a horrible hack that would need to keep up to date with any changes in the framework. Option 2 could work but would still mean every POST action in the web service would contain similar code. Option 3 is probably the neatest solution, just a shame that the AcceptVerbsAttribute is sealed! I will not change my code yet, but I’ll think about it a bit longer… my current favourite is to create an equivalent of AcceptVerbsAttribute. Any other suggestions welcomed!

The code (written in C#) for this version of the web service is available RESTfulMVCWebService06.zip (45.17 kb). If I don’t return to the refactoring issue above in my next post, I’ll probably move onto POST and PUT verbs.

An Aside

As the Products resource has to ASPX views, ItemGet.aspx and ListGet.aspx, I decided to extract the common Help.ascx HTML into a User Control they could both include. So instead of a large amount of explanatory HTML in both files, they both have the following:

<% Html.RenderPartial("Help"); %>

kick it on DotNetKicks.com


Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading