为什么要在asp.net mvc中的常用路线之前先映射特殊路线?

从www:“…路由引擎将采取与提供的URL相匹配的第一个路由,并尝试使用该路由中的路由值,因此,应该首先添加不常见或更专用的路由,而更多一般路线应该稍后添加…“

为什么我应该先映射专门的路线? 有人可以举一个例子,我可以看到“地图通用路线第一”的失败吗?

谢谢!

路由引擎将采用与提供的URL匹配的第一个路由,并尝试使用该路由中的路由值。

发生这种情况的原因是因为RouteTable像switch-case语句一样使用。 图片如下:

 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; } 

如果caseSwitch1 ,则第二个块永远不会到达,因为第一个块捕获它。

Route类遵循类似的模式(在GetRouteDataGetVirtualPath方法中)。 他们可以返回2个状态:

  1. 一组路由值(或GetVirtualPath情况下的VirtualPath对象)。 这表示路线匹配请求。
  2. null 。 这表示路线与请求不符。

在第一种情况下,MVC使用路由产生的路由值来查找Action方法。 在这种情况下, RouteTable不会被进一步分析。

在第二种情况下,MVC将检查RouteTable的下一个Route是否与请求匹配(内置行为与URL和约束匹配,但技术上可以匹配HTTP请求中的任何内容)。 再次,该路由可以返回一组RouteValuesnull取决于结果。

如果您尝试使用上面的switch-case语句,程序将不会编译。 然而,如果你configuration了一个从不返回null的路由,或者在比它应该更多的情况下返回一个RouteValues对象,那么这个程序将会被编译,但是会不正常。

错误configuration示例

下面是我经常看到的在StackOverflow(或其一些变体)上发布的经典示例:

 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 } ); } } 

在这个例子中:

  1. CustomRoute将匹配任何长度为1,2或3段的URL(请注意,段1是必需的,因为它没有默认值)。
  2. Default将匹配任何长度为0,1,2或3段的URL。

因此,如果应用程序通过URL \Home\About ,则CustomRoute将匹配,并将以下RouteValues给MVC:

  1. segment1 = "Home"
  2. controller = "MyController"
  3. action = "About"
  4. id = {}

这将使MVC在一个名为MyControllerController的控制器上寻找名为About的动作,如果它不存在,将会失败。 在这种情况下, Default路由是无法访问的执行path,因为即使它将匹配2段的URL,框架也不会给它机会,因为第一场比赛胜利了。

修复configuration

关于如何继续修复configuration有几个选项。 但是所有这些都取决于第一场比赛获胜的行为,然后路线就不会再进一步​​了。

选项1:添加一个或多个文字段

 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 } ); } } 

选项2:添加一个或多个RegEx约束

 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" }, constraints: new { segment1 = @"house|car|bus" } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } 

选项3:添加一个或多个自定义约束

 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 } ); } } 

选项4:制作所需的分段+使分段数量与现有路线不匹配

 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 } ); } } 

在上面的例子中, CustomRoute只会匹配一个4段的URL(注意这些值可以是任意值)。 如前所述的Default路由只匹配0,1,2或3段的URL。 因此没有不可达的执行path。

选项5:为自定义行为实施RouteBase(或Route)

路由不支持开箱即用的任何事情(例如匹配特定的域或子域)可以通过实现您自己的RouteBase子类或Route子类来完成。 这也是理解路由如何工作的最好方式。

 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); } } 

这个类是从以下借来的: 是否有可能使一个基于子域的ASP.NET MVC路线?

 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 } ); } } 

注意:这里真正的问题是,大多数人认为他们的路线应该都像Default路线。 复制,粘贴,完成,对不对? 错误。

这种方法通常会出现两个问题:

  1. 几乎每一个其他的路线应该至less有一个文字段(或者如果你是这样的事情约束)。
  2. 最合乎逻辑的行为通常是让其余的路线具有所需的细分。

另一个常见的误解是可选的部分意味着你可以忽略任何部分,但实际上,你只能离开最右边的部分或部分。

微软成功地使基于约定的,可扩展的和强大的。 他们没有使直观的理解。 几乎每个人都第一次尝试失败(我知道我做到了!)。 幸运的是,一旦你了解它是如何工作的,这不是很困难。