前面幾篇文章已經詳細分享解說Asp.net
如何透過HttpApplication
找到IHttpHandler
並執行呼叫介面方法.
今天要跟大家分享上圖的最後一塊拼圖揭密並探索Asp.net MVC
使用的IHttpHandler
.
此篇同步發布在筆者Blog [Day08] 揭密Mvc使用IHttpHandler by UrlRoutingModule-4.0
在標題已經透漏我們是透過UrlRoutingModule
這個繼承IHttpModule
的類別來取得IHttpHandler
有人可能會有疑問是我明明沒有註冊此HttpModule
Asp.net
怎麼知道的呢?
原因是這個Module
是預設就載入
下圖是一般IIS預設載入的HttpModule
可以看到UrlRoutingModule
已經在裡面了.
另外我們也可以看applicationhost.config
檔案,也可以看到UrlRoutingModule-4.0
也已經在裡面了.
我們可以發現他是在System.Web.Routing
這個命名空間下.
<modules>
....
<add name="ServiceModel-4.0" type="System.ServiceModel.Activation.ServiceHttpModule,System.ServiceModel.Activation,Version=4.0.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler,runtimeVersionv4.0" />
<add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition="managedHandler,runtimeVersionv4.0" />
</modules>
此連結可以看到 UrlRoutingModule 原始碼
protected virtual void Init(HttpApplication application) {
// Check if this module has been already addded
if (application.Context.Items[_contextKey] != null) {
return; // already added to the pipeline
}
application.Context.Items[_contextKey] = _contextKey;
application.PostResolveRequestCache += OnApplicationPostResolveRequestCache;
}
前面文章有說道Init
方法會在HttpApplication
呼叫InitInternal
方法時被呼叫.
這這裡可看到application.PostResolveRequestCache
多註冊一個OnApplicationPostResolveRequestCache
事件.
讓我們來看看此事件做了什麼事情
OnApplicationPostResolveRequestCache
方法中,利用 HttpContextWrapper
轉接器模式把app.Context
轉接成一個可接受HttpContextBase
物件,並呼叫傳入PostResolveRequestCache
方法中.
private void OnApplicationPostResolveRequestCache(object sender, EventArgs e) {
HttpApplication app = (HttpApplication)sender;
HttpContextBase context = new HttpContextWrapper(app.Context);
PostResolveRequestCache(context);
}
public virtual void PostResolveRequestCache(HttpContextBase context) {
// Match the incoming URL against the route table
RouteData routeData = RouteCollection.GetRouteData(context);
// Do nothing if no route found
if (routeData == null) {
return;
}
// If a route was found, get an IHttpHandler from the route's RouteHandler
IRouteHandler routeHandler = routeData.RouteHandler;
//... 判斷 error 程式碼
if (routeHandler is StopRoutingHandler) {
return;
}
RequestContext requestContext = new RequestContext(context, routeData);
// Dev10 766875 Adding RouteData to HttpContext
context.Request.RequestContext = requestContext;
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
//... 判斷 error 程式碼
// Remap IIS7 to our handler
context.RemapHandler(httpHandler);
}
RouteCollection
是一個全域路由集合,註冊使用路由(Asp.net Global.cs
中我們很常看到使用).
對於此集合註冊路由,是
MVC
,WebApi
能運行的關鍵喔
在MVC
中我們透過MapRoute
擴展方法來註冊路由,其實在這個擴展方法中會建立一個Route
物件並加入RouteCollection
集合中.
Route
物件會提供一個HttpHandler
來給我們呼叫使用.
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
RouteCollection.GetRouteData(context)
取得路由中匹配此次請求的路由資料,藉由此註冊進集合並繼承RouteBase
抽象類別的物件
在routeData
會有一個重要的屬性RouteHandler
是繼承於IRouteHandler
這個介面只有一個方法就是回傳IHttpHandler
看到這基本上就可以知道MVC
的IHttpHandler
是呼叫RouteHandler.GetHttpHandler
回傳的物件.
public interface IRouteHandler {
IHttpHandler GetHttpHandler(RequestContext requestContext);
}
後面會對於此介面有更詳細介紹
在PostResolveRequestCache
最後面幾段程式碼,是透過routeHandler.GetHttpHandler(requestContext)
取得IHttpHandler
,並將其設置給context
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
// Remap IIS7 to our handler
context.RemapHandler(httpHandler);
這邊說明一下RemapHandler
作用,最主要是把傳入參數handler
傳給_remapHandler
欄位
public void RemapHandler(IHttpHandler handler) {
EnsureHasNotTransitionedToWebSocket();
IIS7WorkerRequest wr = _wr as IIS7WorkerRequest;
if (wr != null) {
// Remap handler not allowed after ResolveRequestCache notification
if (_notificationContext.CurrentNotification >= RequestNotification.MapRequestHandler) {
throw new InvalidOperationException(SR.GetString(SR.Invoke_before_pipeline_event, "HttpContext.RemapHandler", "HttpApplication.MapRequestHandler"));
}
string handlerTypeName = null;
string handlerName = null;
if (handler != null) {
Type handlerType = handler.GetType();
handlerTypeName = handlerType.AssemblyQualifiedName;
handlerName = handlerType.FullName;
}
wr.SetRemapHandler(handlerTypeName, handlerName);
}
_remapHandler = handler;
}
_remapHandler
就是RemapHandlerInstance
屬性回傳的值
internal IHttpHandler RemapHandlerInstance {
get {
return _remapHandler;
}
}
我們之前有分享
MapHandlerExecutionStep
,MapHttpHandler
會優先讀取存在context.RemapHandlerInstance
中HttpHandler
如果有物件就給CallHandlerExecutionStep
呼叫使用.
這邊算是比較完整圓了上一篇埋的小伏筆.
今天談到我們了解到
UrlRoutingModule-4.0
這個HttpModule取得HttpHandler
MVC
是在application.PostResolveRequestCache
這個事件決定使用的HttpHandler
Asp.net MVC
呼叫的關鍵MapHandlerExecutionStep
執行前已經決定context.RemapHandlerInstance
所以就不會呼叫到config
設定HttpHander
物件基本上Asp.net
部分已經介紹完了,接下來會進入Asp.net MVC
的世界.