19 people following this project (follow)

Namespace-aware automatic route creation, URL generation and views location.

  1. Organize your controllers and views using namespaces (no more areas) that can go as deep as you want.
  2. Default constraints for primivite types that can be overridden on a per-parameter or per-site basis.
  3. Intelligent grouping of similar routes for efficient matching.
  4. Support for a root controller.
  5. Support for overloaded actions.
  6. Support for hierarchical (a.k.a. RESTful) routes.
  7. Support for user-defined custom routes.
  8. Detection of ambiguous routes.
  9. Formatting of routes (e.g. to lowercase, hyphen-separated, underscore-separated, etc).
  10. Render your routes as calls to the MapRoute extension method, for debugging.
  11. Support for embedded views (as assembly resources).

Get it now!

Using NuGet: Install-Package MvcCodeRouting

Motivation

While ASP.NET MVC is an excellent framework and ASP.NET Routing is a very flexible routing system, the integration between the two is not the best you can wish for.

The generic route {controller}/{action}/{id} seems fine when you get started, but once your application starts to grow you realize how limiting and imprecise it is, for example:

  1. Some actions do not use the {id} token (e.g Home/Index), yet if the URL contains it (e.g Home/Index/foo) you don't get an HTTP 404 (Not Found) response as expected.
  2. If the value for the {id} token is not a valid string according to the corresponding action's parameter type you get an HTTP 500 (Internal Server Error) response, and not an HTTP 404 (Not Found) response as expected. You can improve this by using a constraint on the token, but different actions may use different types for the corresponding parameter.
  3. You want to organize your application and sometimes have more than one controller with the same name. Although that is what Areas are for, they only go one level deeper, what about sub-areas, or sub-sub-areas.
  4. You want to have a root controller so the URL doesn't include the {controller} token, e.g. About instead of Home/About .

You can overcome most of these issues by defining more routes that are more specific to your actions, but this task is not easy and has many pitfalls. You have to make sure your action is handled by the appropriate route so the generation of URLs (e.g. using Url.Action on your controllers and views) is correct. Sometimes this can be achieved by defining the routes in the appropriate order, but other times you are just forced to add constraints for the {controller} and {action} tokens. This comes with the cost that whenever you want to add, change or remove an action or controller you have to update the corresponding route to keep them n'sync.

Many have realized these issues and created solutions for defining routes using an attribute on the action method. Big sites like stackoverflow.com (and the family of stackexchange.com sites) use this attribute based routing. MvcCodeRouting goes a step further and recognizes that your code is declarative enough and has all the information needed to produce the best possible routes.

How to use it

Let's take the MvcMusicStore application and see how to use MvcCodeRouting on it.

These are the existing route definitions:

public static void RegisterRoutes(RouteCollection routes)
{
   routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

   routes.MapRoute(
         "Default", // Route name
         "{controller}/{action}/{id}", // URL with parameters
         new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
   );
}

We'll change it to this:

public static void RegisterRoutes(RouteCollection routes)
{
   routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

   routes.MapCodeRoutes(
      rootController: typeof(Controllers.HomeController),
      settings: new CodeRoutingSettings {
         UseImplicitIdToken = true
      }
   );
}

You'll need to import the MvcCodeRouting namespace to use the MapCodeRoutes extension method.

The rootController is used as the starting point for your controllers and routes, controllers must be defined in the same namespace or any sub-namespace beneath it, in the same assembly. Routes for the root controller do not include the {controller} token, e.g. About instead of Home/About. Imagine a bunch of files in the C:\ drive, they do not belong to a folder. In MVC of course, actions must belong to a controller, so we simply designate a controller to act as the root controller.

We are using the UseImplicitIdToken option so we don't have to change the source code and decorate all action parameters named id with the FromRoute attribute, although that's recommended to make sure that the value actually comes from the URL.

And that's it, the application should work as expected. To see what are the routes that MvcCodeRouting created let's add the RouteDebugHandler to web.config:

   <system.web>
      <httpHandlers>
         <add path="routes.axd" verb="GET,HEAD" type="MvcCodeRouting.RouteDebugHandler, MvcCodeRouting"/>
      </httpHandlers>
   </system.web>
   <system.webServer>
      <validation validateIntegratedModeConfiguration="false"/>
      <handlers>
         <add name="MvcCodeRouting.RouteDebugHandler" path="routes.axd" verb="GET,HEAD" type="MvcCodeRouting.RouteDebugHandler, MvcCodeRouting"/>
      </handlers>
   </system.webServer>

If you visit ~/routes.axd you'll see the following:

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

routes.MapRoute(null, "{action}", 
   new { controller = @"Home", action = @"Index" }, 
   new { action = @"Index" }, 
   new[] { "MvcMusicStore.Controllers" });

routes.MapRoute(null, "Account/{action}", 
   new { controller = @"Account" }, 
   new { action = @"ChangePassword|ChangePasswordSuccess|LogOff|LogOn|Register" }, 
   new[] { "MvcMusicStore.Controllers" });

routes.MapRoute(null, "Checkout/AddressAndPayment", 
   new { controller = @"Checkout", action = @"AddressAndPayment" }, 
   new[] { "MvcMusicStore.Controllers" });

routes.MapRoute(null, "Checkout/Complete/{id}", 
   new { controller = @"Checkout", action = @"Complete" }, 
   new { id = @"0|-?[1-9]\d*" }, 
   new[] { "MvcMusicStore.Controllers" });

routes.MapRoute(null, "ShoppingCart/{action}/{id}", 
   new { controller = @"ShoppingCart" }, 
   new { action = @"AddToCart|RemoveFromCart", id = @"0|-?[1-9]\d*" }, 
   new[] { "MvcMusicStore.Controllers" });

routes.MapRoute(null, "ShoppingCart/{action}", 
   new { controller = @"ShoppingCart", action = @"Index" }, 
   new { action = @"CartSummary|Index" }, 
   new[] { "MvcMusicStore.Controllers" });

routes.MapRoute(null, "Store/{action}", 
   new { controller = @"Store", action = @"Index" }, 
   new { action = @"Browse|GenreMenu|Index" }, 
   new[] { "MvcMusicStore.Controllers" });

routes.MapRoute(null, "Store/Details/{id}", 
   new { controller = @"Store", action = @"Details" }, 
   new { id = @"0|-?[1-9]\d*" }, 
   new[] { "MvcMusicStore.Controllers" });

routes.MapRoute(null, "StoreManager/{action}", 
   new { controller = @"StoreManager", action = @"Index" }, 
   new { action = @"Create|Index" }, 
   new[] { "MvcMusicStore.Controllers" });

routes.MapRoute(null, "StoreManager/{action}/{id}", 
   new { controller = @"StoreManager" }, 
   new { action = @"Delete|Edit", id = @"0|-?[1-9]\d*" }, 
   new[] { "MvcMusicStore.Controllers" });

MvcMusicStore is a relatively small and simple application, so this example does not portrait all MvcCodeRouting features, but it shows us how simple it is to use it, and the quality of the routes it produces.

Using longer routes

To create longer routes you simply need a controller in a deeper namespace, and/or an action with more parameters decorated with the FromRoute attribute. For example, the route Store/Products/Browse/{category}/{page} could map to the following controller/action (assuming MyStore.Controllers is the root namespace):

namespace MyStore.Controllers.Store {
   
   public class ProductsController {
      
      public ActionResult Browse([FromRoute]string category, [FromRoute]int page) { 
         ...
      }
   }
}

The idea is simple. The union of each namespace segment after the root namespace is used as the base route, then we have the {controller} and {action} tokens as usual, and finally all the parameter tokens ordered as defined in the action method.

Views location

Continuing with the previous example, by default views for the Products controller must be located in ~/Views/Products. To change the location of views to a namespace aware location you need to call the EnableCodeRouting extension method (you need to import the MvcCodeRouting namespace to use it):

void Application_Start(object sender, EventArgs e) {
      
   ViewEngines.Engines.EnableCodeRouting();
}

Now views for the MyStore.Controllers.Store.ProductsController are located in ~/Views/Store/Products.

See the Documentation for more information.

Last edited Mon at 12:10 AM by maxtoroq, version 89