asp.net Mvc學(xué)習(xí)之URL路由
Asp.Net MVC的請求的執(zhí)行過程粗略的來看大致是這樣的:
1 WebServer 接收來自的客戶端的Request(請求)。
2 Web Application在第一次運行的時候(Application_Start())根據(jù)其中的設(shè)置代碼會創(chuàng)建一個RouteTable(路由表)實現(xiàn)URL到處理程序之間的映射。
3 UrlRotingModule模塊解析該請求的URL,并選擇相關(guān)的URL路由。
4 MvcHandler對象來處理該URL路由,創(chuàng)建要執(zhí)行的控制器(Controller )。
5 執(zhí)行Controller(即調(diào)用指定的執(zhí)行方法)。
6 返回處理結(jié)果(執(zhí)行View()方法,返回視圖到瀏覽器)。
那么我們首先來深入了解一下URL路由。其實URL路由是ASP.NET 3.5 MVC框架中獨立出來的一個功能,也就是說不僅僅在MVC中,即使是在傳統(tǒng)的WebForm也可以使用它。
------------------------------------------------------------------------------------------------------------------------------------------
一 首先,URL路由是如何加入到HttpApplication處理管道中來的?
我們注意到MVC項目的WebConfig中有這么一個配置項
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
那么UrlRoutingModule必然是處理URL路由的一個HttpModule了,將System.Web.Routing進(jìn)行反編譯,并找到UrlRoutingModule這個類我們可以看到它在Init方法中注冊了下面兩個事件
application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);
application.PostMapRequestHandler += new EventHandler(this.OnApplicationPostMapRequestHandler);
在此我們回顧下HttpApplication處理管線中的各種事件(已省略與本文無關(guān)的事件)
1 請求驗證,檢查瀏覽器發(fā)送的信息,包括確定是否包含潛在的惡意標(biāo)記等。
2 如果WebConfig中配置了UrlMappingsSection,則執(zhí)行URL映射
3 引發(fā)BeginRequest事件
......
......
9 引發(fā)PostResolveRequestCache事件
10 引發(fā)MapRequestHandler事件:
根據(jù)所請求資源文件的擴(kuò)展名(在應(yīng)用程序的配置文件中映射),選擇對應(yīng)實現(xiàn)IHttpHandler接口的處理類。如果請求是aspx,并且需要對該頁進(jìn)行編譯,則asp.net會在獲取該頁面實例之前對其進(jìn)行編譯。
11 引發(fā)PostMapRequestHandler事件
......
......
15 調(diào)用第10步選擇的IHttpHandler的ProcessRequest方法(或異步版的BeginProcessRequest)
......
......
22引發(fā)EndRequest事件
上面黃色部分標(biāo)明的則是UrlRoutingModule所注冊的事件
這兩個事件最開始都對HttpContext進(jìn)行再次封裝()
【關(guān)于下文要提到的Route,RouteConllection,RouteData等的簡單結(jié)構(gòu)說明】

RouteData routeData = this.RouteCollection.GetRouteData(context);
if (routeData != null)
{
IRouteHandler routeHandler = routeData.RouteHandler;
if (routeHandler == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, RoutingResources.UrlRoutingModule_NoRouteHandler, new object[0]));
}
if (!(routeHandler is StopRoutingHandler))
{
RequestContext requestContext = new RequestContext(context, routeData);
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
if (httpHandler == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, RoutingResources.UrlRoutingModule_NoHttpHandler, new object[] { routeHandler.GetType() }));
}
RequestData data2 = new RequestData {
OriginalPath = context.Request.Path,
HttpHandler = httpHandler
};
context.Items[_requestDataKey] = data2;
context.RewritePath("~/UrlRouting.axd");
}
}
(1) 根據(jù)Url在路由表中查找匹配的路由獲得RouteData,即:
遍歷RouteCollection集合,調(diào)用集合中Route(路由)的GetRouteData方法,如果GetRouteData方法返回的RouteData對象不為空,則立即返回,這也就是說明了路由是按照路由設(shè)置的次序解析的,就算一個URL可以匹配多個路由,解析過程中一旦發(fā)現(xiàn)有匹配的路由就不會再進(jìn)行后面的路由檢索了,直接結(jié)束路由的解析過程。
(2) 如果最終獲取到的RouteData不為空,獲取RouteData的RouteHandler屬性,RouteHandler是實現(xiàn)了IRouteHandler接口的,只有一個方法,那就是GetHttpHandler,此Handler(MvcRouteHandler)用于處理URL路由
(3) 如果這個RouteHandler的類型不是StopRoutingHandler
1) 封裝一個RequestContext類,此類只有兩個屬性,一個是之前的RouteData,還有就是封裝過后的context(HttpContextBase)
2) 調(diào)用之前(2)中RouteHandler中的GetHttpHandler方法傳入上一步的RequestContext參數(shù)將得到的HttpHandler,將其與當(dāng)前的請求路徑(context.Request.Path)一并封裝到RequestData對象中,并將RequestData存入context
3) 重寫路徑,指向UrlRouting.axd文件(context.RewritePath("~/UrlRouting.axd")),此文件是類似于aspx,實現(xiàn)了IHttpHandler接口。
2 PostMapRequestHandler中做了如下處理:
在此事件中要替換掉context中的handler, 從context中獲取到在之前這個PostResolveRequestCache事件中存入的RequestData,從RequestData獲取OriginalPath,將開始從寫路徑到UrlRouting.axd改回最開始狀態(tài)(即OriginalPath)讓輸出路徑和輸入路徑相同,同樣設(shè)置context的Handler為之前存入RequestData的HttpHandler。
這個Handler在配置路由表,往RouteConllection中存入Route實例化這個Route時被傳入
3 HttpApplication處理管道后面事件中將會調(diào)用此Handler(MvcHandler)的ProcessRequest方法。
寫到這里我產(chǎn)生了一個疑問??
為什么在PostResolveRequestCache事件中將得到RequestData并存入Context 又在后面的PostMapRequestHandler事件中將其取出得到開始存入的Handler對象以及原始URL,并把重寫的路徑改回原始的URL再設(shè)置Context的Handler屬性,而不是這些過程全部都在PostMapRequestHandler這個事件中處理了,這么一存一放不是多此一舉嗎?
至此我們再來研究下位于PostResolveRequestCache事件與PostMapRequestHandler事件之間的MapRequestHandler事件:
之前的說明是:
根據(jù)所請求資源文件的擴(kuò)展名(在應(yīng)用程序的配置文件中映射),選擇對應(yīng)實現(xiàn)IHttpHandler接口的處理類。如果請求是aspx,并且需要對該頁進(jìn)行編譯,則asp.net會在獲取該頁面實例之前對其進(jìn)行編譯。
繼續(xù)查資料可以知道
此事件只在IIS7.0的集成模式,且.Net Framework版本大于等于3.0的情況下才會觸發(fā),由于asp.net mvc請求的路徑并不會對應(yīng)一個文件,所以在此處會報錯,MS為了使處理模塊能夠在iis7中實現(xiàn)路由,則采取了這么一種簡單的解決辦法。先把路徑指向~/UrlRouting.axd,在此事件中會設(shè)置一個UrlRouting.axd類型的Handler避免報錯,并在下一步事件中替換掉此處的Handler再把~/UrlRouting.axd這個路徑給改回來。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

關(guān)于路由的使用:
(對于已存在路徑的文件想要其不被路由:
RouteTable.Routes.RouteExistingFiles = false;)
一般通過Global.asax文件,在Application_Start()方法設(shè)置
添加路由
Example:
routes.Add("Default", new Route
(
"{controller}/{action}/{id}",
new RouteValueDictionary { { "controller", "Home" }, { "action", "Index" }, { "id", UrlParameter.Optional } },
new MvcRouteHandler()
));
常量 控制器名 對應(yīng)的方法 變量username 變量password
routes.Add("Register", new Route
(
"Register/{controller}/{action}/{username}/{password}",
new RouteValueDictionary { { "controller", "Register" }, { "action", "RegisterUser" } },
));
設(shè)置默認(rèn)值
由于發(fā)布System.Web.Routing程序集之后MS也在不斷的改進(jìn),MS感覺上述路由的設(shè)置比較麻煩,但是此程序集又已經(jīng)發(fā)布了, 所以使用到.net 3.5 framework 新特性,擴(kuò)展方法,即可以在原有類別中添加新的實現(xiàn)方法,從而實現(xiàn)新的功能。
位于System.Web.Mvc中的RouteCollectionExtensions類就是一個靜態(tài)類,其中定義的方法就是擴(kuò)展方法
針對于路由集合類RouteCollection擴(kuò)展了兩類方法
IgnoreRoute()
MapRoute()
對于MapRoute方法可以將上述代碼簡化為
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "", action = "", id = UrlParameter.Optional }
);
routes.MapRoute(
"Register ",
"Register/{controller}/{action}/{username}/{password}",
new { controller = "Register", action = "RegisterUser" }
);
忽略路由
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
在上面的路由設(shè)置其中的路由名稱是可選的參數(shù),路由名稱可以用來生成URL路由,但其在路由解析中并沒有作用
但是如果在視圖中生成相關(guān)的路由鏈接,可以直接指定路由名稱,加快檢索速度,使用指定路由的好處還有可以不必知名路由的其他參數(shù),例如控制器,控制方法等
建議: 將常用的路由存放在路由表最前端
自定義路由約束
實現(xiàn)一個約束類:
public class IDRouteConstraint: IRouteConstraint
{
#region IRouteConstraint Members
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
if ((routeDirection == RouteDirection.IncomingRequest)
&& (parameterName.ToLower(CultureInfo.InvariantCulture) == "id"))
{
long id;
if (long.TryParse(values["id"].ToString(), out id))
{
if (id < 1000000 && id >= 0)
{
return true;
}
}
}
return false;
}
#endregion
}
在路由中配置
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new
{
controller = "Home", action = "Index", id="1001"
},
new
{
id = new IDRouteConstraint()
}
);
浙公網(wǎng)安備 33010602011771號