📜 ⬆️ ⬇️

"Classic" friendly url in ASP.NET MVC

image
Speech in the topic will go about how to make links like www.site.com/helloworld in an ASP.NET MVC application. I hear outraged cries of “why is this necessary?” And “why are you not comfortable with normal human routing?” And I have an answer for this.

The fact is that our project moves to MVC with Web Forms, and contextual advertising has long been paid. Routing is a wonderful thing, however, given that the site is not only about landing pages, we cannot tweak it so cleverly that the usual MVC / Controller / Action links and our incredibly friendly URLs continue to work. The reason is simple: default routing ( "{controller}/{action}/{id}", new { controller = "Default", action = "Index", id = UrlParameter.Optional } ) assumes default values, therefore when accessing www.site.com/helloworld will try to throw us on the helloworld controller in the Action Index.
Sadness
Have to do something. Moreover, the option of creating a controller for each link clearly does not suit us - their darkness, and in general this is pornography, right?

We will go another way. ©

')

Concept


We will hang the handler on the routing, which will check the reference to the frugality - for this we use regular expressions.
If the link MVC-shnaya - just continue without making any extra gestures.
If the link does not look like a typical MVC link, we will try to look for a correspondence in the database - which controller and action to use.
If we don’t find anything, we’ll try to use standard routing and default values. Here already 404, so 404.


Model


image

That's so simple and straightforward. ContentID is a foreign key here, but if you want, you can use VARCHAR (MAX) and write content right here. We have such an implementation due to the fact that the content is divided into pieces (the main content, title, meta tag description ...) - SEO, you know.


Implementation


The basis of the implementation is this:
 routes.MapRoute( "Default", "{controller}/{action}/{id}", new { controller = "Default", action = "Index", id = UrlParameter.Optional } ).RouteHandler = new FriendlyUrlRouteHandler(); 

We attach a handler (the successor of MvcRouteHandler) to our router, which handles ready-made values. That is, first the routing mechanism will deal with the link, fill in the default values, then we will enter into the case and, if necessary, replace the values ​​with ours.

  public class FriendlyUrlRouteHandler : MvcRouteHandler { private static readonly Regex TypicalLink = new Regex("^.+/.+(/.*)?"); protected override IHttpHandler GetHttpHandler(RequestContext requestContext) { // Path  www.site.com/helloworld?id=1   /helloworld //      var url = requestContext.HttpContext.Request.Path.TrimStart('/'); if (!string.IsNullOrEmpty(url) && !TypicalLink.IsMatch(url)) { PageItem page = RedirectManager.GetPageByFriendlyUrl(url); if (page != null) { FillRequest(page.ControllerName, page.ActionName ?? "GetStatic", page.ID.ToString(), requestContext); } } return base.GetHttpHandler(requestContext); } /// <summary>  request-   ,    </summary> private static void FillRequest(string controller, string action, string id, RequestContext requestContext) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } requestContext.RouteData.Values["controller"] = controller; requestContext.RouteData.Values["action"] = action; requestContext.RouteData.Values["id"] = id; } } 


I use the record ID as a parameter, and when loading the page I use a view that consists of a join of several tables. If you do not need such problems, you can simply pass as a ContentID parameter, rather than the id of the entire entry.

The default action is GetStatic, which is defined as virtual in our base class for controllers. In a specific controller, it takes the text of the page from the database, and generates a page, taking into account the features and layouts of the section.
  public abstract class BaseController : Controller { /// <summary>     </summary> public virtual ActionResult GetStatic(int id) { return HttpNotFound(); } } 


And finally the redirect mechanism itself. Frankly, I do not remember why I took it to a separate class. On the other hand, why not.
  public static class RedirectManager { public static PageItem GetPageByFriendlyUrl(string friendlyUrl) { PageItem page = null; using (var cmd = new SqlCommand()) { cmd.Connection = new SqlConnection(/*YourConnectionString*/); cmd.CommandText = "select * from FriendlyUrl where FriendlyUrl = @FriendlyUrl"; cmd.Parameters.Add("@FriendlyUrl", SqlDbType.NVarChar).Value = friendlyUrl.TrimEnd('/'); cmd.Connection.Open(); using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)) { if (reader.Read()) { page = new PageItem { ID = (int) reader["Id"], ControllerName = (string) reader["ControllerName"], ActionName = (string) reader["ActionName"], FriendlyUrl = (string) reader["FriendlyUrl"], }; } } return page; } } } 



Result


In the end, we got exactly what we wanted. After filling in the database, we earned a redirect to the correct pages, and nothing changes in the address bar, and we get 200 OK without additional dancings.

Of course, the situation is quite atypical - in most cases, standard routing is more than enough: you can make a controller, for example, Static, which also takes data from the database from a URL. But if contextual advertising has already been paid for, or there is simply a wild desire to make links in which not a single extra slash, the described version, in my opinion, is very useful.

In the end, we set the task, and successfully coped with it.
In my opinion, this is good.

Source: https://habr.com/ru/post/132799/


All Articles