为什么要特别路线在asp.net mvc的共同路线之前首先映射 [英] Why map special routes first before common routes in asp.net mvc
问题描述
从WWW:...路由引擎将所提供的URL匹配的第一路径,并尝试在该路线使用路由值。因此,较少见的或更专门的路线应首先被添加到该表。 ,而更普遍的路线应该在以后添加...
为什么要我先映射专业化路线?有人可以给我一个例子,请在那里我可以看到首先映射常见的途径的失败?
谢谢!
路由引擎会提供的URL匹配的第一条路线,并试图在该路由使用路由值。
块引用>为什么出现这种情况的原因是因为
RouteTable
用于像一个开关case语句。图片如下:INT caseSwitch = 1;
开关(caseSwitch)
{
情况1:
Console.WriteLine(案例1);
打破;
情况1:
Console.WriteLine(第二个案例1);
打破;
默认:
Console.WriteLine(默认情况下);
打破;
}如果
caseSwitch
是1
,永远达不到第二块,因为第一个块捕获它。
路线
类遵循类似的模式(均<一个href=\"https://msdn.microsoft.com/en-us/library/system.web.routing.routebase.getroutedata(v=vs.110).aspx\"相对=nofollow>GetRouteData code> 和<一个href=\"https://msdn.microsoft.com/en-us/library/system.web.routing.routebase.getvirtualpath(v=vs.110).aspx\"相对=nofollow>
GetVirtualPath
方法)。他们可以返回2种状态:
- 系统设定的路线值(或
VirtualPath
对象GetVirtualPath
的情况下)。这表示路由相匹配的要求。空
。这表示路由不匹配的要求。在第一种情况下,MVC使用由路径产生的路由值来查找
动作
方法。在这种情况下,在RouteTable
未分析任何进一步的在第二种情况下,MVC将检查下一
路线
在RouteTable
,看它是否与匹配请求(内置行为URL和约束相匹配,但在技术上可以匹配在HTTP请求的任何东西)。再一次,这条路线可以返回一组RouteValues
或空
根据结果。错误配置示例
下面是我经常看到StackOverflow上张贴的经典例子(或它的某些变种):
公共类RouteConfig
{
公共静态无效的RegisterRoutes(RouteCollection路线)
{
routes.IgnoreRoute({}资源个.axd / {*} PATHINFO); routes.MapRoute(
名称:CustomRoute
网址:{SEGMENT1} / {行动} / {ID}
默认:新{控制器=myController的行动=索引,ID = UrlParameter.Optional}
); routes.MapRoute(
名称:默认,
网址:{控制器} / {行动} / {ID}
默认:新{控制器=家,行动=索引,ID = UrlParameter.Optional}
);
}
}在这个例子:
CustomRoute
将匹配要么是1,2,或3段长度的任何URL(请注意,SEGMENT1
是必需的,因为它没有默认值)。默认
将匹配0,1,2,3段,长度任意URL。因此,如果应用程序是通过网址
\\首页\\关于
的CustomRoute
将匹配和供应以下RouteValues
来MVC:
SEGMENT1 =家
控制器=myController的
=动作关于
ID = {}
这将使MVC查找名为名为控制器
MyControllerController
上一个动作关于
,如果将失败不存在。在默认
路线是在这种情况下,无法到达的执行路径,因为即使它会匹配2段URL,该框架将不给它机会,因为第一场比赛的胜利杀青配置
有关于如何继续以固定的配置的几个选项。但他们都依赖于行为的第一场比赛胜,然后路由不继续看。
选项1:添加一个或多个文字段
公共类RouteConfig
{
公共静态无效的RegisterRoutes(RouteCollection路线)
{
routes.IgnoreRoute({}资源个.axd / {*} PATHINFO); routes.MapRoute(
名称:CustomRoute
网址:用户/ {}动作/(编号), //注意,留下`action`和`id`出来的默认值
//使他们必需的,因此该URL将只匹配,如果3
//段随附的自定义或自定义开始时。
//例:自定义/细节/ 343
默认:新{控制器=myController的}
); routes.MapRoute(
名称:默认,
网址:{控制器} / {行动} / {ID}
默认:新{控制器=家,行动=索引,ID = UrlParameter.Optional}
);
}
}选项2:加1以上的正则表达式约束
公共类RouteConfig
{
公共静态无效的RegisterRoutes(RouteCollection路线)
{
routes.IgnoreRoute({}资源个.axd / {*} PATHINFO); routes.MapRoute(
名称:CustomRoute
网址:{SEGMENT1} / {segment2} / {行动} / {ID}
默认:新{控制器=myController的},
限制:新{SEGMENT1 = @房子|汽车|巴士}
); routes.MapRoute(
名称:默认,
网址:{控制器} / {行动} / {ID}
默认:新{控制器=家,行动=索引,ID = UrlParameter.Optional}
);
}
}选项3:加1或多个自定义约束
公共类CorrectDateConstraint:IRouteConstraint
{
公共BOOL匹配(HttpContextBase HttpContext的,路由路径,字符串参数名称,RouteValueDictionary价值,RouteDirection routeDirection)
{
VAR年=值[年作为字符串;
VAR月=值[月作为字符串;
VAR天=值[日]作为字符串; 日期时间theDate;
返回DateTime.TryParse(年+ - +月+ - +天,System.Globalization.CultureInfo.InvariantCulture,DateTimeStyles.None,出theDate);
}
}公共类RouteConfig
{
公共静态无效的RegisterRoutes(RouteCollection路线)
{
routes.IgnoreRoute({}资源个.axd / {*} PATHINFO); routes.MapRoute(
名称:CustomRoute
网址:{一年} / {月} / {天} / {}条,
默认:新{控制器=新闻,行动=ArticleDetails},
限制:新的一年{=新CorrectDateConstraint()}
); routes.MapRoute(
名称:默认,
网址:{控制器} / {行动} / {ID}
默认:新{控制器=家,行动=索引,ID = UrlParameter.Optional}
);
}
}选项4:制作的必需的段+制作的段数不匹配现有路线
公共类RouteConfig
{
公共静态无效的RegisterRoutes(RouteCollection路线)
{
routes.IgnoreRoute({}资源个.axd / {*} PATHINFO); routes.MapRoute(
名称:CustomRoute
网址:{SEGMENT1} / {segment2} / {行动} / {ID}
默认:新{控制器=myController的}
); routes.MapRoute(
名称:默认,
网址:{控制器} / {行动} / {ID}
默认:新{控制器=家,行动=索引,ID = UrlParameter.Optional}
);
}
}在上述情况下,在
CustomRoute
将仅匹配4段的URL(注意,这些可以是任何值)。因为只有在默认
路由匹配的网址0,1,2,或3段。因此,不存在不可达执行路径。选项5:实现RouteBase(或路由)的自定义行为
这是路由不支持开箱即用(如在一个特定的域或子域匹配),任何东西都可以通过<一做href=\"http://stackoverflow.com/questions/31934144/multiple-levels-in-mvc-custom-routing/31958586#31958586\">implementing你自己的
RouteBase
子或路由子类。这也是理解如何/为什么路由工作方式是这样的最佳方式。公共类SubdomainRoute:路线
{
公共SubdomainRoute(字符串URL):基地(网址,新MvcRouteHandler()){} 公众覆盖的RouteData GetRouteData(HttpContextBase的HttpContext)
{
VAR的RouteData = base.GetRouteData(HttpContext的);
如果(的RouteData == NULL)返回NULL; //只看子,如果这条路摆在首位匹配。
串子= httpContext.Request.Params [子域]; //指定为查询参数的子域接受precedence过的主机名。
如果(子域== NULL){
字符串主机= httpContext.Request.Headers [主机];
INT指数= host.IndexOf('。');
如果(索引&gt; = 0)
子域= host.Substring(0,索引);
}
如果(子!= NULL)
routeData.Values [子域] =子;
返回的RouteData;
} 公众覆盖VirtualPathData GetVirtualPath(RequestContext的的RequestContext,RouteValueDictionary值)
{
反对subdomainParam = requestContext.HttpContext.Request.Params [子域];
如果(subdomainParam!= NULL)
值[子域] = subdomainParam;
返回base.GetVirtualPath(RequestContext的,值);
}
}这个类是从借来的:<一href=\"http://stackoverflow.com/questions/278668/is-it-possible-to-make-an-asp-net-mvc-route-based-on-a-subdomain#15287579\">Is有可能使基于子域的ASP.NET MVC路线?
公共类RouteConfig
{
公共静态无效的RegisterRoutes(RouteCollection路线)
{
routes.IgnoreRoute({}资源个.axd / {*} PATHINFO); routes.Add(新SubdomainRoute(URL:某处/独一无二)); routes.MapRoute(
名称:默认,
网址:{控制器} / {行动} / {ID}
默认:新{控制器=家,行动=索引,ID = UrlParameter.Optional}
);
}
}
注意:这里真正的疑难杂症是,大多数人认为他们的路线都应该看起来像
默认
路线。复制,粘贴,做,对不对?错了。
有2的问题,这种方法通常出现的:
- pretty多少每隔路线应该至少有一个文字段(或约束,如果你是到诸如此类的事情)。
- 最合乎逻辑的行为通常是为了使线路具有与其他的需要的段。
另一种常见的误解是可选的细分意味着你可以忽略的任何的段,但在现实中你只能离开了最右边的段或部分。
微软成功地使基于约定的路由,可扩展,功能强大。他们未能使其直观明了。几乎每个人都失败他们第一次尝试(我知道我做到了!)。幸运的是,一旦你了解它是如何工作是不是很困难的。
块引用>From the www:"...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" ?
Thanks!
解决方案The routing engine will take the first route that matches the supplied URL and attempt to use the route values in that route.
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; }
If
caseSwitch
is1
, the second block is never reached because the first block catches it.
Route
classes follow a similar pattern (in both theGetRouteData
andGetVirtualPath
methods). They can return 2 states:
- 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.In the first case, MVC uses the route values that are produced by the route to lookup the
Action
method. In this case, theRouteTable
is not analyzed any further.In the second case, MVC will check the next
Route
in theRouteTable
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 ofRouteValues
ornull
depending on the result.Misconfiguration Example
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 } ); } }
In this example:
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.Therefore, if the application is passed the URL
\Home\About
, theCustomRoute
will match, and supply the followingRouteValues
to MVC:
segment1 = "Home"
controller = "MyController"
action = "About"
id = {}
This will make MVC look for an action named
About
on a controller namedMyControllerController
, which will fail if it doesn't exist. TheDefault
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.Fixing the Configuration
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.
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: Add 1 or more RegEx Constraints
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 } ); } }
Option 3: Add 1 or more Custom Constraints
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 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, the
CustomRoute
will only match a URL with 4 segments (note these can be any values). TheDefault
route as before only matches URLs with 0, 1, 2, or 3 segments. Therefore there is no unreachable execution path.Option 5: Implement RouteBase (or Route) for Custom Behavior
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 } ); } }
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 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屋!