这个礼拜在维护公司一款老的MVC项目时,突然发现在访问非首页部分以外的其他控制器时,总是返回404,无法匹配到正确的Controller。
根据项目主要结构还原的Demo大致如下:
项目中一共包含三个区域,其中Admin和Wap的路由规则大致如下:
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
context.MapRoute(
"Wap_default",
"Wap/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
只有当输入完整的URL时(例如 wap/home/index/1),才会正确的匹配到相应的Controller,否则返回的都是404。而Web下的Controller则始终都可以访问,因此推测是路由规则的问题。
打开Web下的AreaRegistration类,发现有这么一段代码:
context.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
404的原因显然是因为匹配到了这条路由规则。例如,当请求 wap/home/index 时,将wap当做controller,home当做action,index当做id,在Web区域下自然无法匹配到相应的controller。
尝试把这条路由注释以后,wap/home/index果然可以适配到相应的action了。
这条路由的用途也很明显,就是希望提供一个默认的匹配规则,把它注释掉肯定是不合适的。而如果直接修改路由规则,又很可能对项目中其他访问了Web下controller的代码产生影响。
路由的匹配是根据其注册时的先后顺序进行的,因此只要降低这条默认规则的“优先级”即可。
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
打开Global.asax文件,可以发现注册区域路由的方法(AreaRegistration.RegisterAllAreas)位于RouteConfig.RegisterRoutes(RouteTable.Routes)之前。
因此,这里可以将Default移到RouteConfig.cs中进行注册,降低Default的注册顺序。这样,当在尝试匹配所有的区域路由无果后,才会去匹配这个默认的路由规则了。
但在注册路由时,需要通过namespaces参数来指定当满足规则,希望在哪些命名空间下进行控制器的匹配,否则如果项目中存在同名的控制器时,将会导致以下错误:
除此之外,由于控制器和视图是在区域中创建的,因此在注册路由时,还需要通过DataTokens来设置区域名称,否则匹配到的视图很可能是非预期的,或者会发生以下的错误:
完整代码如下:
public class RouteConfig
{
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 string[] { "WebApplication1.Areas.Web.*" }
).DataTokens["area"] = "Web";
}
}