为什么在asp.net mvc中先映射特殊路由,然后再映射普通路由? [英] Why map special routes first before common routes in asp.net mvc?
问题描述
从www:
...路由引擎将采用与所提供的URL匹配的第一条路由,并尝试使用该路由中的路由值.因此,应该先将不常见或更专用的路由添加到表中,而稍后再添加更通用的路由...
...The routing engine will take the first route that matches the supplied URL and attempt to use the route values in that route. Therefore, less common or more specialized routes should be added to the table first, while more general routes should be added later on...
为什么我应该先绘制专业路线?有人可以给我举个例子,在那里我可以看到首先将公共路线映射"失败了吗?
Why should I map specialized routes first? Someone can give me an example please where I can see the failing of "map common route first" ?
推荐答案
路由引擎将采用与所提供的URL匹配的第一条路由,并尝试使用该路由中的路由值.
The routing engine will take the first route that matches the supplied URL and attempt to use the route values in that route.
之所以发生这种情况,是因为RouteTable
的用法类似于switch-case语句.图片如下:
The reason why this happens is because the RouteTable
is used like a switch-case statement. Picture 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;
}
如果caseSwitch
是1
,则永远不会到达第二个块,因为第一个块捕获了它.
If caseSwitch
is 1
, the second block is never reached because the first block catches it.
Route
类遵循类似的模式(在 GetVirtualPath
方法).他们可以返回2个状态:
Route
classes follow a similar pattern (in both the GetRouteData
and GetVirtualPath
methods). They can return 2 states:
- 一组路由值(在
GetVirtualPath
情况下为VirtualPath
对象).这表明路由已符合请求. -
null
.这表明路线与请求不匹配.
- A set of route values (or a
VirtualPath
object in the case ofGetVirtualPath
). This indicates the route matched the request. null
. This indicates the route did not match the request.
在第一种情况下,MVC使用由路由产生的路由值来查找Action
方法.在这种情况下,RouteTable
将不再进行分析.
In the first case, MVC uses the route values that are produced by the route to lookup the Action
method. In this case, the RouteTable
is not analyzed any further.
在第二种情况下,MVC将检查RouteTable
中的下一个Route
是否与请求匹配(内置行为与URL和约束匹配,但是从技术上讲,您可以在HTTP请求中匹配任何内容).再一次,该路由可以根据结果返回一组RouteValues
或null
.
In the second case, MVC will check the next Route
in the RouteTable
to see if it matches with the request (the built in behavior matches the URL and constraints, but technically you can match anything in the HTTP request). And once again, that route can return a set of RouteValues
or null
depending on the result.
如果您尝试使用上述的switch-case语句,则该程序将无法编译.但是,如果您配置的路由从不不会返回null
或返回RouteValues
对象的情况超出了应有的情况,则该程序将编译但行为不当.
If you try to use a switch-case statement as above, the program won't 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 misbehave.
这是我经常在StackOverflow上发布的经典示例(或其一些变体):
Here is the classic example that I frequently see posted on StackOverflow (or some variant of it):
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 }
);
}
}
在此示例中:
-
CustomRoute
将匹配任何长度为1、2或3段的URL(请注意,由于segment1
没有默认值,因此是必需的). -
Default
将匹配任何长度为0、1、2或3段的URL.
CustomRoute
will match any URL that is either 1, 2, or 3 segments in length (note thatsegment1
is required because it has no default value).Default
will match any URL that is 0, 1, 2, or 3 segments in length.
因此,如果将URL \Home\About
传递给应用程序,则CustomRoute
将匹配,并将以下RouteValues
提供给MVC:
Therefore, if the application is passed the URL \Home\About
, the CustomRoute
will match, and supply the following RouteValues
to MVC:
-
segment1 = "Home"
-
controller = "MyController"
-
action = "About"
-
id = {}
segment1 = "Home"
controller = "MyController"
action = "About"
id = {}
这将使MVC在名为MyControllerController
的控制器上寻找名为About
的动作,如果该动作不存在,它将失败.在这种情况下,Default
路由是无法到达的执行路径,因为即使它匹配2段URL,框架也不会给它机会,因为第一个匹配获胜.
This will make MVC look for an action named About
on a controller named MyControllerController
, which will fail if it doesn't exist. The Default
route is an unreachable execution path in this case because even though it will match a 2-segment URL, the framework will not give it the opportunity to because the first match wins.
关于如何继续修复配置,有几个选项.但是所有这些都取决于首个比赛获胜的行为,然后路由看起来就不会再变了.
There are several options on how to proceed to fix the configuration. But all of them depend on the behavior that the first match wins and then routing won't look any further.
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:添加1个或多个RegEx约束
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 }
);
}
}
选项3:添加1个或多个自定义约束
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:细分为必填 +设置细分数与现有路线不匹配
Option 4: Make 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 }
);
}
}
在上述情况下,CustomRoute
将仅与具有4个段的URL匹配(请注意,这些值可以是任何值).以前的Default
路由仅匹配具有0、1、2或3段的URL.因此,没有不可达的执行路径.
In the above case, the CustomRoute
will only match a URL with 4 segments (note these can be any values). The Default
route as before only matches URLs with 0, 1, 2, or 3 segments. Therefore there is no unreachable execution path.
Anything that routing doesn't support out of the box (such as matching on a specific domain or subdomain) can be done by implementing 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 }
);
}
}
注意:这里的真正陷阱是,大多数人都认为他们的路线都应该看起来像
Default
路线.复制,粘贴,完成,对吗?不对.
NOTE: The real gotcha here is that most people assume that their routes should all look like the
Default
route. Copy, paste, done, right? Wrong.
这种方法通常会出现两个问题:
There are 2 problems that commonly arise with this approach:
- 几乎所有其他路线都应至少具有一个文字部分(如果您喜欢这种东西,则应具有约束条件.)
- 最合乎逻辑的行为通常是使其余路由具有必需个分段.
- Pretty much every other route should have at least one literal segment (or a constraint if you are into that sort of thing).
- The most logical behavior is usually to make the rest of the routes have required segments.
另一个常见的误解是可选段意味着您可以忽略任何任何段,但实际上,您只能忽略最右边的一个或多个段.
Another common misconception is that optional segments mean you can leave out any segment, but in reality you can only leave off the right-most segment or segments.
Microsoft成功地使基于路由约定的,可扩展的和强大的路由.他们未能使其直观易懂.几乎每个人都第一次尝试失败(我知道我做过!).幸运的是,一旦您了解了它是如何工作的,这并不是很难.
Microsoft succeeded in making routing convention-based, extensible, and powerful. They failed in making it intuitive to understand. Virtually everyone fails the first time they try it (I know I did!). Fortunately, once you understand how it works it is not very difficult.
这篇关于为什么在asp.net mvc中先映射特殊路由,然后再映射普通路由?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!