Supporting content hierarchies with a custom RouteHandler

In this recipe, we will take a look at a great use of the wildcard route. We will show how to make a single route that handles multiple URLs from /Sports/Football/Padding/Rawlings/ShoulderPads to /Aquariums/100-Gallon/Stands/Marineland/Monterey-Stand. We will also take this concept one step further by supporting additional commands other than just a Get content type scenario. We will also add support Delete, Post, and Edit, so that our single route can support an inline style content management solution.

How to do it...

  1. Start by creating a new MVC application.
  2. Then open up the Global.asax file. In the routing section, add a new route definition to the bottom of the list. This route will not specify a controller, an action, or any other parameters. Instead it will have only a wildcard option to catch all requests that make it to the bottom of our route dictionary. This route will use the Home Controller and a Catalog action that we will create shortly. Also, this route will use a custom RouteHandler called CatalogRouteHandler.

    Global.asax:

    routes.MapRoute("DbContent", "{*path}", new {controller = "Home", action = "Catalog"}) .RouteHandler = new CatalogRouteHandler();
    
  3. Next, we need to create our Catalog action. This action should technically not be reached by this style of request, but it can be used as an entry page to your catalog.

    Controllers/HomeController.cs:

    public ActionResult Catalog()
    {
    return View();
    }
    
  4. Then we need to create our CatalogRouteHandler. This class will implement the IRouteHandler, which requires that a GetHttpHandler method be specified.

    Models/CatalogRouteHandler.cs:

    public class CatalogRouteHandler : IRouteHandler
    {
    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
    ...
    }
    }
    
  5. With our method in place, we now need to add the guts. This method will be responsible for catching the path requested and parsing it in the best manner possible. You could use the entire path value as the unique ID in your system to identify a piece of content. Or you could use just the last part of the path as the unique identifier. I will leave that up to you! In our implementation we will parse for the entire path. We will also check to see if there is an additional command at the end of the path, so that we can support different types of commands (which will translate to different actions) for pretty much the same URL.

    Models/CatalogRouteHandler.cs:

    string path = requestContext.RouteData.Values["path"] as string;
    //remove trailing slash if there is one
    if (path.EndsWith("/"))
    path = path.Substring(0, path.Length - 1);
    if(path != null && path.Contains("/")) //valid path parameter
    {
    int lastIndex = path.LastIndexOf('/'),
    if(lastIndex >= 0)
    {
    string commandName = path.Substring(lastIndex + 1);
    ...
    }
    }
    
  6. Once we have the path in hand and have parsed for the command that may or may not be there, we can then try to route to the appropriate action for the given request. We will use a simple switch statement to achieve this. Once we locate the type of command we are going to use, we will then modify our current route definition to have the appropriate action as well as the cleaned and adjusted path.

    Models/CatalogRouteHandler.cs:

    switch(commandName.ToUpper())
    {
    case "GET": //get the catalog item
    requestContext.RouteData.Values["action"] = "Get";
    //add the path minus the command back in
    requestContext.RouteData.Values["path"] = path.Substring(0, lastIndex);
    break;
    case "DELETE": //delete catalog item
    requestContext.RouteData.Values["action"] = "Delete";
    //add the path minus the command back in
    requestContext.RouteData.Values["path"] = path.Substring(0, lastIndex);
    break;
    case "EDIT": //edit catalog item
    requestContext.RouteData.Values["action"] = "Edit";
    //add the path minus the command back in
    requestContext.RouteData.Values["path"] = path.Substring(0, lastIndex);
    break;
    case "POST": //save catalog item (insert/update)
    requestContext.RouteData.Values["action"] = "Post";
    //add the path minus the command back in
    requestContext.RouteData.Values["path"] = path.Substring(0, lastIndex);
    break;
    default: //we will allow nothing to act as a GET
    requestContext.RouteData.Values["action"] = "Get";
    //add the path minus the command back in
    requestContext.RouteData.Values["path"] = path;
    break;
    }
    
  7. Once the appropriate route has been picked, we are ready to return the modified route.

    Models/CatalogRouteHandler.cs:

    ...
    }
    return new MvcHandler(requestContext);
    }
    
  8. Now that we have a dynamic routing engine that is capable of determining all sorts of different actions based on the passed in URL, we need to actually create the actions and views to capture the requests.

    Controllers/HomeController.cs:

    [HandleError]
    public class HomeController : Controller
    {
    ...
    public ActionResult Get(string path)
    {
    return View();
    }
    public ActionResult Delete(string path)
    {
    return View();
    }
    public ActionResult Post(string path)
    {
    return View();
    }
    public ActionResult Edit(string path)
    {
    return View();
    }
    }
    
  9. Next we need to create the views for each action. These will just be empty views as our recipe is about routing rather than data access and application. (I am not showing empty views as that is boring!)
  10. Now you can build and run the application. You can browse to just about anything and you will be routed to one of our new actions, as long as you don't navigate to /Home/About or /Home/Index or /Account/LogOn, and so on!

How it works...

As with most other recipes of this type, we have hooked into yet another extension point of the MVC framework. We have defined a catch all route at the bottom of our route dictionary. When this route is picked for a request, we shell out to a custom route handler, which doctors the route to suit its needs and returns the appropriate route definition.

There's more...

When researching a CMS system that I was building, I needed a recipe of this nature. While this post doesn't quite do all that I would have liked, it got me pretty close to what I was trying to achieve. Credit where credit is due! Thanks chris166. http://stackoverflow.com/questions/1023252/asp-net-mvc-complex-routing-for-tree-path.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset