原文 http://blog.csdn.net/lan_liang/article/details/22993839
創建一個路由
打開 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匹配的例子:
Controller =Admin,Action=Index |
|
Controller=Index,Action=Admin |
|
Controller=Apples,Action=Oranges |
|
匹配失敗,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:
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錯誤: