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. UrlRouteData urlRoute = null;
  7. using(var repository = DependencyResolver.Current.GetService < IRouteRepository > ()) {
  8. var routes = repository.Find(url);
  9. var route = routes.FirstOrDefault();
  10. if (route != null) {
  11. route.Id = GetIdFromUrl(url);
  12. urlRoute = route;
  13. urlRoute.Success = true;
  14. } else {
  15. route = GetControllerActionFromUrl(url);
  16. urlRoute = route;
  17. urlRoute.Success = false;
  18. }
  19. }
  20. return urlRoute;
  21. }
  22. private static RouteData GetControllerActionFromUrl(string url) {
  23. var route = new RouteData();
  24. if (!string.IsNullOrEmpty(url)) {
  25. var segmments = url.Split('/');
  26. if (segmments.Length >= 1) {
  27. route.Id = GetIdFromUrl(url);
  28. route.Controller = segmments[0];
  29. route.Action = route.Id == 0 ? (segmments.Length >= 2 ? segmments[1] : route.Action) : route.Action;
  30. }
  31. }
  32. return route;
  33. }
  34. private static long GetIdFromUrl(string url) {
  35. if (!string.IsNullOrEmpty(url)) {
  36. var segmments = url.Split('/');
  37. if (segmments.Length >= 1) {
  38. var lastSegment = segmments[segmments.Length - 1];
  39. long id = 0;
  40. long.TryParse(lastSegment, out id);
  41. return id;
  42. }
  43. }
  44. return 0;
  45. }
  46. }

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. routeData["url"] = route.Url;
  7. routeData["controller"] = route.Controller;
  8. routeData["action"] = route.Action;
  9. routeData["id"] = route.Id;
  10. routeData["urlRouteHandler"] = route;
  11. return new MvcHandler(requestContext);
  12. }

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. routes.MapRoute(
  7. "IUrlRouteHandler",
  8. "{*urlRouteHandler}").RouteHandler = new UrlRouteHandler();
  9. }
  10. }

Repository/IRouteRepository.cs

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

Repository/StaticRouteRepository.cs

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

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.