Why create custom routes before regular routes in asp.net mvc?

From www:

... The routing engine will take the first route matching the specified URL and try to use the route values ​​in that route. Therefore, you should first add less general or more specialized routes to the table, while more general routes should be added later ...

Why do I need to map specialized routes first? Can someone give me an example, please, where can I see the failure of the “general map route first”?

Thanks!

+2
source share
1 answer

The routing engine will execute the first route corresponding to the specified URL and try to use the route values ​​in this route.

The reason this happens is because RouteTable used as a switch-case statement. Imagine the following:

 int caseSwitch = 1; switch (caseSwitch) { case 1: Console.WriteLine("Case 1"); break; case 1: Console.WriteLine("Second Case 1"); break; default: Console.WriteLine("Default case"); break; } 

If caseSwitch is 1 , the second block is never reached, because the first block catches it.

Classes

GetRouteData follow a similar pattern (in GetRouteData and GetVirtualPath ). They can return 2 states:

  • A set of route values ​​(or a VirtualPath object in the case of GetVirtualPath ). This indicates that the route matches the request.
  • null This indicates that the route does not match the request.

In the first case, MVC uses the route values ​​that are created by the route to find the Action method. In this case, the RouteTable no longer parsed.

In the second case, MVC will check the next Route in the RouteTable to see if it matches the request (the built-in behavior matches the URL and restrictions, but technically you can match anything in the HTTP request). Again, this route can return a set of RouteValues or null depending on the result.

If you try to use the switch-case statement as described above, the program will not compile. However, if you configure a route that never returns null or returns a RouteValues object in more cases than it should, the program will compile, but will not work correctly.

Mismatch Example

Here is a classic example that I often see in StackOverflow (or its variant):

 public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "CustomRoute", url: "{segment1}/{action}/{id}", defaults: new { controller = "MyController", action = "Index", id = UrlParameter.Optional } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } 

In this example:

  • CustomRoute will match any URL that has a length of 1, 2, or 3 segments (note that segment1 is required since it does not have a default value).
  • Default will match any URL that is 0, 1, 2, or 3 segments long.

Therefore, if the URL \Home\About is passed to the application, CustomRoute will match, and put the following RouteValues in MVC:

  • segment1 = "Home"
  • controller = "MyController"
  • action = "About"
  • id = {}

This causes MVC to look for an action called About on a controller named MyControllerController , which will not work if it does not exist. The Default route is an unattainable execution path in this case, because although it will correspond to a URL with two segments, the structure will not give it an opportunity, because the first match wins.

Commit configuration

There are several options for customizing the fix. But they all depend on the behavior that the first match wins , and then the routing will no longer look.

Option 1: add one or more literal segments

 public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "CustomRoute", url: "Custom/{action}/{id}", // Note, leaving `action` and `id` out of the defaults // makes them required, so the URL will only match if 3 // segments are supplied begining with Custom or custom. // Example: Custom/Details/343 defaults: new { controller = "MyController" } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } 

Option 2: Adding 1 or More RegEx Restrictions

 public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "CustomRoute", url: "{segment1}/{action}/{id}", defaults: new { controller = "MyController", action = "Index", id = UrlParameter.Optional }, constraints: new { segment1 = @"house|car|bus" } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } 

Option 3: Add 1 or more custom restrictions

 public class CorrectDateConstraint : IRouteConstraint { public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { var year = values["year"] as string; var month = values["month"] as string; var day = values["day"] as string; DateTime theDate; return DateTime.TryParse(year + "-" + month + "-" + day, System.Globalization.CultureInfo.InvariantCulture, DateTimeStyles.None, out theDate); } } public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "CustomRoute", url: "{year}/{month}/{day}/{article}", defaults: new { controller = "News", action = "ArticleDetails" }, constraints: new { year = new CorrectDateConstraint() } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } 

Option 4: Make the required segments + Make the number of segments not match existing routes

 public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "CustomRoute", url: "{segment1}/{segment2}/{action}/{id}", defaults: new { controller = "MyController" } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } 

In the above case, CustomRoute will only match a URL with 4 segments (note that these can be any values). The Default route still only matches URLs with 0, 1, 2, or 3 segments. Therefore, there is no unattainable execution path.

Option 5: Implement RouteBase (or Route) for User Behavior

Everything that routing is not supported out of the box (for example, matching by a specific domain or subdomain) can be performed using your own RouteBase subclass or Route subclass. It is also the best way to understand how / why routing works the way it does.

 public class SubdomainRoute : Route { public SubdomainRoute(string url) : base(url, new MvcRouteHandler()) {} public override RouteData GetRouteData(HttpContextBase httpContext) { var routeData = base.GetRouteData(httpContext); if (routeData == null) return null; // Only look at the subdomain if this route matches in the first place. string subdomain = httpContext.Request.Params["subdomain"]; // A subdomain specified as a query parameter takes precedence over the hostname. if (subdomain == null) { string host = httpContext.Request.Headers["Host"]; int index = host.IndexOf('.'); if (index >= 0) subdomain = host.Substring(0, index); } if (subdomain != null) routeData.Values["subdomain"] = subdomain; return routeData; } public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { object subdomainParam = requestContext.HttpContext.Request.Params["subdomain"]; if (subdomainParam != null) values["subdomain"] = subdomainParam; return base.GetVirtualPath(requestContext, values); } } 

This class was borrowed from: Is it possible to make an ASP.NET MVC route based on a subdomain?

 public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.Add(new SubdomainRoute(url: "somewhere/unique")); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } 

NOTE: The real information here is that most people think their routes should look like the Default route. Copy, paste, do, right? Wrong.

There are usually two problems with this approach:

  • Virtually every other route should have at least one literal segment (or restriction if you are doing this).
  • The most logical behavior usually is for the rest of the routes to have segments.

Another common misconception is that optional segments mean that you can leave a segment, but in fact you can leave only the rightmost segment or segments.

Microsoft has succeeded in creating protocol-based routing, extensibility, and power. They failed to understand intuition. Almost everyone fails the first time they try to do it (I know I did!). Fortunately, once you understand how it works, it is not very difficult.

+12
source

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


All Articles