Asp.Net MVC4 系列-- 進階篇之路由(1)

mybwu_com發表於2014-04-05

建立一個路由

開啟 RouteConfig.cs ,發現已經建立了一個預設路由 :

routes.MapRoute(
                name:"Default",
                url:"{controller}/{action}/{id}"
              //  defaults: new { controller ="Home", action = "Index", id = UrlParameter.Optional }
            );


為了說明路由的url匹配過程,暫時comment掉default引數。

開啟Global.cs ,可以看到路由配置檔案已經註冊:

protected void Application_Start()
        {
           AreaRegistration.RegisterAllAreas();
 
           WebApiConfig.Register(GlobalConfiguration.Configuration);
           FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
           RouteConfig.RegisterRoutes(RouteTable.Routes);
           BundleConfig.RegisterBundles(BundleTable.Bundles);
        }


關於路由工作方式

Asp.net MVC Framework 的路由部分,是插入在http pipeline中的,當接受到http請求,會尋找註冊的路由表(在ApplicationStart時候註冊,就是應用啟動時候),找到路由規則,獲取每個路由規則的pattern,試圖匹配當前請求合適的那個route,匹配成功,則解析出controller和action,從controllerfactory找到相應的controller,把請求傳遞給action,如果請求中傳參,路由還會解析出引數,給action。

下面是幾種url匹配的例子:

http://mysite/Admin/Index

Controller =Admin,Action=Index

http://mysite/Index/Admin

Controller=Index,Action=Admin

http://mysite/Apples/Oranges

Controller=Apples,Action=Oranges

http://mysite/Admin

匹配失敗,Segment太少

http://mysite/Admin/Index/Soccer

匹配失敗,Segment太多

路由會呼叫route handler來完成路由過程,預設的,mvc應用會使用MVCRouteHandler.手動新增一個Route,就可以體現出來:

routes.Add("MyRoute",newRoute("{controller}/{action}", new MvcRouteHandler()));


指定預設(default)

剛才說明url匹配時候,拿掉了default引數,這時我們一起看看default引數的作用。

routes.MapRoute(
                name:"Default",
                url:"{controller}/{action}/{id}",
               defaults: new { controller = "Home", action ="Index", id = UrlParameter.Optional }
            );


可以看到最後一個引數,指定了一個預設的controller和action。

Mydomain.com

Controller = Home ,action=Index

Mydomain.com/Customer

Controller=Customer ,action=Index

Mydomain.com/Customer/List

Controller=Customer, action=List

Mydomain.com/Customer/List/All

匹配失敗,segment太多

定值Segment

場景1,所有請求中第一個Segment為”public”的,需要統一處理,因此定義一個路由:

     routes.MapRoute(name: "PublicReqRoute", url:"Public/{controller}/{action}",
                           defaults: new {controller = "PublicHome", action ="Index"});


示例url:http://mysite/Public

匹配結果:controller = PublicHome,action=Index

場景2,請求中以public開始的,需要統一處理,定義路由:

     routes.MapRoute(name: "PublicReqRoute", url:"Public{controller}/{action}",
                           defaults: new {controller = "PublicHome", action ="Index"});


示例url: Http:/mysite/PublicX/

匹配結果:controller=X,action=Index


場景3:有個controller或action不工作,需要改個bug,把所有原來指向這個controller的請求暫時路由到首頁:

routes.MapRoute("myshop","Shop/OldAction",
new { controller = "Home", action ="Index" });


注意:路由是按著新增順序依次解析的,因此把最特殊的那個路由放在前面,避免先fall 到相對generall的那個。

獲取引數

對於Route:

routes.MapRoute(
                name:"Default",
                url:"{controller}/{action}/{id}",
               defaults: new { controller = "Home", action ="Index", id = UrlParameter.Optional }
            );


請求:http://mysite/Home/Index/15

Action 中使用RouteData.Values獲取傳入的id:

public ActionResult CustomVariable() {
ViewBag.Controller = "Home";
ViewBag.Action = "CustomVariable";
ViewBag.CustomVariable = RouteData.Values["id"];
return View();
}


使用mvcframework Route System自動傳參機制

除了使用RouteData.Values取出傳入的引數,可以更簡單的定義個引數在action,但是引數名要和route定義的相同(id)

Public  ActionResult  CustomVariable(string id) {
ViewBag.Controller = "Home";
ViewBag.Action = "CustomVariable";
ViewBag.CustomVariable = id;
return View();
}


對於url:http://mysite/Home/Index/15,id就會自動被賦值為15傳入action

定義可選引數

依然對於url:

routes.MapRoute(
                name:"Default",
                url:"{controller}/{action}/{id}",
               defaults: new { controller = "Home", action ="Index", id = UrlParameter.Optional }
            );


Id=UrlParameter.Optional,此時id就是可選引數

Mydomain.com

Controller=home ,action=Index

Mydomain.com/customer

Controller=customer, action=Index

Mydomain.com/customer/List

Controller=customer, action=List

Mydomain.com/customer/List/All

Controller=customer , action=List, Id=All

Mydomain.com/customer/List/All/Delete

url 匹配失敗

如果沒有傳參,action提供了id引數,那麼id此時就為null;

public  ActionResultCustomVariable(string id) {
ViewBag.Controller = "Home";
ViewBag.Action = "CustomVariable";
ViewBag.CustomVariable = id == null ? "<novalue>" : id;
return View();
}


作為另一個選擇,可以指定一個預設引數,如果沒url沒傳值,預設引數值就會被使用。

Public  ActionResultCustomVariable(string id = "DefaultId") {
ViewBag.Controller = "Home";
ViewBag.Action = "CustomVariable";
ViewBag.CustomVariable = id;
return View();
}


使用{*catchall}捕捉超出數量的segment

例如,對於這條route:

routes.MapRoute("MyRoute","{controller}/{action} /{*catchall}",
new { controller = "Home", action ="Index",
id = UrlParameter.Optional });


由於使用了{*catchall},對於url:

http://mysite/Home/Index/All/More/More/More

此時,controller=Home,Action=Index, catchall=”All/More/More/More”

這樣,就把解析剩下segment的工作交給了自己處理

解決在不同namespace的同名controller

例如現在有兩個controller,在不同的名稱空間:

namespace UrlsAndRoutes.Controllers {
public class HomeController : Controller {
public ActionResult Index() {
ViewBag.Controller = "Additional Controllers - Home";
ViewBag.Action = "Index";
return View("ActionName");
}
}
}


namespace UrlsAndRoutes.AdditionalControllers {
public  classHomeController : Controller {
public  ActionResultIndex() {
ViewBag.Controller = "Additional Controllers -Home";
ViewBag.Action = "Index";
return View("ActionName");
}
}
}


對於這種情況,可能希望路由先在一個指定的名稱空間裡找,找到了返回:

routes.MapRoute("MyRoute","{controller}/{action}/{id}/{*catchall}",
new { controller = "Home", action ="Index",
id = UrlParameter.Optional
},
new[] { "URLsAndRoutes.AdditionalControllers" });


關鍵在於最後的那個名稱空間引數,mvc framework會優先找 “URLsAndRoutes.AdditionalControllers”裡面的controller,如果沒找到,會搜尋其餘的名稱空間。

注意,這個new []{}數字裡的引數是平行的,也就是說,如果mvc framework在這些名稱空間裡找到多個同名controller,不會找到第一個就返回,依然會丟擲異常,例如:

new[] { "URLsAndRoutes.AdditionalControllers","UrlsAndRoutes.Controllers"});


對於url:

http://mysite/home/Index/15

mvc framework會在指定的名稱空間陣列裡找,由於找到了多個homecontroller,因此丟擲異常。

如果希望這樣:指定多個名稱空間,找到了第一個就返回,怎麼做?

可以配置多條route:

routes.MapRoute("AddContollerRoute","Home/{action}/{id}/{*catchall}",
new { controller = "Home", action ="Index",
id = UrlParameter.Optional },
new[] { "URLsAndRoutes.AdditionalControllers" });
 
routes.MapRoute("MyRoute","{controller}/{action}/{id}/{*catchall}",
new { controller = "Home", action ="Index",
id = UrlParameter.Optional },
new[] { "URLsAndRoutes.Controllers" });


mvc framework就會按著新增的順序依次查詢匹配的controller和action,找到了把解析好的引數(如果有)傳遞給action就返回了。

只允許mvc framework在指定的 namespace裡找,如何做?

Route myRoute=routes.MapRoute("AddContollerRoute",
"Home/{action}/{id}/{*catchall}",
new { controller = "Home", action ="Index",
id = UrlParameter.Optional },
new[] { "URLsAndRoutes.AdditionalControllers" }); 

myRoute.DataTokens["UseNamespaceFallback"] = false;



由於把DataTokens[“UseNamespaceFallback”] 設為false,因此mvcframework在指定的名稱空間:URLsAndRoutes.AdditionalControllers"裡面找完了,就不去其他地方找了。

給路由加限制

正則限制

可以加正則限制在controller和action:

routes.MapRoute("MyRoute","{controller}/{action}/{id}/{*catchall}",
new { controller = "Home", action ="Index", id = UrlParameter.Optional },
new { controller = "^H.*"},
new[] { "URLsAndRoutes.Controllers"});


由於給controller加上了正規表示式:”^H.*”的限制,因此對於url匹配了urlpattern,解析出controller,如果不是以H開頭的,也會被過濾掉。

類似的,也可以使用給action加正則限制:

new { controller = "^H.*", action ="^Index$|^About$"}


這樣,就限制了action必須是Index和About。

使用Http Method限制

可以限制請求是Get ,Post亦或是Put ,Delete等型別的。

new { controller = "^H.*", action ="Index|About",
httpMethod = new HttpMethodConstraint("GET") } ,



這樣,就限制了請求必須是Get方式的。

注意,給route加httpmethod限制,和給controller還有action加httpmethod區別在於,route在httppipeline很早的位置就被處理了,而到controller和action的時候,已經是httppipeline很晚的時候了,controller和action已經被解析了,引數也已經被解析了(如果有)。

自定義限制

Mvc 提供了IRouteConstranit 介面,可以自己定義限制,通過實現這個介面:

public class UserAgentConstraint : IRouteConstraint {
private string requiredUserAgent;
public UserAgentConstraint(string  agentParam) {
requiredUserAgent = agentParam;
}
Public  boolMatch(HttpContextBase  httpContext, Routeroute, string  parameterName,
RouteValueDictionary  values,RouteDirection routeDirection) {
return httpContext.Request.UserAgent != null &&
httpContext.Request.UserAgent.Contains(requiredUserAgent);
}
}


以上程式碼,實現了一個限制,必須Request物件中的代理物件不為空,也就是必須使用代理才能訪問,並且代理名稱包含指定的名稱。

使用自定義限制:

routes.MapRoute("ChromeRoute","{*catchall}",
new { controller = "Home", action ="Index" },
new {
customConstraint = newUserAgentConstraint("Chrome")
},
new[] { "UrlsAndRoutes.AdditionalControllers" });


這樣,就實現了,必須使用chrome才能訪問的限制。

讓訪問物理檔案目錄的請求也參與路由

預設情況下,route system會先檢查url是否指向了一個物理檔案目錄,如果是,返回找到這個檔案,返回;否則,執行註冊到路由表裡面的每一條route。也就是說,指向物理檔案目錄的請求實際在mvc路由機制之前已經被處理掉了,預設沒有參與路由。

如果要改變這個行為,例如訪問

http://mysite/resource/pic/ ,不希望route去找resource/pic物理目錄,而希望route找resourcecontroller ,pic action,那麼:

在註冊路由時,加上

routes.RouteExistingFiles = true;

這樣,本地檔案的檢測 就會在路由機制執行之後了。

在路由前後,httppipeline的示意圖


其中,httpmodule 1和 httpmodule2 在pipeline上面routesystem前後的兩個module。

加上route.RouteExistingFiles=true

Route system中,就會先執行MVC Route,後執行CheckDisk Files .最後,為物理檔案指定一個route:

routes.MapRoute("DiskFile","Content/StaticContent.html",
new {
controller = "Resource",
action = "List",
});


By Passing Route System

有些訪問資原始檔的請求,是需要ignore的,這種需求直接用ignoreRoute就可以了:

routes.IgnoreRoute("Content/{filename}.html");


這樣,對於url:http://mysite/Content.test.html 的請求就被route忽略了,訪問頁面,就會出現資源無法找到和404錯誤:

HTTP Error 404.0 - Not Found

The resource you are looking for has beenremoved, had its name changed, or is temporarily unavailable.

相關文章