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.
- The URLs must be stored in a Repository. This means, I want to change and create new URLs in my repository;
- One or more URLs can be pointed to the same Controller/Action. This means, I want to have alias for URLs;
- 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;
- 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.
- An MVC project;
- A class to handle route requests;
- A route repository;
- 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
- 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/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 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
- private static void RegisterServices(IKernel kernel)
- {
- kernel.Bind < Repository.IRouteRepository > ().To < Repository.StaticRouteRepository > ();
- }
Download full source code.