MVC 依賴 System.Web.Routing 處理請求路徑解析,也就是說整個流程的起始是由 System.Web.Routing.UrlRoutingModule 開始的。
Web.config
<httpModules>
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, ..."/>
</httpModules>
那麼我們就看看這個 UrlRoutingModule 內部都做了什麼。
public class UrlRoutingModule : IHttpModule
{
protected virtual void Init(HttpApplication application)
{
application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);
application.PostMapRequestHandler += new EventHandler(this.OnApplicationPostMapRequestHandler);
}
}
訂閱了兩個 HttpApplication 事件。在 ASP.NET 應用程式生命週期中 PostResolveRequestCache 會在 PostMapRequestHandler 之前觸發,最後是 IHttpHandler.ProcessRequest() 完成最終的請求處理。
通過對請求上下文進行封裝,進一步進行處理。
public class UrlRoutingModule : IHttpModule
{
private void OnApplicationPostMapRequestHandler(object sender, EventArgs e)
{
HttpContextBase context = new HttpContextWrapper(((HttpApplication) sender).Context);
this.PostMapRequestHandler(context);
}
private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
{
HttpContextBase context = new HttpContextWrapper(((HttpApplication) sender).Context);
this.PostResolveRequestCache(context);
}
}
首先, PostResolveRequestCache 被執行。
public class UrlRoutingModule : IHttpModule
{
public virtual void PostResolveRequestCache(HttpContextBase context)
{
RouteData routeData = this.RouteCollection.GetRouteData(context);
if (routeData != null)
{
IRouteHandler routeHandler = routeData.RouteHandler;
...
if (!(routeHandler is StopRoutingHandler))
{
RequestContext requestContext = new RequestContext(context, routeData);
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
...
RequestData data2 = new RequestData();
data2.OriginalPath = context.Request.Path;
data2.HttpHandler = httpHandler;
context.Items[_requestDataKey] = data2;
context.RewritePath("~/UrlRouting.axd");
}
}
}
}
RouteCollection 實際是對 RouteTable.Routes 的引用。
public class UrlRoutingModule : IHttpModule
{
public RouteCollection RouteCollection
{
get
{
if (this._routeCollection == null)
{
this._routeCollection = RouteTable.Routes;
}
return this._routeCollection;
}
set
{
this._routeCollection = value;
}
}
}
好嗎,又跳了一回。
public class RouteTable
{
private static RouteCollection _instance = new RouteCollection();
public static RouteCollection Routes
{
get
{
return _instance;
}
}
}
這時候,我們要提及 Global.asax.cs 中的代碼。
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
}
IgnoreRoute 和 MapRoute 都是 System.Web.Mvc.dll 中提供的擴充方法。
public static class RouteCollectionExtensions
{
public static void IgnoreRoute(this RouteCollection routes, string url);
...
public static Route MapRoute(this RouteCollection routes, string name, string url);
...
}
唯一需要注意的是方法內部一個細節,其註冊的 IHttpHandler 是 MvcRouteHandler。
public static class RouteCollectionExtensions
{
public static Route MapRoute(this RouteCollection routes, string name, ...)
{
Route <>g__initLocal1 = new Route(url, new MvcRouteHandler());
<>g__initLocal1.Defaults = new RouteValueDictionary(defaults);
<>g__initLocal1.Constraints = new RouteValueDictionary(constraints);
Route route = <>g__initLocal1;
if ((namespaces != null) && (namespaces.Length > 0))
{
route.DataTokens = new RouteValueDictionary();
route.DataTokens["Namespaces"] = namespaces;
}
routes.Add(name, route);
return route;
}
public static void IgnoreRoute(this RouteCollection routes, string url, object constraints)
{
IgnoreRouteInternal <>g__initLocal0 = new IgnoreRouteInternal(url);
<>g__initLocal0.Constraints = new RouteValueDictionary(constraints);
IgnoreRouteInternal route = <>g__initLocal0;
routes.Add(route);
}
private sealed class IgnoreRouteInternal : Route
{
public IgnoreRouteInternal(string url) : base(url, new StopRoutingHandler()) { }
}
}
public class MvcRouteHandler : IRouteHandler
{
protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext);
IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext);
}
好了,回到 UrlRoutingModule.PostResolveRequestCache。
public class UrlRoutingModule : IHttpModule
{
public virtual void PostResolveRequestCache(HttpContextBase context)
{
RouteData routeData = this.RouteCollection.GetRouteData(context);
if (routeData != null)
{
IRouteHandler routeHandler = routeData.RouteHandler;
...
if (!(routeHandler is StopRoutingHandler))
{
RequestContext requestContext = new RequestContext(context, routeData);
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
...
RequestData data2 = new RequestData();
data2.OriginalPath = context.Request.Path;
data2.HttpHandler = httpHandler;
context.Items[_requestDataKey] = data2;
context.RewritePath("~/UrlRouting.axd");
}
}
}
}
在擷取 RouteData 和 MvcRouteHandler 後,調用 MvcRouteHandler.GetHttpHandler() 擷取 IHttpHandler。
public class MvcRouteHandler : IRouteHandler
{
protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new MvcHandler(requestContext);
}
IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
{
return this.GetHttpHandler(requestContext);
}
}
好!MvcHandler 出現了,這意味著執行流程從 System.Web.Routing 轉到 System.Web.Mvc 的契機出現了。UrlRoutingModule.PostResolveRequestCache() 的最後將相關請求參數儲存到上下文中,還有一行奇怪的代碼,看看我們從 Microsoft Symbol Server 下載的原始碼會有什麼說明。
具體步驟:
1. 首先添加 PostResolveRequestCache 斷點 (我們可以為沒有原始碼的方法設定斷點)。
2. 然後下載符號檔案。
3. 開啟原始碼。
好了,看看原作者如何說的。
public virtual void PostResolveRequestCache(HttpContextBase context)
{
// Save data to be used later in pipeline
context.Items[_requestDataKey] = new RequestData()
{
OriginalPath = context.Request.Path,
HttpHandler = httpHandler
};
// Rewrite path to something registered as a managed handler in IIS. This is necessary so IIS7 will
// execute our managed handler (instead of say the static file handler).
context.RewritePath("~/UrlRouting.axd");
}
不扯閑話了,我們繼續執行流程。依照 ASP.NET HttpApplication 事件順序,接下來 UrlRoutingModule.PostMapRequestHandler() 會被執行。
public class UrlRoutingModule : IHttpModule
{
public virtual void PostMapRequestHandler(HttpContextBase context)
{
RequestData data = (RequestData) context.Items[_requestDataKey];
if (data != null)
{
context.RewritePath(data.OriginalPath);
context.Handler = data.HttpHandler;
}
}
}
RequestData data 顯然就是 PostResolveRequestCache() 儲存的資料。通過將 HttpContext.Handler 設定為 MvcHandler,使得後續執行得以進行。對了,原作者對於這個方法的 RewritePath 也有說明。
public virtual void PostMapRequestHandler(HttpContextBase context)
{
RequestData requestData = (RequestData)context.Items[_requestDataKey];
if (requestData != null)
{
// Rewrite the path back to its original value, so the request handler only sees the original path.
context.RewritePath(requestData.OriginalPath);
// Set Context.Handler to the IHttpHandler determined earlier in the pipeline.
context.Handler = requestData.HttpHandler;
}
}
好了,有關 System.Web.Routing 的流程分析就到這了。