创建测试工程
分别在vs2013和vs2015中创建mvc项目,并创建First、Second、Three三个Area,每个Area下面创建一个HomeController和Index视图。修改RouteConfig.cs中的路由注册方法,添加命名空间
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }, namespaces: new[] { "RouteDebuggerMvc5Demo.Controllers" } ); }
修改三个Area的路由注册方法,添加命名空间:
public class FirstAreaRegistration : AreaRegistration { public override string AreaName { get { return "First"; } } public override void RegisterArea(AreaRegistrationContext context) { context.MapRoute( "First_default", "First/{controller}/{action}/{id}", new { action = "Index", id = UrlParameter.Optional }, namespaces: new[] { "RouteDebuggerMvc5Demo.Areas.First.Controllers" } ); } }
使用nuget添加RouteDebugger引用,在Web.config中配置启用 <add key="RouteDebugger:Enabled" value="true" />,运行起来:
VS2013中升序
Matches Current Request | Url | Defaults | Constraints | DataTokens |
---|---|---|---|---|
False | First/{controller}/{action}/{id} | action = Index, id = UrlParameter.Optional | (empty) | Namespaces = RouteDebuggerMvc5Demo.Areas.First.Controllers, area = First, UseNamespaceFallback = False |
False | Second/{controller}/{action}/{id} | action = Index, id = UrlParameter.Optional | (empty) | Namespaces = RouteDebuggerMvc5Demo.Areas.Second.Controllers, area = Second, UseNamespaceFallback = False |
False | Three/{controller}/{action}/{id} | action = Index, id = UrlParameter.Optional | (empty) | Namespaces = RouteDebuggerMvc5Demo.Areas.Three.Controllers, area = Three, UseNamespaceFallback = False |
False | {resource}.axd/{*pathInfo} | (null) | (empty) | (null) |
True | {controller}/{action}/{id} | controller = Home, action = Index, id = UrlParameter.Optional | (empty) | Namespaces = RouteDebuggerMvc5Demo.Controllers |
True | {*catchall} | (null) | (null) | (null) |
VS2015中降序
Matches Current Request | Url | Defaults | Constraints | DataTokens |
---|---|---|---|---|
False | Three/{controller}/{action}/{id} | action = Index, id = UrlParameter.Optional | (empty) | Namespaces = RouteDebuggerDemo.Areas.Three.Controllers, RouteDebuggerDemo, area = Three, UseNamespaceFallback = False |
False | Second/{controller}/{action}/{id} | action = Index, id = UrlParameter.Optional | (empty) | Namespaces = RouteDebuggerDemo.Areas.Second.Controllers, RouteDebuggerDemo, area = Second, UseNamespaceFallback = False |
False | First/{controller}/{action}/{id} | action = Index, id = UrlParameter.Optional | (empty) | Namespaces = RouteDebuggerDemo.Areas.First.Controllers, RouteDebuggerDemo, area = First, UseNamespaceFallback = False |
False | {resource}.axd/{*pathInfo} | (null) | (empty) | (null) |
True | {controller}/{action}/{id} | controller = Home, action = Index, id = UrlParameter.Optional | (empty) | Namespaces = RouteDebuggerDemo.Controllers, RouteDebuggerDemo |
True | {*catchall} | (null) | (null) | (null) |
如果在VS2013中的某个路由注册有问题,一直没有显现出来,升级到VS2015后出现了404就有可能是路由匹配顺序导致的。问题参考:
手动修改路由顺序
默认在 *.csproj文件中的路由顺序是
<Compile Include="Areas\First\Controllers\HomeController.cs" />
<Compile Include="Areas\First\FirstAreaRegistration.cs" /> <Compile Include="Areas\Second\Controllers\HomeController.cs" /> <Compile Include="Areas\Second\SecondAreaRegistration.cs" /><Compile Include="Areas\Three\Controllers\HomeController.cs" /> <Compile Include="Areas\Three\ThreeAreaRegistration.cs" />
修改成
<Compile Include="Areas\First\Controllers\HomeController.cs" />
<Compile Include="Areas\First\FirstAreaRegistration.cs" /> <Compile Include="Areas\Three\Controllers\HomeController.cs" /> <Compile Include="Areas\Three\ThreeAreaRegistration.cs" /> <Compile Include="Areas\Second\Controllers\HomeController.cs" /> <Compile Include="Areas\Second\SecondAreaRegistration.cs" />
重新访问页面查看路由,顺序已经改变了(对比上面VS2015里的顺序),可以通过这种方法把最常用的路由调到最前面,提高匹配速度。经测试添加新的Area后,调整的路由顺序不会变回去,可以放心使用。
参考链接:
RouteDebugger 简介
作者地址:,代码结构图如下:
程序集属性[assembly: PreApplicationStartMethod(typeof(PreApplicationStart), "Start")] 使PreApplicationStart的Start方法在Application_Start方法之前执行,
public static void Start() { bool flag = Convert.ToBoolean(ConfigurationManager.AppSettings["RouteDebugger:Enabled"]); if (flag) { DynamicModuleUtility.RegisterModule(typeof(RouteDebuggerHttpModule)); } }
最后处理请求的是DebugHttpHandler里的ProcessRequest方法把匹配的路由信息获取出来,代码如下:
//处理请求 public void ProcessRequest(HttpContext context) { HttpRequest request = context.Request; if (!this.IsRoutedRequest(request) || context.Response.ContentType == null || !context.Response.ContentType.Equals("text/html", StringComparison.OrdinalIgnoreCase)) { return; } string text = string.Empty; RequestContext requestContext = request.RequestContext; if (request.QueryString.Count > 0) { RouteValueDictionary routeValueDictionary = new RouteValueDictionary(); foreach (string text2 in request.QueryString.Keys) { if (text2 != null) { routeValueDictionary.Add(text2, request.QueryString[text2]); } } VirtualPathData virtualPath = RouteTable.Routes.GetVirtualPath(requestContext, routeValueDictionary); if (virtualPath != null) { text = ": "; text = text + "" + virtualPath.VirtualPath + ""; Route route = virtualPath.Route as Route; if (route != null) { text = text + " using the route \"" + route.Url + "\"
"; } } } string text3 = string.Empty; RouteData routeData = requestContext.RouteData; RouteValueDictionary values = routeData.Values; RouteBase route2 = routeData.Route; string text4 = string.Empty; using (RouteTable.Routes.GetReadLock()) { foreach (RouteBase current in RouteTable.Routes) { //查询请求与路由是否匹配 bool flag = current.GetRouteData(requestContext.HttpContext) != null; string isMatchRequest = string.Format(" {1}", DebugHttpHandler.BoolStyle(flag), flag); string url = "n/a"; string defaults = "n/a"; string constraints = "n/a"; string dataTokens = "n/a"; Route route3 = this.CastRoute(current); if (route3 != null) { url = route3.Url; defaults = DebugHttpHandler.FormatDictionary(route3.Defaults); constraints = DebugHttpHandler.FormatDictionary(route3.Constraints); dataTokens = DebugHttpHandler.FormatDictionary(route3.DataTokens); } text4 += string.Format("{0}{1}{2}{3}{4}", new object[] { isMatchRequest, url, defaults, constraints, dataTokens }); } } string text10 = "n/a"; string text11 = ""; if (!(route2 is DebugRoute)) { foreach (string current2 in values.Keys) { text3 += string.Format("\t{0}{1} ", current2, values[current2]); } foreach (string current3 in routeData.DataTokens.Keys) { text11 += string.Format("\t{0}{1} ", current3, routeData.DataTokens[current3]); } Route route4 = route2 as Route; if (route4 != null) { text10 = route4.Url; } } else { text10 = string.Format(" NO MATCH!", DebugHttpHandler.BoolStyle(false)); } text3 = "text3"; text10 = "text10"; text4 = "text4"; text11 = "text11"; text = "text"; context.Response.Write(string.Format("\r\n\r\n \r\n", new object[] { text3, text10, text4, request.AppRelativeCurrentExecutionFilePath, text11, text })); }
\r\nRoute Debugger
\r\n\r\n\r\n\r\n Type in a url in the address bar to see which defined routes match it. \r\n A {
{*catchall}} route is added to the list of routes automatically in \r\n case none of your routes match.\r\n \r\n\r\n To generate URLs using routing, supply route values via the query string. example:
\r\nhttp://localhost:14230/?id=123
\r\n: {1}
\r\n {5}\r\n\r\n\r\n\r\n
\r\nRoute Data \r\n\r\n {0}\r\n Key Value \r\n\r\n\r\n
\r\nData Tokens \r\n\r\n {4}\r\n Key Value
\r\n\r\n
\r\nAll Routes \r\n\r\n \r\n {2}\r\nMatches Current Request \r\nUrl \r\nDefaults \r\nConstraints \r\nDataTokens \r\n
\r\nCurrent Request Info
\r\n\r\n AppRelativeCurrentExecutionFilePath is the portion of the request that Routing acts on.\r\n
\r\nAppRelativeCurrentExecutionFilePath: {3}
\r\n
注:RouteDebugger的源码是通过ILSpy反编译的。
结束语
抽出时间去验证路由顺序是因为两个错误引起的:
1.升级vs2013的mvc4解决方案到vs2015后所有的Area路由失效,返回404错误。以为是vs2015对mvc4的支持不够(刚出来是对mvc4是有点问题),就撤销升级返回vs2013了。
2.在vs2013中添加了一个新的Area,排在了以前的所有路由之后,出现404错误。通过RouteDebugger发现是路由的顺序问题,所以想到上面那个问题应该也是顺序问题,不应该是vs2015的缺陷。
希望这篇文章可以帮助你避免遇到同样的问题,觉着有用就推荐一下吧