Published: 12 Oct 2009
By: Ben Scheirman
Download Sample Code

In the last article, we covered ASP.NET MVC 2.0 Preview 1. Since then, the team shipped Preview 2 (did I mention these go fast?) and that will be the topic for this article. If you haven't read Part I yet, I suggest you read it now.

Contents [hide]

Getting the Bits

Again, if you want to follow along in this article, you'll need to download ASP.NET MVC Preview 2. This will require you to remove MVC 2.0 Preview 1. Be careful you don't uninstall MVC 1.0, as you'll probably want to keep that around for your regular work. MVC 2.0 Preview 2 installs alongside MVC 1.0 and won't affect it in any way.

Download ASP.NET MVC 2.0 Preview 2 here: http://go.microsoft.com/fwlink/?LinkID=154414.

Upgrading from Preview 1?

It is possible to upgrade projects from Preview 1 to Preview 2. I'll point you to the Release Notes for that, as it does require some manual hacking on project files and the like. Check out the Release Notes here: http://go.microsoft.com/fwlink/?LinkID=157066.

In this article we'll start from scratch with new projects built with Preview 2.

Changes in Preview 2

Preview 2 brings quite a few new additions, some of which directly relate to what I wrote about in the last article. In this article, we're going to focus on implementing RESTful routes & controllers using two new features in Preview 2: HttpMethodOverride and the new HTTP method attributes.

REST-style Routes & Controllers

ASP.NET MVC 2.0 Preview 2 ships with two core pieces of functionality that can enable RESTful scenarios for your controllers and actions. If you aren't familiar with REST, don't worry. REST is a complex topic, but the implementations are quite simple and straightforward.

REST is an architectural style. REST stands for REpresentation State Transfer. Clear as mud, right? It actually makes sense when you read more about it. The core of a RESTful architecture is the resource. The resource is a noun, which represents some concept in your system. One of the first things you'll notice is that REST leverages lesser-known HTTP verbs like PUT and DELETE to describe operations that one would perform on a resource. The resource is addressable via URI, however the HTTP verb of the request is used to define what happens to the resource. An example is needed to fully explain this.

In our example last time we talked about a Contact. The contact would be your resource. The various actions on this could be:

  • Get a Contact
  • Create a new Contact
  • Update an existing Contact
  • Delete a Contact

If you recall, these were all accessible via separate action methods, all via HTTP GET or POST.

With RESTful routing, you'd end up with something like this.

Table 1: RESTful routing leverages the HTTP Verb to define the action called

URL

HTTP VERB

ACTION

/contacts

GET

Index()

/contact/51

GET

Show(int id)

/contact/51/edit

GET

Edit(int id)

/contact/51

PUT

Update(Contact contact)

/contact/new

GET

New()

/contact

POST

Create(Contact contact)

/contact/51

DELETE

Delete(int id)

REST is much more than URL trickery. In the above table we're only talking about HTML pages. HTML is 1 representation of a resource. You might also want the same data formulated as an XML or JSON document. Here is an example of that:

Table 2: Requesting different representations of a resource

URL

HTTP VERB

XML / JSON

/contact/51.xml

GET

<contact>…</contact>

/contact/51.json

GET

{"contact": … }

In this article we'll cover how to achieve the routing rules above leveraging new features in Preview 2. The latter examples (requesting different formats) are covered in my book ASP.NET MVC in Action. For more information on what REST is (including a lot of history) check out the Wikipedia page. Also very informative is a post by Ryan Tomayko entitled How I Explained REST to My Wife.

ASP.NET MVC 2.0 Preview 2 introduces a couple new features that enable this functionality with minimal effort. First, is the HttpMethodOverride. Since today's browsers don't even speak PUT or DELETE, we have to use a little hack. By calling <%= Html.HttpMethodOverride(…) %> inside of a form, you are asking the framework to generate a hidden HTML input tag that coerces the framework to treat the form POST as some other verb. Preview 2 also provides new attributes that we can apply to our action methods, to restrict which HTTP verbs we allow to call a given action.

You already know about [AcceptVerbs(HttpVerbs.POST)]. The new attributes are of the same idea, however much cleaner: [HttpGet], [HttpPost], [HttpPut], [HttpDelete]. Apply these attributes to an action method to restrain the action to only those verbs.

Let's say we want to have RESTful controllers & actions by convention. The first piece of code we'll have to visit is our routing rules.

RESTful Routing Rules

It's clear that the default {controller}/{action}/{id} rules will not work for our RESTful controllers. We'll have to come up with a new set of rules that understand the different HTTP verbs we plan to use.

Before we create our routes, let's examine how they might look. As you saw in the table above, there are a few URLs that are identical, yet they differ in behavior based on which HTTP verb is used. We can use a route constraint for this.

The astute reader will already be thinking of using the built-in HttpMethodConstraint. The only problem is that it doesn't handle outbound routing at all. It only constrains requests coming in (inbound routing) and the route does not match if the request's HttpMethod property is contained in the allowed values of the constraint. Since this class is defined in System.Web.Routing and thus part of the .NET Framework, don't expect this to change any time soon.

What is outbound routing?

Outbound routing refers to the framework generating URLs based on a set of route values. It is generally not a good idea to hardcode URLs in your views. Any time you use Html.ActionLink, Html.BeginForm, or Url.Action the framework is using the routing rules to generate a URL for you. This is called outbound routing and it leverages the exact same set of routes that are used during incoming requests to map a URL to a controller & action pair.

Let's create a quick custom constraint that will fix this minor issue. We'll have the default behavior if the URL is an inbound request, but if it is an outbound request (RouteDirection.UrlGeneration) then we'll look for a custom route value called "httpMethod." If this is found, it will be used to match the values.

Listing 1: A custom class that adds outbound routing behavior to HttpMethodConstraint

Armed with this constraint, we can create routes for common URLs that will work with inbound and outbound routing. Since all of the routes we'll need to satisfy the table above are fairly common, and really the only varying piece is the controller and the prefix for the URLs, we can package them all up inside one reusable method.

Here is our new route definition:

The MapResource<T> method is an extension method that packages up all of these routes:

Listing 2: A reusable extension method for registering the RESTful routes

Each of these routes is defined with a custom name (though you'll never refer to these routes by name), and a URL that's based off of the alias you pass in. I did this because at times you want to have a different name for you URLs than what you name your controller. You might also go as far as to provide a singular and plural version of the resource alias, in order to get even cleaner looking routes.

We need to be sure to place this route call above the default route. Leaving the default route in there ensures that other non-Restful controllers can still be addressed.

Listing 3: Global.asax – The default routing rule is left, as not all of our controllers will use the RESTful routes

So now we have all these routes pointing to specific action methods on our controllers. Let's extend this convention into a base controller class to make it easier on the controller developer. This way we can simply inherit and override.

Listing 4: A base class enforcing our RESTful conventions

Since we've decorated each method with the appropriate HTTP verb attribute in the base class, we don't need to do this on the derived classes. Also, the abstract methods guarantee that derived classes will use the names that our routes are pointing to from before. Conventions can save you time, but you do have to know the convention to follow it. This base class allows us to be a bit lazy, as there's less to remember.

We can now create our ContactController, derive from this base class, and implement all of the methods required.

Listing 5: An implementation of the RestfulController for Contacts

Our actions are concise and only consist of the abstract methods from the base class. For the curious, the guts of the InMemoryContactRepository are available below.

Listing 6: A quick, in memory repository for testing our our ContactController

Let's focus on the ContactController's Index() action for now. It renders a view with a list of contacts. Here is the Index.aspx view.

Listing 7: Index.aspx (listing contacts)

You can see there is nothing spectacular here. This gives us a quick list of contacts. The new contact action & view are similar to what you've seen before. Where it does get interesting is the edit view.

When you want to edit an existing resource, you'll need to utilize the HTTP verb PUT for our form. Likewise when you delete, you have to use the HTTP verb DELETE. We'll use both in our edit view.

Listing 8: Edit.aspx (editing or deleting an existing record)

I've bolded the interesting parts of this form. First is the additional route value httpMethod that we're specifying on the BeginForm call. This is required for our outbound routing rule to generate the correct URL. If we omitted this, the framework would generate the wrong URL, and the action wouldn't work! If we look at the generated form, we can see that the URL is correct:

Wait a minute! Didn't we specify the method to be PUT? Why does that form tag say POST? This is due to the simple fact that no modern browser supports PUT or DELETE in HTML forms. This might change in the future, but for now we're stuck with GET & POST. That doesn't stop us from faking it, which brings me to the second interesting part of this form: the new HttpMethodOverride call.

Why aren't PUT and DELETE supported in today's browsers?

In HTML 4 and XHTML 1, only GET and POST are supported via HTML forms. As we'll see later, you can perform a PUT and DELETE in JavaScript. Also we'll see with HTML 5 that browsers will start supporting PUT and DELETE (and perhaps more) HTTP verbs.

This new html helper generates a hidden field that looks like this:

The ASP.NET MVC Framework will look for this when it verifies that an action can be called. Remember that [HttpPut] attribute we placed on the base class Update method? This attribute is verifying that the HTTP method of the request (slightly faked by the presence of this hidden form value) is equal to PUT.

Our Edit view & action are now working properly. It did require a slightly awkward tweak of using the custom route constraint and adding a route value to our BeginForm call. These things could likely be automatically provided with a custom helper, which would be trivial to write. You might even automatically include the HtmlOverride call as well, but just be aware that you might be producing invalid XHTML if you don't wrap your form controls in some block level element like a <div> or <fieldset>. (Of course I didn't do this in my example either!)

The delete button has a similar concept:

The New view & action doesn't need these tweaks, as it is a simple form POST, and will work just the same as previous versions of the MVC framework.

PUT and DELETE from Javascript

I've mentioned that modern browsers don't support PUT and DELETE. This is true for HTML forms under HTML 4 and XHTML 1, which can only utilize GET and POST (POST being the most common). It is possible, however, to submit requests to the server using PUT & DELETE in an ajax request.

For example, the following jQuery code will submit a PUT request to the server correctly:

This can be used to access your PUT only actions for ajax requests. In Figure 3, you can see that a link has submitted an ajax request and the method is correctly marked as PUT.

Figure 1: Performing an ajax PUT request from jQuery

Performing an ajax PUT request from jQuery

If you were to try this same URL as a GET or POST, you'd get a 404, since the action on the controller is marked as [HttpPost] and therefore invisible to the other methods.

Possibilities

One interesting possibility for these new RESTful controllers, actions, and routes would be to nest them. Rails has this concept, and it allows you to very easily address purely hierarchical content. For example, if you have a domain of artists, albums, and songs it makes perfect sense to nest them. Take a look at the following URL:

If we pick this URL apart it is very clear that the data is nested. It's also easy to guess what type of result we were to get if we omitted a portion off the end of the URL. For example, if we remove the song name, we'll probably get a list of songs in that album. If we omit the album name we'll probably get a list of albums for that artist.

Nested resources are absolutely possible, but it would probably take similar customization and conventions similar to what we accomplished in this article.

What's next?

In this article we only covered one new feature of ASP.NET MVC 2.0. This article covered Preview 2. We learned how to apply the new HttpMethodOverride helper and associated HTTP method attributes to achieve a convention-based RESTful set of routes for managing a resource in your ASP.NET MVC application. REST is a large topic, but if you follow the trends of some of the other web frameworks out there, REST is clearly becoming more and more popular.

Look forward for more articles on ASP.NET MVC 2.0 as we further explore new features of the framework.

<<  Previous Article Continue reading and see our next or previous articles Next Article >>

About Ben Scheirman

Ben Scheirman is a software developer specializing in .NET. He has worked extensively on the web on various platforms and languages. Ben is a Microsoft MVP, Microsoft ASP Insider, and Certified ScrumMaster. When not programming, Ben enjoys speaking, blogging, spending time with his wife and five won...

This author has published 4 articles on DotNetSlackers. View other articles or the complete profile here.

Other articles in this category


Code First Approach using Entity Framework 4.1, Inversion of Control, Unity Framework, Repository and Unit of Work Patterns, and MVC3 Razor View
A detailed introduction about the code first approach using Entity Framework 4.1, Inversion of Contr...
jQuery Mobile ListView
In this article, we're going to look at what JQuery Mobile uses to represent lists, and how capable ...
Exception Handling and .Net (A practical approach)
Error Handling has always been crucial for an application in a number of ways. It may affect the exe...
JQuery Mobile Widgets Overview
An overview of widgets in jQuery Mobile.
Book Review: SignalR: Real-time Application Development
A book review of SignalR by Simone.
Top
 
 
 

Discussion


Subject Author Date
placeholder HTTPMethodOverride not working Glenn Goodrich 1/3/2011 3:18 PM
RE: HTTPMethodOverride not working Glenn Goodrich 1/3/2011 3:35 PM

Please login to rate or to leave a comment.