restful-routing

RestfulRouting is a routing library for ASP.NET MVC based on the Rails 3 routing DSL.

Links

Table of Contents

Example

public class Routes : RouteSet
{
  public Routes()
  {
    Resource<SessionsController>();
    Resources<BlogsController>(() =>
    {
      As("weblogs");
      Only("index", "show");
      Collection("latest", HttpVerbs.Get);

      Resources<PostsController>(() =>
      {
        Except("create", "update", "destroy");
        Resources<CommentsController>(() => Except("destroy"));
      });
    });
  }
}

// Global.asax.cs
public class MvcApplication : System.Web.HttpApplication
{
  protected void Application_Start()
  {
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new RestfulRoutingViewEngine());

    RouteTable.Routes.MapRoutes<Routes>();
  }
}

Getting started

Clone the repository and run rake. This will compile and test the project. Now go to the build folder and grab the 2 assemblies you need to reference from your project.

git clone git://github.com/stevehodgkiss/restful-routing.git
cd restful-routing
rake

The RouteSet

All routes are defined inside the constructor of a class which inherits from RouteSet. You can then connect the main RouteSet to your application routes using RouteTable.Routes.MapRoutes().

Mapping Resources

There are 2 types of resource mappings that you can use. Resource<TController>() and Resources<TController>() (Notice the pluralization). Resources maps a collection of resources. Resource maps a singleton. The two main differences are that Resource doesn’t have an {id} route parameter, and it doesn’t have an index.

Resource

This maps a sessions resource as a singleton which has a nested avatars singleton resource.

Resource<SessionsController>(() =>
{
  Resource<AvatarsController>();
});

The RouteTable will look like this with the above mapping:

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

Resources

This maps a blogs resource with a nested posts resource.

Resources<BlogsController>(() =>
{
  Resources<PostsController>();
});

The RouteTable will have routes which look like this with the above mapping:

Http Method Path Endpoint (controller#action)
GET blogs blogs#index
POST blogs blogs#create
GET blogs/{id} blogs#show
GET blogs/new blogs#new
GET blogs/{id}/edit blogs#edit
PUT blogs/{id} blogs#update
DELETE blogs/{id} blogs#destroy
GET blogs/{blogId}/posts posts#index
POST blogs/{blogId}/posts posts#create
GET blogs/{blogId}/posts/{id} posts#show
GET blogs/{blogId}/posts/new posts#new
GET blogs/{blogId}/posts/{id}/edit posts#edit
PUT blogs/{blogId}/posts/{id} posts#update
DELETE blogs/{blogId}/posts/{id} posts#destroy

Route configuration

You can adjust the behaviour of a given resource mapping in the context of it's nested action parameter.

Resources<PostsController>(() => {
  As("weblogs"); // changes the path name to weblogs
  Only("index", "show") // only maps the index and show actions
  Except("destroy") // maps all actions except destroy
  Constrain("id", "\d+"); // constrains the id to digits
});

Member and collection routes

Use the Member and Collection methods inside the resource to add routes to the collection (without an id route parameter) or the member (with an id route parameter). Methods available to map are Get, Post, Put, Delete and Head.

Resources<PostsController>(() => {
  Collection(x => {
    x.Get("latest"); // GET posts/latest => posts#latest
    x.Put("someaction") // PUT posts/someaction => posts#someaction
  });
  Member(x => x.Post("like")); // POST posts/1/like => posts#like(1)
});

Areas

Area<Controllers.Admin.BlogsController>("admin", () =>
{
  Resources<BlogsController>(() =>
  {
    Resources<PostsController>(() =>
    {
      Resources<CommentsController>();
    });
  });
});

This maps an area called admin and will only look for 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");

RestfulRoutingViewEngine

RestfulRoutingViewEngine inherits from WebFormsViewEngine and changes where it looks for area views slightly. It changes it from {area}/Views/{controller}/{action} to Views/{area}/{controller}/{action}.

Connecting RouteSets together

The connect method allows you to pull in routes from another routeset.

public class BlogRoutes : RouteSet
{
  public BlogRoutes()
  {
    Area<BlogsController>("blogs");
    Resources<BlogsController>();
    Resources<PostsController>();
  }
}

public class Routes : RouteSet
{
  public Routes()
  {
    // maps the BlogRoutes route set without a path prefix.
    Connect<BlogRoutes>("");
  }
}

// Application_Start()
RouteTable.Routes.MapRoutes<Routes>();

Standard mappings

Two additional methods are provided so you can add custom routes to the route set if you need to. Map is a chainable method that allows you to add standard mappings. Route is a method that will allow you to add a custom route to the route set.

Map("posts/{year}/{slug}")
  .To<PostsController>(x => x.Post(-1, ""))
  .Constrain("slug", @"\w+")
  .Constrain("year", @"\d+");
Route(new Route("posts/{action}", 
  new RouteValueDictionary(new { controller = "posts" }), 
  new MvcRouteHandler());