[Day08] 揭密Mvc使用IHttpHandler by UrlRoutingModule-4.0

前言:

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

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

此篇同步發布在筆者Blog [Day08] 揭密Mvc使用IHttpHandler by UrlRoutingModule-4.0

UrlRoutingModule-4.0

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

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

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

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

7-MVCModule.PNG

另外我們也可以看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事件

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);
}

PostResolveRequestCache方法

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抽象類別的物件

IRouteHandler取得執行HttpHandler

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

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

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

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

RemapHandler設置HttpContext的HttpHandler

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.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的世界.


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