路由引擎将采用与所提供的 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;
}
If caseSwitch
is 1
,第二个块永远不会到达,因为第一个块捕获了它。
Route
类遵循类似的模式(在GetRouteData and GetVirtualPath方法)。他们可以返回 2 种状态:
- 一组路由值(或
VirtualPath
对象的情况下GetVirtualPath
)。这表明路由与请求匹配。
-
null
。这表明路由与请求不匹配。
在第一种情况下,MVC 使用路由生成的路由值来查找Action
方法。在这种情况下,RouteTable
不再进一步分析。
在第二种情况下,MVC 将检查下一个Route
in the RouteTable
查看它是否与请求匹配(内置行为与 URL 和约束匹配,但从技术上讲,您可以匹配 HTTP 请求中的任何内容)。再一次,该路由可以返回一组RouteValues
or null
取决于结果。
如果您尝试使用上面的 switch-case 语句,程序将无法编译。但是,如果您配置的路由never回报null
或返回一个RouteValues
在超过应有的情况下,程序可以编译,但会出现错误。
错误配置示例
这是我经常在 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 }
);
}
}
在这个例子中:
-
CustomRoute
将匹配长度为 1、2 或 3 段的任何 URL(请注意segment1
是必需的,因为它没有默认值)。
-
Default
将匹配长度为 0、1、2 或 3 段的任何 URL。
因此,如果应用程序传递 URL\Home\About
, the CustomRoute
将匹配,并提供以下内容RouteValues
to MVC:
segment1 = "Home"
controller = "MyController"
action = "About"
id = {}
这将使 MVC 寻找名为的操作About
在名为的控制器上MyControllerController
,如果不存在就会失败。这Default
在这种情况下,route 是无法访问的执行路径,因为即使它将匹配 2 段 URL,框架也不会给它机会,因为第一个匹配获胜。
修复配置
有关如何继续修复配置的选项有多种。但所有这些都取决于行为第一场比赛获胜然后路由就不会再查找了。
选项 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:添加 1 个或多个正则表达式约束
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:制作Required路段 + 使路段数量与现有路线不匹配
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
与之前一样,route 仅匹配具有 0、1、2 或 3 段的 URL。因此不存在不可达的执行路径。
选项 5:为自定义行为实现 RouteBase(或路由)
路由不支持开箱即用的任何操作(例如特定域或子域上的匹配)都可以通过以下方式完成实施你自己的RouteBase子类或路线子类。这也是了解路由如何/为何如此工作的最佳方式。
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 }
);
}
}
NOTE:这里真正的问题是大多数人认为他们的路线都应该像Default
路线。复制、粘贴,完成吧?错误的。
这种方法通常会出现两个问题:
- 几乎所有其他路线都应该至少有一个字面段(如果您喜欢这类事情,则应有一个约束)。
- 最合乎逻辑的行为通常是使其余路由具有required段。
另一个常见的误解是可选部分意味着您可以省略any段,但实际上您只能省略最右边的一个或多个段。
Microsoft 成功地使路由基于约定、可扩展且功能强大。他们未能使其直观易懂。几乎每个人第一次尝试都会失败(我知道我失败了!)。幸运的是,一旦你理解了它是如何工作的,这并不是很困难。