關於MVC Area Routing的怪異問題

問題
同事反應了一個關於MVC Area的怪異問題。客戶自行輸入一個不存在的網址,本應該出現404找不到,但卻發生Exception。如下圖,找到JiahuaController的Index,但找不到View。


原因

Route往子層的Area比對到了,專案的層級如下:
  • Root/Home/Index
  • Kim(Area/Jiahua/Index
一般來說,瀏覽/Jiahua會比對到上層預設的Route,而透過namespace的限制,照理來說不會往Area比對才對。如下圖,結果,它是在此namespace比對不到後,又往area的contoller尋找...昏。



解決方式
以下兩個擇一即可解決:

1.透過自訂Route,只比對上層的Controller。ps:此方法不是最佳解,這是在找到最佳解前實作出來的。

/// <summary>
    /// 此類別的建講式只會被執行一次
    /// http://johnatten.com/2013/08/21/customizing-routes-in-asp-net-mvc/
    ///  https://stackoverflow.com/questions/21583278/getting-all-controllers-and-actions-names-in-c-sharp/21583402
    /// </summary>
    public class IsRootController : IRouteConstraint
    {
        private Dictionary<string, string> _rootControllers = new  Dictionary<string, string>();
        public IsRootController()
        {
            Assembly asm = Assembly.GetExecutingAssembly();
            var _rootControllers = asm.GetTypes()
                  .Where(type => typeof(Controller).IsAssignableFrom(type) &&  type.FullName.IndexOf(".Areas.") == -1) //filter controllers
                  .Select(type => type.Name.Replace("Controller", ""))
                  .ToDictionary(c => c, c =>  c);//https://stackoverflow.com/questions/627867/linq-query-to-return-a-dictionarystring-string
        }
        public bool Match(HttpContextBase httpContext, Route route, string  parameterName, RouteValueDictionary values, RouteDirection routeDirection)
        {
            string controller = values["controller"].ToString();
            return _rootControllers.ContainsKey(controller);
        }
    }

    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[] { "WebApplication1.Controllers" }
                   , constraints: new { controller = new IsRootController() }
            );
        }
    }

2.在DataToken加入UseNamespaceFallback=false,告設Route只比對目前namespace的controller就好。

  routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id =  UrlParameter.Optional }
                , namespaces: new[] { "WebApplication1.Controllers" }
            ).DataTokens["UseNamespaceFallback"] = false;

參考文章

這個網誌中的熱門文章

[TFS] 分支與合併