RESTful routes for ASP.NET MVC
This post refers to an old version of restful routing, please see this updated post for a more recent reference.
Rails’ RESTful routing is something I miss when developing for ASP .NET MVC. The style it imposes on your application architecture has many benefits. I wanted this style for my ASP.NET MVC apps. I started looking at Adam Tybor’s SimplyRestfulRouting handler in MvcContrib. This works well for simple applications, but it doesn’t have a lot of the features/configuration options rails’ routing does. I needed more, so I decided to build my own.
RestfulRouting
RestfulRouting is based on Rails’ RESTful routing and has nested routes, shallow nested routes, singleton resources and a lot of configuration options. You map a resource like this:
var map = new RestfulRouteMapper(RouteTable.Routes);
map.Resources<BlogsController>();
This will map a blogs resource with the following routes.
| Path | Default Action | Constraints |
|---|---|---|
| blogs | index | GET |
| blogs | create | POST |
| blogs/new | new | GET |
| blogs/{id}/action | show | GET, action = show,edit or delete |
| blogs/{id} | update | PUT |
| blogs/{id} | destroy | DELETE |
It also includes a route to accept post overrides, which will let you specify put or delete on a POST request to a resource and have it map to update or destroy <input type="hidden" name="_method" value="delete" />. This is needed because HTML forms only support POST or GET.
Configuration
// maps it to admin/blogs
map.Resources<BlogsController>(config => config.PathPrefix = "admin");
// maps the resource as weblogs instead of blogs
map.Resources<BlogsController>(config => config.As = "weblogs");
// only accepts integer ids
map.Resources<BlogsController>(config => config.IdValidationRegEx = @"\d+");
// adds an extra collection action blogs/latest GET
map.Resources<BlogsController>(config => config.AddCollectionRoute("latest");
// excludes the delete route from the mappings
map.Resources<BlogsController>(config => config.Except("delete");
// only maps the index and show actions
map.Resources<BlogsController>(config => config.Only("index", "show"));
// adds an extra member action blogs/1/moveup that only accepts POSTs
map.Resources<BlogsController>(config => config.AddMemberRoute("moveup", HttpVerbs.Post));
Nested Resources
Resources can be nested
map.Resources<BlogsController>(m => {
m.Resources<PostsController>(p => {
p.Resources<CommentsController>();
})
});
So a comment path would look like this
blogs/1/posts/2/comments/3
Shallow nested resources
If you don’t want to nest 3 levels down you can specify the shallow option.
map.Resources<BlogsController>(config => config.Shallow = true, m => {
m.Resources<PostsController>(p => {
p.Resources<CommentsController>();
})
});
This maps the index of posts under a blog resource and at the root. You would have paths like this
blogs/1/posts
posts/1
posts/1/comments
comments/1
A more complex example
Route mappings for a case study, which has many images and videos. The CaseStudyImagesController is mapped as images and has a member action to get a thumbnail. The path for a case study thumbnail would be casestudies/2/images/3/thumb.
var map = new RestfulRouteMapper(routes);
map.Resources<CaseStudiesController>(study => {
study.Resources<CaseStudyImagesController>(config => {
config.As = "images";
config.AddMemberRoute("thumb");
});
study.Resources<CaseStudyVideosController>(config => config.As = "videos");
});
More info and a sample project can be found on the GitHub project page.