On a project I was working on we would like to have URLs like
www.thesite.com/greatproduct
Since this was a MVC 3 site the normal routing like this should work as well.
www.thesite.com/product/details/4
routes.MapRoute("Friendly", "{*FriendlyUrl}").RouteHandler = new FriendlyUrlRouteHandler();
This breaks the default MVC route defined like this
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new {controller = "Product", action = "Index", id = UrlParameter.Optional},
new[] {"thesite.Web.Controllers"} // Parameter defaults
);
Since both routes are catch all mixing them was not an option. But what about override the functionality of the default route handler? This is very easy. Basically by subclassing MvcRouteHandler we can add custom routing as well :) My custom routehandler does a lookup in the product table to find a product of that name and then show the details for that product. If the product name is not found the normal functionality is 'restored'. Also I do a check to see if the name is not a controller name. So the url www.thesite.com/product still maps to the index method of my product controller even if some product's name is 'product'.
public class FriendlyUrlRouteHandler : System.Web.Mvc.MvcRouteHandler
{
private static IList<string> controllerNames;private static IList<string> ControllerNames
{
get
{
if (controllerNames == null)
{
controllerNames = Assembly.GetExecutingAssembly().GetTypes().
Where(t => typeof(Controller).IsAssignableFrom(t)).
Select(c=>c.Name.Replace("Controller","")).
ToList();
}
return controllerNames;
}
}
protected override IHttpHandler GetHttpHandler(System.Web.Routing.RequestContext requestContext)
{
var friendlyUrl = (string)requestContext.RouteData.Values["controller"];
var firstPart = friendlyUrl.Contains("/")?
friendlyUrl.Split('/')[0] :
friendlyUrl;//it's a controller..handle normally
if (ControllerNames.Contains(firstPart))
{
return base.GetHttpHandler(requestContext);
}
if (!string.IsNullOrEmpty(friendlyUrl))
{
using (var ctx = new SiteDbContext())
{
var product = ctx.Events.FirstOrDefault(p => p.Title == friendlyUrl);
if (product != null)
{
requestContext.RouteData.Values["controller"] = "product";
requestContext.RouteData.Values["action"] = "details";
requestContext.RouteData.Values["id"] = product.Id;
}
}
}
return base.GetHttpHandler(requestContext);
}
}