Dynamic and Friendly URL using MVC

Also available in portuguese

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

So let’s define some premises:

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

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

For the solution we will need:

1 – A 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 identify a URL.

Handlers/UrlHandler.cs

public sealed class UrlHandler
    {
        public static UrlRouteData GetRoute(string url)
        {
            url = url ?? "/";
            url = url == "/" ? "" : url;
            url = url.ToLower();

            UrlRouteData urlRoute = null;

            using (var repository = DependencyResolver.Current.GetService<IRouteRepository>())
            {
                var routes = repository.Find(url);
                var route = routes.FirstOrDefault();
                if (route != null)
                {
                    route.Id = GetIdFromUrl(url);
                    urlRoute = route;
                    urlRoute.Success = true;
                }
                else
                {
                    route = GetControllerActionFromUrl(url);
                    urlRoute = route;
                    urlRoute.Success = false;
                }
            }

            return urlRoute;
        }

        private static RouteData GetControllerActionFromUrl(string url)
        {
            var route = new RouteData();

            if (!string.IsNullOrEmpty(url))
            {
                var segmments = url.Split('/');
                if (segmments.Length >= 1)
                {
                    route.Id = GetIdFromUrl(url);
                    route.Controller = segmments[0];
                    route.Action = route.Id == 0? (segmments.Length >= 2? segmments[1] : route.Action) : route.Action;
                }
            }

            return route;
        }

        private static long GetIdFromUrl(string url)
        {
            if (!string.IsNullOrEmpty(url))
            {
                var segmments = url.Split('/');
                if (segmments.Length >= 1)
                {
                    var lastSegment = segmments[segmments.Length - 1];
                    long id = 0;
                    long.TryParse(lastSegment, out id);

                    return id;
                }
            }

            return 0;
        }
    }

Route Handler that handles all requests.

Handlers/UrlRouteHandler.cs

public IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            var routeData = requestContext.RouteData.Values;
            var url = routeData["urlRouteHandler"] as string;
            var route = UrlHandler.GetRoute(url);

            routeData["url"] = route.Url;
            routeData["controller"] = route.Controller;
            routeData["action"] = route.Action;
            routeData["id"] = route.Id;
            routeData["urlRouteHandler"] = route;

            return new MvcHandler(requestContext);
        }

The route handler configuration.

App_Start/RouteConfig.cs

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

            routes.MapRoute(
                "IUrlRouteHandler",
                "{*urlRouteHandler}").RouteHandler = new UrlRouteHandler();
        }
    }

Repository classes

Repository/IRouteRepository.cs

public interface IRouteRepository : IDisposable
    {
        IEnumerable<RouteData> Find(string url);
    }

Repository/StaticRouteRepository.cs

public class StaticRouteRepository : IRouteRepository
    {
        public void Dispose()
        {

        }

        public IEnumerable<RouteData> Find(string url)
        {
            var routes = new List<RouteData>();
            routes.Add(new RouteData()
            {
                RoouteId = Guid.NewGuid(),
                Url = "how-to-write-file-using-csharp",
                Controller = "Articles",
                Action = "Index"
            });
            routes.Add(new RouteData()
            {
                RoouteId = Guid.NewGuid(),
                Url = "help/how-to-use-this-web-site",
                Controller = "Help",
                Action = "Index"
            });

            if (!string.IsNullOrEmpty(url))
            {
                var route = routes.SingleOrDefault(r => r.Url == url);
                if (route == null)
                {
                    route = routes.FirstOrDefault(r => url.Contains(r.Url)) ?? routes.FirstOrDefault(r => r.Url.Contains(url));
                }

                if (route != null)
                {
                    var newRoutes = new List<RouteData>();
                    newRoutes.Add(route);

                    return newRoutes;
                }
            }

            return new List<RouteData>();
        }
    }

I have created 2 URL. One Url will point to Help Controller and another for Articles Controller.

The dependency resolver configuration. I use Ninject to do that.

App_Start/NinjectWebCommon.cs

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

This is it. I hope it helped.

Dynamic and Friendly URL using MVC

Friendly URL in MVC: Questions, suggestions and feedbacks will be appreciated. Good luck!

Do complete download of the source code on github.

Try demo online of that application on codefinal.
About the Author:
He works as a solution architect and developer, has more than 16 years of experience in software development on several platforms and more than 14 years only for the insurance market.