揭密Mvc使用IHttpHandler by UrlRoutingModule-4.0 (第8天)

Agenda

前言:

前面幾篇文章已經詳細分享解說Asp.net如何透過HttpApplication找到IHttpHandler並執行呼叫介面方法.

瀏覽器請求IIS流程

今天要跟大家分享上圖的最後一塊拼圖揭密並探索Asp.net MVC使用的IHttpHandler.

UrlRoutingModule-4.0

在標題已經透漏我們是透過UrlRoutingModule這個繼承IHttpModule的類別來取得IHttpHandler

有人可能會有疑問是我明明沒有註冊此HttpModule Asp.net怎麼知道的呢?

原因是這個Module是預設就載入

下圖是一般IIS預設載入的HttpModule可以看到UrlRoutingModule已經在裡面了.

7-MVCModule.PNG

另外我們也可以看applicationhost.config檔案,也可以看到UrlRoutingModule-4.0也已經在裡面了.

我們可以發現他是在System.Web.Routing這個命名空間下.

1
2
3
4
5
<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 原始碼

1
2
3
4
5
6
7
8
9
10
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事件

OnApplicationPostResolveRequestCache方法中,利用 HttpContextWrapper轉接器模式把app.Context轉接成一個可接受HttpContextBase物件,並呼叫傳入PostResolveRequestCache方法中.

1
2
3
4
5
private void OnApplicationPostResolveRequestCache(object sender, EventArgs e) {
HttpApplication app = (HttpApplication)sender;
HttpContextBase context = new HttpContextWrapper(app.Context);
PostResolveRequestCache(context);
}

PostResolveRequestCache方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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來給我們呼叫使用.

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

RouteCollection.GetRouteData(context)取得路由中匹配此次請求的路由資料,藉由此註冊進集合並繼承RouteBase抽象類別的物件

IRouteHandler取得執行HttpHandler

routeData會有一個重要的屬性RouteHandler是繼承於IRouteHandler

這個介面只有一個方法就是回傳IHttpHandler看到這基本上就可以知道MVCIHttpHandler是呼叫RouteHandler.GetHttpHandler回傳的物件.

1
2
3
public interface IRouteHandler {
IHttpHandler GetHttpHandler(RequestContext requestContext);
}

後面會對於此介面有更詳細介紹

RemapHandler設置HttpContext的HttpHandler

PostResolveRequestCache最後面幾段程式碼,是透過routeHandler.GetHttpHandler(requestContext)取得IHttpHandler,並將其設置給context

1
2
3
4
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);

// Remap IIS7 to our handler
context.RemapHandler(httpHandler);

這邊說明一下RemapHandler作用,最主要是把傳入參數handler傳給_remapHandler欄位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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屬性回傳的值

1
2
3
4
5
internal IHttpHandler RemapHandlerInstance {
get {
return _remapHandler;
}
}

我們之前有分享MapHandlerExecutionStep,MapHttpHandler會優先讀取存在context.RemapHandlerInstanceHttpHandler如果有物件就給CallHandlerExecutionStep呼叫使用.

這邊算是比較完整圓了上一篇埋的小伏筆.

小結

今天談到我們了解到

  1. MVC是透過UrlRoutingModule-4.0這個HttpModule取得HttpHandler
  2. MVC是在application.PostResolveRequestCache這個事件決定使用的HttpHandler
  3. 路由其實是Asp.net MVC呼叫的關鍵
  4. 因為在MapHandlerExecutionStep執行前已經決定context.RemapHandlerInstance所以就不會呼叫到config設定HttpHander物件

基本上Asp.net部分已經介紹完了,接下來會進入Asp.net MVC的世界.

此文作者:Daniel Shih(石頭)
此文地址https://isdaniel.github.io/Ithelp-day8/
版權聲明:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW 許可協議。轉載請註明出處!


如果本文對您幫助很大,可街口支付斗內鼓勵石頭^^