Restful routing for ASP.NET MVC 2
Restful routing has a new syntax and better support for ASP.NET MVC 2 areas. Routes are now grouped into route sets and a route set can be composed of multiple route sets using the Connect<RouteSet>(“pathprefix”) method described later.
public class Routes : RouteSet
{
public Routes()
{
Resources<BlogsController>(() =>
{
As("weblogs");
Only("index", "show");
Collection("latest", HttpVerbs.Get);
Resources<PostsController>(() =>
{
Except("create", "update", "destroy");
Resources<CommentsController>(() => Except("destroy"));
});
});
}
}
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new RestfulRoutingViewEngine());
RouteTable.Routes.MapRoutes<Routes>();
}
}
This will generate the following routes:
| Http Method | Path | Endpoint (controller#action) |
|---|---|---|
| GET | weblogs | blogs#index |
| GET | weblogs/latest | latest |
| GET | weblogs/{id} | blogs#show |
| GET | weblogs/{blogId}/posts | posts#index |
| GET | weblogs/{blogId}/posts/new | posts#new |
| GET | weblogs/{blogId}/posts/{id}/edit | posts#edit |
| GET | weblogs/{blogId}/posts/{id} | posts#show |
| GET | weblogs/{blogId}/posts/{postId}/comments | comments#index |
| POST | weblogs/{blogId}/posts/{postId}/comments | comments#create |
| GET | weblogs/{blogId}/posts/{postId}/comments/new | comments#new |
| GET | weblogs/{blogId}/posts/{postId}/comments/{id}/edit | comments#edit |
| GET | weblogs/{blogId}/posts/{postId}/comments/{id} | comments#show |
| PUT | weblogs/{blogId}/posts/{postId}/comments/{id} | comments#update |
Http method overrides
Http methods can be overridden on POST requests by specifying a form parameter name _method with the value of the method you wanted to use. We need this because you can only use GET and POST as the HTTP method on HTML forms. There are a couple of helper methods available to generate these tags.
<%= Html.PutOverrideTag() %>
<input type="hidden" name="_method" value="put">
<%= Html.DeleteOverrideTag() %>
<input type="hidden" name="_method" value="delete">
Resources and Resource
Resources maps a collection of resources and Resource maps a singleton resource. You would use the singleton resource mapping for resources that the user only has one of. Session is a typical example of this.
Resource<SessionsController>(() =>
{
Resource<AvatarsController>();
});
Note the controller name is still plural, as the application handles many of them, it is just the path to the resource that is singular. This produces the following routes:
| Http Method | Path | Endpoint (controller#action) |
|---|---|---|
| GET | session | sessions#show |
| POST | session | sessions#create |
| GET | session/new | sessions#new |
| GET | session/edit | sessions#edit |
| PUT | session | sessions#update |
| DELETE | session | sessions#destroy |
| GET | session/avatar | avatars#show |
| POST | session/avatar | avatars#create |
| GET | session/avatar/new | avatars#new |
| GET | session/avatar/edit | avatars#edit |
| PUT | session/avatar | avatars#update |
| DELETE | session/avatar | avatars#destroy |
Areas
Area<Controllers.Admin.BlogsController>("admin", () =>
{
Resources<BlogsController>(() =>
{
Resources<PostsController>(() =>
{
Resources<CommentsController>();
});
});
});
This maps an area called admin with the controllers located in the Controllers.Admin namespace. You can specify that the whole RouteSet is to be put into an area without putting a path prefix on the mapping. The following would just constrain all the routes to the namespace of BlogsController, but still look for views based on the area name blogs.
Area<BlogsController>("blogs");
Resources<BlogsController>();
If you want to nest mappings under a specific path you can use a non-namespaced area. This will just prepend admin/ to the blogs resource, without constraining to a namespace of controllers.
Area("admin", () => Resources<BlogsController>());
To generate URL’s for area’s you need to specify the are name
Url.Action("index", "blogs", new { area = "admin "}, new {})
Connecting route sets together
You can connect routes together using the Connect method.
public class BlogRoutes : RouteSet
{
public BlogRoutes()
{
Area<BlogsController>("blogs");
Resources<BlogsController>();
Resources<PostsController>();
}
}
public class Routes : RouteSet
{
public Routes()
{
Connect<BlogRoutes>(""); // maps the BlogRoutes route set.
}
}
// Application_Start()
RouteTable.Routes.MapRoutes<Routes>();
RestfulRoutingViewEngine
By default the WebformViewEngine looks for area views in this format: Areas/{area}/Views/{controller}/{action}. The RestfulRoutingViewEngine looks for views in this format: Views/{area}/{controller}/{action}. Using the RestfulRoutingViewEngine is completely optional.
Member and collection actions
Member actions are those which are to be performed on a member of the collection, and include the ID in the path. Collection actions are mapped against the collection and do not include the ID in the generated route.
Resources<BlogsController>(() =>
{
Member("print", HttpVerbs.Get); // blogs/{id}/print
Collection("latest", HttpVerbs.Get); // blogs/latest
});
Standard mappings
A way to specify standard mappings is also provided.
Map("posts/{year}/{slug}").To<PostsController>(x => x.Post(-1, "")).Constrain("slug", @"\w+").Constrain("year", @"\d+");
You can also add custom routes.
Route(new Route("posts/{action}", new RouteValueDictionary(new { controller = "posts" }), new MvcRouteHandler());
More information: