1. 程式人生 > >MVC之前的那點事兒系列(6):動態註冊HttpModule

MVC之前的那點事兒系列(6):動態註冊HttpModule

文章內容

通過前面的章節,我們知道HttpApplication在初始化的時候會初始化所有配置檔案裡註冊的HttpModules,那麼有一個疑問,能否初始化之前動態載入HttpModule,而不是隻從Web.config裡讀取?

答案是肯定的, ASP.NET MVC3釋出的時候提供了一個Microsoft.Web.Infrastructure.dll檔案,這個檔案就是提供了動態註冊HttpModule的功能,那麼它是如何以註冊的呢?我們先去MVC3的原始碼看看該DLL的原始碼。

注:該DLL位置在C:\Program Files\Microsoft ASP.NET\ASP.NET Web Pages\v1.0\Assemblies\下

我們發現了一個靜態類DynamicModuleUtility,裡面有個RegisterModule方法引起了我的注意:

// Call from PreAppStart to dynamically register an IHttpModule, just as if you had added it to the
// <modules> section in Web.config. 
[SecuritySafeCritical] 
public static void RegisterModule(Type moduleType) {
    if (DynamicModuleReflectionUtil.Fx45RegisterModuleDelegate != null
) { // The Fx45 helper exists, so just call it directly. DynamicModuleReflectionUtil.Fx45RegisterModuleDelegate(moduleType); } else { // Use private reflection to perform the hookup. LegacyModuleRegistrar.RegisterModule(moduleType); } }

通過程式碼和註釋我們可以看到,這個方法就是讓我們動態註冊IHttpModule的,而且由於.Net4.5已經有helper類支援了,所以直接就可以用,其它版本使用LegacyModuleRegistrar.RegisterModule來動態註冊IHttpModule 的。而這個方法裡又分IIS6和IIS7整合或經典模式之分,程式碼大體上是一致的,我們這裡就只分析IIS6版本的程式碼:

private static void AddModuleToClassicPipeline(Type moduleType) {
    // This works by essentially adding a new entry to the <httpModules> section defined 
    // in ~/Web.config. Need to set the collection to read+write while we do this. 

    // httpModulesSection = RuntimeConfig.GetAppConfig().HttpModules; 
    // httpModulesSection.Modules.bReadOnly = false;
    // httpModulesSection.Modules.Add(new HttpModuleAction(...));
    // httpModulesSection.Modules.bReadOnly = true;
 
    HttpModulesSection httpModulesSection = null;
    try { 
        object appConfig = _reflectionUtil.GetAppConfig(); 
        httpModulesSection = _reflectionUtil.GetHttpModulesFromAppConfig(appConfig);
        _reflectionUtil.SetConfigurationElementCollectionReadOnlyBit(httpModulesSection.Modules, false /* value */); 

        DynamicModuleRegistryEntry newEntry = CreateDynamicModuleRegistryEntry(moduleType);
        httpModulesSection.Modules.Add(new HttpModuleAction(newEntry.Name, newEntry.Type));
    } 
    finally {
        if (httpModulesSection != null) { 
            _reflectionUtil.SetConfigurationElementCollectionReadOnlyBit(httpModulesSection.Modules, true /* value */); 
        }
    } 
}

上面程式碼的註釋非常重要,通過註釋我們可以看到,該方法先從RuntimeConfig.GetAppConfig().HttpModules獲取HttpModules集合,然後在集合裡新增需要註冊的新HttpModule,那就是說HttpApplication在初始化所有HttpModule之前必須將需要註冊的HttpModule新增到這個集合裡,那是在哪個週期呢?HttpApplication之前是HostingEnvironment,那是不是在這裡可以註冊呢?我們去該類檢視一下相關的程式碼,在Initialize方法裡突然發現一個貌似很熟悉的程式碼BuildManager.CallPreStartInitMethods(),程式碼如下:

// call AppInitialize, unless the flag says not to do it (e.g. CBM scenario).
// Also, don't call it if HostingInit failed (VSWhidbey 210495) 
if(!HttpRuntime.HostingInitFailed) { 
    try {
        BuildManager.CallPreStartInitMethods(); 
        if ((hostingFlags & HostingEnvironmentFlags.DontCallAppInitialize) == 0) {
            BuildManager.CallAppInitializeMethod();
        }
    } 
    catch (Exception e) {
        // could throw compilation errors in 'code' - report them with first http request 
        HttpRuntime.InitializationException = e; 

        if ((hostingFlags & HostingEnvironmentFlags.ThrowHostingInitErrors) != 0) { 
            throw;
        }
    }
}

通過去BuildManager類去檢視該方法的詳情,最終發現瞭如下這個方法:

internal static ICollection<MethodInfo> GetPreStartInitMethodsFromAssemblyCollection(IEnumerable<Assembly> assemblies) {
    List<MethodInfo> methods = new List<MethodInfo>(); 
    foreach (Assembly assembly in assemblies) {
        PreApplicationStartMethodAttribute[] attributes = null;
        try {
            attributes = (PreApplicationStartMethodAttribute[])assembly.GetCustomAttributes(typeof(PreApplicationStartMethodAttribute), inherit: true); 
        }
        catch { 
            // GetCustomAttributes invokes the constructors of the attributes, so it is possible that they might throw unexpected exceptions. 
            // (Dev10 bug 831981)
        } 

        if (attributes != null && attributes.Length != 0) {
            Debug.Assert(attributes.Length == 1);
            PreApplicationStartMethodAttribute attribute = attributes[0]; 
            Debug.Assert(attribute != null);
 
            MethodInfo method = null; 
            // Ensure the Type on the attribute is in the same assembly as the attribute itself
            if (attribute.Type != null && !String.IsNullOrEmpty(attribute.MethodName) && attribute.Type.Assembly == assembly) { 
                method = FindPreStartInitMethod(attribute.Type, attribute.MethodName);
            }

            if (method != null) { 
                methods.Add(method);
            } 
            else { 
                throw new HttpException(SR.GetString(SR.Invalid_PreApplicationStartMethodAttribute_value,
                    assembly.FullName, 
                    (attribute.Type != null ? attribute.Type.FullName : String.Empty),
                    attribute.MethodName));
            }
        } 
    }
    return methods; 
} 

發現了該方法會查詢應用程式下所有的程式集,判斷如果程式集標記為PreApplicationStartMethodAttribute特性,就會執行這個特性裡指定的方法(靜態方法),再檢查該類的程式碼:

    [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] 
    public sealed class PreApplicationStartMethodAttribute : Attribute {
         private readonly Type _type;
        private readonly string _methodName;
        public PreApplicationStartMethodAttribute(Type type, string methodName) { 
            _type = type;
            _methodName = methodName; 
        } 

        public Type Type {   get { return _type; }   } 
        public string MethodName {  get {  return _methodName; }   }
    }

這時候,心裡應該有數了吧,我們可以在這裡指定一個靜態方法名稱,然後在該方法去通過如下程式碼去註冊一個自定義的HttpModule(注意我們只能使用一次):

DynamicModuleUtility.RegisterModule(typeof(CustomModule));

我們來做個試驗試試我們分析的結果是不是正確,首先建立一個自定義HttpModule,程式碼如下:

public class CustomModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.BeginRequest += new EventHandler(context_BeginRequest);
            
    }

    void context_BeginRequest(object sender, EventArgs e)
    {
        HttpApplication ap = sender as HttpApplication;
        if (ap != null)
        {
            ap.Response.Write("湯姆大叔測試PreApplicationStartMethod通過!<br/>");
        }
    }

    public void Dispose()
    {
        //nothing to do here
    }
}

然後在建立一個用於註冊這個HttpModule的類並且帶有靜態方法:

public class PreApplicationStartCode
{
    private static bool hasLoaded;

    public static void PreStart()
    {
        if (!hasLoaded)
        {
            hasLoaded = true;
            //注意這裡的動態註冊,此靜態方法在Microsoft.Web.Infrastructure.DynamicModuleHelper
            DynamicModuleUtility.RegisterModule(typeof(CustomModule));
        }
    }
}

接著要安裝要求對該程式新增一個特性,程式碼如下:

[assembly: PreApplicationStartMethod(typeof(WebApplication1.Test.PreApplicationStartCode), "PreStart")]

最後,編譯執行,會發現所有的頁面頂部都會出現我們指定的文字(湯姆大叔測試PreApplicationStartMethod通過!),截圖如下:

這就證實了我們的分析結果是正確的,怎麼樣,這個功能不錯吧,不需要每次都在web.config裡定義你的HttpModule咯,而且我們以後封裝自己的類庫就方便多了,不需要在網站程式集裡指定程式碼去啟動自己封裝好的單獨類庫了,因為我們可以在自己的類庫裡直接使用這種方式實現自動註冊的功能。下個章節,我們將介紹一個利用此功能開發的超強類庫。

注:同一個程式集只能使用一次這個特性來呼叫一個靜態方法。

同步與推薦

MVC之前的那點事兒系列文章,包括了原創,翻譯,轉載等各型別的文章,如果對你有用,請推薦支援一把,給大叔寫作的動力。

相關推薦

MVC之前事兒系列6動態註冊HttpModule

文章內容 通過前面的章節,我們知道HttpApplication在初始化的時候會初始化所有配置檔案裡註冊的HttpModules,那麼有一個疑問,能否初始化之前動態載入HttpModule,而不是隻從Web.config裡讀取? 答案是肯定的, ASP.NET MVC3釋出的時候提供了一個Microsof

MVC之前事兒系列8UrlRouting的理解

文章內容 根據對Http Runtime和Http Pipeline的分析,我們知道一個ASP.NET應用程式可以有多個HttpModuel,但是隻能有一個HttpHandler,並且通過這個HttpHandler的BeginProcessRequest(或ProcessRequest)來處理並返回請求,前

MVC之前事兒系列7WebActivator的實現原理詳解

文章內容 上篇文章,我們分析如何動態註冊HttpModule的實現,本篇我們來分析一下通過上篇程式碼原理實現的WebActivator類庫,WebActivator提供了3種功能,允許我們分別在HttpApplication初始化之前,之後以及ShutDown的時候分別執行指定的程式碼,示例如下: [

MVC之前事兒系列9MVC如何在Pipeline中接管請求的?

文章內容 上個章節我們講到了,可以在HttpModules初始化之前動態新增Route的方式來自定義自己的HttpHandler,最終接管請求的,那MVC是這麼實現的麼?本章節我們就來分析一下相關的MVC原始碼來驗證一下我們的這個問題。 先建立一個MVC3的Web Application,選擇預設的模

MVC之前事兒系列3HttpRuntime詳解分析

文章內容 話說,經過各種各樣複雜的我們不知道的內部處理,非託管程式碼正式開始呼叫ISPAIRuntime的ProcessRequest方法了(ISPAIRuntime繼承了IISPAIRuntime介面,該介面可以和COM進行互動,並且暴露了ProcessRequest介面方法)。至於為什麼要呼叫這個方法,

MVC之前事兒系列4Http Pipeline詳細分析

文章內容 繼續上一章節的內容,通過HttpApplicationFactory的GetApplicationInstance靜態方法獲取例項,然後執行該例項的BeginProcessRequest方法進行執行餘下的Http Pipeline 操作,程式碼如下: // Get application i

MVC之前事兒系列5Http Pipeline詳細分析

文章內容 接上面的章節,我們這篇要講解的是Pipeline是執行的各種事件,我們知道,在自定義的HttpModule的Init方法裡,我們可以新增自己的事件,比如如下程式碼: public class Test : IHttpModule { public void Init(HttpAp

MVC之前事兒系列2HttpRuntime詳解分析

文章內容 從上章文章都知道,asp.net是執行在HttpRuntime裡的,但是從CLR如何進入HttpRuntime的,可能大家都不太清晰。本章節就是通過深入分析.Net4的原始碼來展示其中的重要步驟。請先看下圖:   首先,CLR在初始化載入的時候,會載入一個非常重要的類AppManagerApp

MVC之前事兒系列10MVC為什麼不再需要註冊萬用字元*.*了?

文章內容 很多教程裡都提到了,在部署MVC程式的時候要配置萬用字元對映(或者是*.mvc)到aspnet_ISPAI.dll上,在.NET4.0之前確實應該這麼多,但是.NET4.0之後已經不要再費事了,因為它預設就支援了。 你可以會問,沒有對映配置,請求這麼可能會走到aspnet_ISPAI.dll

MVC之前事兒系列1:進入CLR

MVC之前的那點事兒系列,是筆者在2012年初閱讀MVC3原始碼的時候整理的,主要講述的是從HTTP請求道進入MVCHandler之前的內容,包括了原創,翻譯,轉載,整理等各型別文章,當然也參考了部落格園多位大牛的文章,對此表示感謝,這次有時間貼出來,希望對大家有用。 主要內容 本文講解的是:伺服器接受H

arcgis jsapi介面入門系列6樣式

symbol: function () { //線樣式 //樣式詳情請看官方文件 let style = { //線顏色,支援多種格式:

python快速學習系列6面向物件程式設計OOP

一、面向物件程式設計: 1.比設計模式更重要的是設計原則: 1)面向物件設計的目標: ·可擴充套件:新特性很容易新增到現有系統中,基本不影響系統原有功能 ·可修改:當修改某一部分程式碼時,不會影響到其他不相關的部分 ·可替代:用具有相同介面的程式碼去替換系統中某一部分程式碼時,系統不受影

解讀ASP.NET 5 & MVC6系列6Middleware詳解

在第1章專案結構分析中,我們提到Startup.cs作為整個程式的入口點,等同於傳統的Global.asax檔案,即:用於初始化系統級的資訊(例如,MVC中的路由配置)。本章我們就來一一分析,在這裡如何初始化這些系統級的資訊。 新舊版本之間的Pipeline區別 ASP.NET 5和之前版本的最大區別是對HT

足球與oracle系列6伴隨建庫的亞洲盃——加油中國隊

原創作品,出自 “深藍的blog” 部落格,歡迎轉載,轉載時請務必註明以下出處,否則追究版權法律責任。         黑龍江的建庫之旅,讓北漂的我有了一段喘息和舒緩的平坦期。更高興的是在緊張的工

ASP.NET AJAX入門系列6UpdateProgress控制元件簡單介紹

{    // Introducing delay for demonstration.    System.Threading.Thread.Sleep(3000);    Label1.Text ="Page refreshed at "+        DateTime.Now.ToString();}

程式設計師之網路安全系列動態密碼

系列目錄 前文回顧 程式設計師之網路安全系列(五):數字證書以及12306的證書問題 我們使用了數字證書,確保了對方的公鑰身份,也就是網際網路中確定了要訪問的網站就是你要訪問的網站。 但是我們如何確定要訪問這個網站的使用者就是要訪問的使用者呢? 對銀行來說需要確保“敏捷的水”登入銀行時,必須是"敏捷

MVC之前事兒 ---- 系列文章

需要 cnblogs post omx pip 實現原理 內容 activator div MVC之前的那點事兒系列,是筆者在2012年初閱讀MVC3源碼的時候整理的,主要講述的是從HTTP請求道進入MVCHandler之前的內容,包括了原創,翻譯,轉載,整理等各類型文

HD-ACM算法專攻系列6——Big Number

ostream math image main 代碼 bsp str fine sum 題目描述: 源碼: #include"iostream" #include"cmath" using namespace std; #define PI 3.1415926 #d

NMI,FIQ 與arm構架事-1

微信公眾號 mindshare思享   NMI是Non Maskable Interrupt的縮寫,它是一種不能mask的硬體中斷,主要用於當一些不能恢復的硬體錯誤發生時傳送訊號給CPU。 NMI通常是用在當一般的中斷被軟體(比如OS)mask的情況下需要響應一

NMI, FIQ 與arm構架事-2

微信公眾號 mindshare思享   以下內容會談到: 1.   NMI的使用場景。 2.   在arm上怎麼模擬NMI a.   通過GIC的中斷優先順序模擬 b