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 best way to work with friendly URL.
LIVE DEMO:
http://codefinal.com/FSL.DynamicAndFriendlyUrlUsingMvc/
FULL ARTICLE:
English: https://fabiosilvalima.net/en/dynamic-friendly-url-using-mvc/
Português: https://fabiosilvalima.net/url-amigavel-e-dinamica-no-mvc/
- Visual Studio solution file;
- MVC project;
- A class to handle route requests;
- A route repository
- Controllers and views
Remarks:
- I created the application using the Web Application template. Visual Studio created a lot of files, views, scripts.
- The URLs must be stored in a Repository. It means I want to change and create new urls in my repository;
- One or more URL can be pointed to the same Controller/Action. It means I want to have alias for URLs;
- 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;
- 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;
Assumptions:
- You need to create a virtual directory in your IIS.
- 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.
First of all, MVC does not have a built in feature for dynamic and friendly URL. You must write your own custom code.
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>();
}
- ASP.NET MVC click here;
- More at my blog click here;
- Licence MIT