Dynamic And Friendly URL Using MVC

Dynamic URL is a great feature working with MVC. Friendly URLs are even better. The following approach, I think, is the best way to work with friendly URL.

So, let's define some premises.

  1. The URLs must be stored in a Repository. This means, I want to change and create new URLs in my repository;
  2. One or more URLs can be pointed to the same Controller/Action. This means, I want to have alias for URLs;
  3. If a URL does not exist in my Repository, try to resolve it using MVC Controller/Action default behavior. It means, the MVC default behavior will still work;
  4. The URL cannot contain an ID at the end. It means that the last segment of those URLs can be a long ID number.

First of all, MVC does not have a built-in feature for dynamic and friendly URLs. You must write your own custom code.

For solution, we will need the following.

  1. An MVC project;
  2. A class to handle route requests;
  3. A route repository;
  4. Controllers and Views;
PS-  I will not use a database to store those URLs but I will use the repository pattern and dependency resolver to configure it. So, you can create a database repository in future.

Class that identifies a URL -

Handlers/UrlHandler.cs 
  1. public sealed class UrlHandler {  
  2.     public static UrlRouteData GetRoute(string url) {  
  3.         url = url ? ? "/";  
  4.         url = url == "/" ? "" : url;  
  5.         url = url.ToLower();  
  6.   
  7.         UrlRouteData urlRoute = null;  
  8.   
  9.         using(var repository = DependencyResolver.Current.GetService < IRouteRepository > ()) {  
  10.             var routes = repository.Find(url);  
  11.             var route = routes.FirstOrDefault();  
  12.             if (route != null) {  
  13.                 route.Id = GetIdFromUrl(url);  
  14.                 urlRoute = route;  
  15.                 urlRoute.Success = true;  
  16.             } else {  
  17.                 route = GetControllerActionFromUrl(url);  
  18.                 urlRoute = route;  
  19.                 urlRoute.Success = false;  
  20.             }  
  21.         }  
  22.   
  23.         return urlRoute;  
  24.     }  
  25.   
  26.     private static RouteData GetControllerActionFromUrl(string url) {  
  27.         var route = new RouteData();  
  28.   
  29.         if (!string.IsNullOrEmpty(url)) {  
  30.             var segmments = url.Split('/');  
  31.             if (segmments.Length >= 1) {  
  32.                 route.Id = GetIdFromUrl(url);  
  33.                 route.Controller = segmments[0];  
  34.                 route.Action = route.Id == 0 ? (segmments.Length >= 2 ? segmments[1] : route.Action) : route.Action;  
  35.             }  
  36.         }  
  37.   
  38.         return route;  
  39.     }  
  40.   
  41.     private static long GetIdFromUrl(string url) {  
  42.         if (!string.IsNullOrEmpty(url)) {  
  43.             var segmments = url.Split('/');  
  44.             if (segmments.Length >= 1) {  
  45.                 var lastSegment = segmments[segmments.Length - 1];  
  46.                 long id = 0;  
  47.                 long.TryParse(lastSegment, out id);  
  48.   
  49.                 return id;  
  50.             }  
  51.         }  
  52.   
  53.         return 0;  
  54.     }  
  55. }

Route Handler that handles all requests.

Handlers/UrlRouteHandler.cs

  1. public IHttpHandler GetHttpHandler(RequestContext requestContext)   
  2. {  
  3.     var routeData = requestContext.RouteData.Values;  
  4.     var url = routeData["urlRouteHandler"] as string;  
  5.     var route = UrlHandler.GetRoute(url);  
  6.   
  7.     routeData["url"] = route.Url;  
  8.     routeData["controller"] = route.Controller;  
  9.     routeData["action"] = route.Action;  
  10.     routeData["id"] = route.Id;  
  11.     routeData["urlRouteHandler"] = route;  
  12.   
  13.     return new MvcHandler(requestContext);  

The route handler configuration.

App_Start/RouteConfig.cs

  1. public class RouteConfig   
  2. {  
  3.     public static void RegisterRoutes(RouteCollection routes)   
  4.   {  
  5.         routes.IgnoreRoute("{resource}.axd/{*pathInfo}");  
  6.   
  7.         routes.MapRoute(  
  8.             "IUrlRouteHandler",  
  9.             "{*urlRouteHandler}").RouteHandler = new UrlRouteHandler();  
  10.     }  

Repository/IRouteRepository.cs

  1. public interface IRouteRepository: IDisposable   
  2. {  
  3.     IEnumerable < RouteData > Find(string url);  

Repository/StaticRouteRepository.cs

  1. public class StaticRouteRepository: IRouteRepository  
  2. {  
  3.     public void Dispose() {  
  4.   
  5.     }  
  6.   
  7.     public IEnumerable < RouteData > Find(string url) {  
  8.         var routes = new List < RouteData > ();  
  9.         routes.Add(new RouteData() {  
  10.             RoouteId = Guid.NewGuid(),  
  11.                 Url = "how-to-write-file-using-csharp",  
  12.                 Controller = "Articles",  
  13.                 Action = "Index"  
  14.         });  
  15.         routes.Add(new RouteData() {  
  16.             RoouteId = Guid.NewGuid(),  
  17.                 Url = "help/how-to-use-this-web-site",  
  18.                 Controller = "Help",  
  19.                 Action = "Index"  
  20.         });  
  21.   
  22.         if (!string.IsNullOrEmpty(url)) {  
  23.             var route = routes.SingleOrDefault(r => r.Url == url);  
  24.             if (route == null) {  
  25.                 route = routes.FirstOrDefault(r => url.Contains(r.Url)) ? ? routes.FirstOrDefault(r => r.Url.Contains(url));  
  26.             }  
  27.   
  28.             if (route != null) {  
  29.                 var newRoutes = new List < RouteData > ();  
  30.                 newRoutes.Add(route);  
  31.   
  32.                 return newRoutes;  
  33.             }  
  34.         }  
  35.   
  36.         return new List < RouteData > ();  
  37.     }  

I have created 2 URLs. One URL will point to the Help Controller while the other one to the Articles Controller. For dependency resolver configuration, I used Ninject.

App_Start/NinjectWebCommon.cs

  1. private static void RegisterServices(IKernel kernel)  
  2. {  
  3.     kernel.Bind < Repository.IRouteRepository > ().To < Repository.StaticRouteRepository > ();  
  4. }

Download full source code.