Customizing Routes in ASP.NET MVC – Custom RouteBase implementations

MVC Routing is pretty flexible in itself and as we recently saw in Friendly URLs in ASP.NET Web Forms , we can mix and match old web forms style URLs with modern friendly URLs even keeping both side by side. However, there are often quirks of implementation that need us to dig a little deeper for a solution.

A quick search on the interwebs reveals the fact that people have very specific reasons from MultiTenancy to Custom paths not possible with the out of the box routing mechanisms.
Today we’ll pick an easy and superfluous one to demonstrate how to do a custom RouteBase implementation. We will see how we can re-direct non-standard URLs like html/asp and aspx files to a cleaner ‘Friendly Url’ format.

The Requirement

Handle extensions like html/pdf and redirect to Friendly URLs that ‘look like’ MVC generated URLs.

The Custom Route Implementation

We have to derive our own Route class from Route base and implement two methods

1. GetRouteData: This method is called when Inbound URL Matching is required.

2. GetVirtualPath: This method is called when Outbound URL Generation is required

Step 1: To create a custom route base, we start off with a Basic Project template.

Step 2: Next we add a CustomRouteController to channel all the requests to. The controller simply puts the incoming URL in a label in our sample, but in reality, it could use the incoming URL and find out from a mapping database what the new equivalent URL was and do a RedirectPermanent action to a new Controller/ActionMethod too.

That’s with the Controller.

public class CustomRouteController : Controller
{
public ActionResult DirectCustomUrls(string customUrl)
{
  if (customUrl.EndsWith("html"))
  {
   return new RedirectResult(customUrl);
  }
  else
  {
   ViewBag.ErrorMessage = "Unrecognized URL";
   return View();
  }
}

}

Step 3: Now let’s setup the Custom Route. The constructor by design takes an array of valid URLs that we will have to pass when we are creating the route for this.

The GetRouteData method is called when a new request is made. In this method, the Requested path is checked to see if it contains any of the URLs that were passed in the constructor. If anyone matches, we send back the required RouteData, if not, we return Null and MVC takes the Route resolution to the next registered route.

Point to note is - the mechanism to verify the incoming Request Information matches the ‘custom requirements’ has to be in the GetRouteData method. Our oversimplistic scenario has resulted in a rather petty check involving string matching.

public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
string requestedURL = string.Empty;
for (int i = 0; i < urls.Length; i++)
{
  if (httpContext.Request.AppRelativeCurrentExecutionFilePath.Contains(urls[i]))
  {
   requestedURL = httpContext.Request.AppRelativeCurrentExecutionFilePath;
   break;
  }
}     


if (!string.IsNullOrEmpty(requestedURL))
{
  result = new RouteData(this, new MvcRouteHandler());
  result.Values.Add("controller", "CustomRoute");
  result.Values.Add("action", "DirectCustomUrls");
  result.Values.Add("customUrl", requestedURL);
}
return result;
}

Step 4: Registering our Route.

1.    We register our route as follows in the App_Data\RouteConfig.cs

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add(new CustomRouteBase.Routing.CustomRouteBase(
  "html",
  "pdf"));

routes.MapRoute(
  name: "Default",
  url: "{controller}/{action}/{id}",
  defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}

Essentially we are taking over all URLs that contain an html or pdf and redirecting via our Route.
We add two default views for the Home Controller and the DynamicController. The DynamicController picks up the message from View Bag and displays it if any.

Let’s Run our app.

We initially are at the home page. Thereafter when we navigate to a random URL that ends with html we get redirected to our custom page via our CustomRouteBase.

custom-route-incoming

Niceee.

Generating Outgoing URLs

Lets say now user needs to generate an HttpActionLink, to support this, we need to implement the GetVirtualPath method in our CustomRouteBase class. Once again, if we are unable to deal with the request, we let the routing system know by returning null. Otherwise, we return an instance of the VirtualPathData class.

public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
    VirtualPathData result = null;
    if (values.ContainsKey("html") || urls.Contains((string)values["html"], StringComparer.OrdinalIgnoreCase))
    {
        result = new VirtualPathData(this,
        new UrlHelper(requestContext)
        .Content((string)values["html"]).Substring(1));
    }
    return result;
}


Now we place the following markup in our Index.cshtml

<div>This is a URL:
@Html.ActionLink("Click me", "CustomUrl",
new { html = "~/html/OldFile.html" })
</div>


This results in the following ActionLink getting generated

custom-route-outgoing

We can manipulate the generated any way we want based on the final requirement.

Conclusion

With that we conclude this demo of how to Manipulate Route with a custom implementation of RouteBase. Next time we’ll see how we can create a custom RouteHandler and how it can be used.

Download the entire source code of this article (Github)




About The Author

Suprotim Agarwal
Suprotim Agarwal, Developer Technologies MVP (Microsoft Most Valuable Professional) is the founder and contributor for DevCurry, DotNetCurry and SQLServerCurry. He is the Chief Editor of a Developer Magazine called DNC Magazine. He has also authored two Books - 51 Recipes using jQuery with ASP.NET Controls. and The Absolutely Awesome jQuery CookBook.

Follow him on twitter @suprotimagarwal.

1 comment:

Anonymous said...

The only thing that makes routing flexible in ASP.NET MVC is Attribute Routing. Without that routing in MVC is completely worthless.