Bring Rails to ASP.NET MVC With Restful Routing

April 26, 2013 • Damien White

Restful Routing - Url

When I’m working with ASP.NET MVC, I find myself wanting to do things “the Rails way.” There are a lot of excellent conceptions/conventions in Rails where I think ASP.NET MVC could really learn something.

One thing I really dislike about ASP.NET MVC is the way it handles routing. I find the default route in ASP.NET MVC ({controller}/{action}/{id}) to be very ugly, antiquated, and super greedy. When I’m writing an application I like the URLs to be “hackable.” This isn’t possible with the default ASP.NET MVC routes. Take, for example, something like a book website. Using the default route, you would have something like /books/edit/1. Now what happens if we remove that one (the id)? Boom 404! What about the default route for a single book? You would have /books/details/1. You know what happens if we remove that id. The URLs just do not flow nicely.

Routing Approaches

Why do I prefer Rails routing? Their approach makes so much sense to me, not to mention that you get nice restful routes. The approach that Rails takes is called resource-based routing.

ASIDE: What is a resource you ask? You can think of a resource as a model, and with a model, you would typically want CRUD operations. That’s where the “routing” comes into play. Instead of using a greedy route, it sets up specific routes to handle the CRUD operations for a given resource. We’ll be taking a closer look at this as we continue, but if you’re eager to learn more, head over to the Rails Guides on Resource Routing.

With this resource approach, the URLs follow RESTful conventions and embrace HTTP. Isn’t that one of the key concepts behind the MVC pattern, embracing HTTP?

Let me show you an example of the difference in approaches.

We’ll base the comparison on the basic seven actions that are required to perform CRUD operations (assuming you have a New and Edit action to handle an HTML form, otherwise you would only need five).

The ASP.NET MVC Approach

Action HTTP Method URL
Index GET /books
Create GET /books/create
Create POST /books/create
Details GET /books/details/1
Edit GET /books/edit/1
Edit POST /books/edit/1
Delete POST /books/delete/1

Notice how only HTTP GET and POST are being used? HTTP has more verbs than just those two. Why not use some of these to clean up our URLs and be RESTful?>

ASIDE: I’m sure that ASP.NET MVC does this because HTML forms (HTML v4 and XHTML v1) only support GET and POST. That said, there is an easy way to use other HTTP verbs within an HTML form. The typical approach is to use POST, but use a hidden form field to designate the intended verb (e.g. PUT). The server would read this field and route the user to the correct action.

RESTful routing is exactly what Rails does using the resource routing approach.

The Rails Approach

Here’s how Rails’ RESTful routing would look:

Action HTTP Method URL
Index GET /books
New GET /books/new
Create POST /books
Show GET /books/1
Edit GET /books/1/edit
Update PUT /books/1
Destroy DELETE /books/1

Notice how those routes are cleaner and “hackable?” Instead of explicitly listing the action in the URL, we are making use of the HTTP verbs for the Update and Delete actions (the Delete action is called destroy in Rails). We also aren’t confusing ourselves by essentially “posting back” to the Create or Edit actions like the ASP.NET defaults. Instead we are working against a “resource” where “books” is the collection. If you want to act against the collection, such as to create a new record, you would POST against the collection (which is the second ASP.NET MVC Create action-see how confusing that is?). We’ll use an action named New to access the form needed to create a new record, which is a standard GET.

Now, if you’re working against a specific model, you would request that against the collection as represented by /books/1 (where the 1 is the record’s ID). That takes care of the next four actions. If you want to view the resource, you’d perform a GET against /books/1 (this would call a Show action using Rail’s naming conventions, not Details). To get the edit form, you’d GET the Edit action for the resource /books/1/edit. To work against the resource, you would either PUT (which may change to PATCH in the future) to do an Update or a DELETE to, well, perform a delete. All resource based actions work against the base /books/1 URL.

Do you see how that method works better than the ASP.NET MVC approach. I completely love REST and RESTful interfaces. Why not use those conventions in web development?

Hello Restful Routing

We obviously don’t need to stick with the default routing scheme in ASP.NET MVC. We could manually set up those seven different routes (times X number of resources), but wouldn’t it be nice to simplify things by thinking about “resources?” Using our books example in Rails, we’d simply add one line to our routes file:

resources :books

One line to specify those seven different routes. Nice. There isn’t any magic here, resources is simply a method that does the heavy lifting, which we could replicate within ASP.NET. That is the basic premise behind the fantastic Restful Routing project.

Of course routing in Rails isn’t limited to one method. That’s where Restful Routing does the heavy lifting. The project is a quick and easy way to get all of the power behind routing in Rails within your ASP.NET MVC project.

Start by installing the NuGet package:

PM> Install-Package RestfulRouting

Once that completes, you’ll have two files added to your project, Routes.cs (which would mimic routes.rb within Rails), and an ApplicationController.cs, which can act as a base class for your controllers (the same pattern is used in Rails). The ApplicationController isn’t required, but with it, you’ll be able to do some cool things with your routes. I’ll be discussing that in a future post.

Routes.cs

Prior to MVC4, ASP.NET MVC routes were set up in the Global.asax file. I never liked putting my routes there. Coming from working in Rails, your routes exist in a single file, routes.rb. That’s exactly what the Routes.cs replicates. Ditch the declaration in Global.asax, and set up everything in there. If you go this route, you can just remove related routing code from the Global.asax and start working. I like this approach since it mirrors Rails’ approach.

Keeping the MVC4 Route Structure

With MVC4, the default routing changed a bit. With the addition of the App_Start directory, the routes have moved to a RouteConfig.cs file, cleaning up the Global.asax to just a single line to register the routes:

public class Global: System. Web. HttpApplication
{
  protected void Application_Start(object sender, EventArgs e)
  {
    ...
    RouteConfig. RegisterRoutes(RouteTable. Routes);
    ...
  }
}

In MVC4 you can tweak the setup for Restful Routing a bit if you wish. You can move the Routes class into the MVC4 RouteConfig.cs file (and delete the Routes.cs file) giving you a structure that looks like this:

public class RouteConfig
{
  public static void RegisterRoutes(RouteCollection routes)
  {
    ...
    routes.MapRoutes<Routes>();
  }

  public class Routes : RouteSet
  {
      public override void Map(IMapper map)
      {
          map.DebugRoute("routedebug");
          // TODO: Add routes here
      }
  }
}

This, of course, isn’t necessary. You could just stick with the Routes.cs approach. This is just an option for those who want to keep the MVC4 route declaration structure.

Resources

The majority of things in an application would fall under a “resources route.” This refers to a collection of things, be it books, posts, orders, customers, etc. We saw what that looked like earlier (resources :books), but how does Restful Routing define resources? Using the books example, inside the Map method, you would simply add the following:

map.Resources<BooksController>();

It looks similar to Rails, except in this case, instead of passing a symbol (:books is a Ruby symbol), we use the controller’s class name.

The seven routes created by that one line (via Restful Routing) mirror those that are provided by Rails. The routes, complete with the appropriate HTTP Verb are mapped to actions in your controller. The action naming conventions are the same as Rails as well, though in a .NET class, they would be upper-camel cased (e.g. Index, Show, New, Create, Edit, Update, and Destroy).

The documentation for Restful Routing is fantastic. There are all sort of things you can do with a resources route, including, but not limited to: tweaking the names of the actions, tweaking which of the seven routes get created, adding nested routes, etc. I would recommend that you check out the Resources section of the documentation to learn about all of the different options.

Resource

Not everything in an application is presented as a collection. Think about things like a user’s profile. It is a single thing (albeit there is one for each user) that is presented as such (e.g. http://mywebapp/profile).

Things represented as a singular resource don’t require the id to be part of the URL. Instead, we know what we’re working with without the id. Because of this, the routes look different from the seven resource actions/routes, plus there are only six this time (no need for an index route/action because we are working against a single resource). Using a profile resource as an example, we’d get the following routes:

Action HTTP Method URL
Show GET /profile
New GET /profile/new
Create POST /profile
Edit GET /profile/edit
Update PUT /profile
Destroy DELETE /profile

NOTE: These routes/actions are the same as they’d be in Rails.

The concept of a “resource” keeps your routes nice and clean. I encourage you to check out the Restful Routing resource documentation for more information.

More Routing Fun

At this point you’re probably wondering about additional routes. Sure, for CRUD operations the six or seven routes make sense, but we do a lot more than that when writing an app. Have no fear. Rails and Restful Routing have plenty of other mapping schemes for routes without resorting to a greedy workaround. For example, you can route your root action for the application using the Root method:

map.Root<HomeController>(x => x.Index());

You can add additional routes to a resources block. For example, say you wanted to add a book search page, /books/search. For that, you would add a collection route because it is against the collection books.

map.Resources<BooksController>(books => {
  books.Collection(x => x.Get("search"));
});

If, on the other hand, your route works against a member, that is a single model in the collection, you’d add a member route. Sticking with the books example, what if you wanted something like /books/:id/rate where you’d want it to be an HTTP POST instead of a GET?

map.Resources<BooksController>(books => {
  books.Member(x => x.Post("rate"));
});

Note that if you are working with a resource mapping (e.g. /profile), only the Member method is available as there isn’t a collection. Check out the documentation on collection/member routes for more information.

Finally, you may have routes that don’t fit nicely into the resource(s) convention. No problem and no need to use a greedy route! Check out the documentation showing standard mappings.

Conclusion

Restful Routing is a fantastic project. It is the very first thing I install anytime I start work on a MVC project. This post touched the basics of Restful Routing, but let me assure you, there are some other really nice surprises built in. I’ll save those features for another post. In the meantime, I’d recommend you head over to the Restful Routing site and discover how awesome the project is for yourself!

Posted in asp.net, mvc, rest, ruby on rails and tagged with ASP.NET MVC, Rails, mvc, rest, restful, routing

Damien White

I am a software architect with over 15 years of experience. I simply love coding! I have a driving passion for computers and software development, and a thirst for knowledge that just cannot be quenched. I'm happy to share what I knows in my quest to learn as much as possible. I focus most of my time on web development using Ruby on Rails and ASP.NET MVC.

comments powered by Disqus