1. 程式人生 > >MVC之前的那點事兒系列(7):WebActivator的實現原理詳解

MVC之前的那點事兒系列(7):WebActivator的實現原理詳解

文章內容

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

[assembly: WebActivator.PreApplicationStartMethod(typeof(A.InitClass1), "PreStart")]
[assembly: WebActivator.PostApplicationStartMethod(typeof(A.InitClass1), "
PostStart")] [assembly: WebActivator.ApplicationShutdownMethod(typeof(A.InitClass1), "ShutDown")]

另外還有一點和系統自帶的PreApplicationStartMethodAttribute不同的是,WebActivator的每種特性都可以使用多次,比如:

[assembly: WebActivator.PreApplicationStartMethod(typeof(A.InitClass1), "PreStart")]
[assembly: WebActivator.PreApplicationStartMethod(
typeof(A.InitClass2), "PreStart")] [assembly: WebActivator.PreApplicationStartMethod(typeof(A.InitClass3), "PreStart")]

因為它的原始碼很少,所以今天我們就來全面分析一下WebActivator的實現原理,首先下載WebActivator的最新1.5原始碼,原始碼地址:https://bitbucket.org/davidebbo/webactivator/src

解壓程式碼,我們可以看到WebActivator專案裡總共有6個重要的cs檔案,以及一個packages.config檔案(用於標記本專案引用了Microsoft.Web.Infrastructure.dll類庫),下面我們來分析一下每個檔案的原始碼。

3個XXXMethodAttribute屬性:

根據上面的用法,我們指導WebActivator提供了3個MethodAttribute,我們先來看看這3個檔案都是如何實現的,查閱程式碼發現3個類(PreApplicationStartMethodAttribute/ PostApplicationStartMethodAttribute/ ApplicationShutdownMethodAttribute)的內容都是一樣的,都是繼承於BaseActivationMethodAttribute類,然後提供建構函式所需要的Type型別和方法名稱, 3個特性類都支援使用多次並且只能用於Assembly,程式碼如下:

[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]

通用的基類BaseActivationMethodAttribute:

using System;
using System.Reflection;

namespace WebActivator
{
    // Base class of all the activation attributes
    [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
    public abstract class BaseActivationMethodAttribute : Attribute
    {
        private Type _type;
        private string _methodName;

        public BaseActivationMethodAttribute(Type type, string methodName)
        {
            _type = type;
            _methodName = methodName;
        }

        public Type Type { get { return _type; } }

        public string MethodName { get { return _methodName; } }

        public int Order { get; set; }


        public void InvokeMethod()
        {
            // Get the method
            MethodInfo method = Type.GetMethod(
                MethodName,
                BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);

            if (method == null)
            {
                throw new ArgumentException(
                    String.Format("The type {0} doesn't have a static method named {1}",
                        Type, MethodName));
            }

            // Invoke it
            method.Invoke(null, null);
        }
    }
}

通過程式碼,我們首先可以看到,除了Type和MethodName以外,還多了一個Order屬性,用來標記多次使用同一個Attribute的時候的執行順序。然後提供了一個InvokeMethod方法,用來執行該類裡傳入當前Type類的MethodName靜態方法。

Assembly擴充套件方法AssemblyExtensions:

using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace WebActivator
{
    static class AssemblyExtensions
    {
        // Return all the attributes of a given type from an assembly
        public static IEnumerable<T> GetActivationAttributes<T>(this Assembly assembly) where T : BaseActivationMethodAttribute
        {
            return assembly.GetCustomAttributes(
                typeof(T),
                inherit: false).OfType<T>();
        }
    }
}

該擴充套件方法主要是用於獲取某一個程式集Assembly下指定型別的所有Attribute(並且不包括繼承的類),也就是查詢上述3種特性的Attributes(因為每種都允許宣告多次)。

主管理類ActivationManager:

該類主要分為如下幾個部分:

1 私有靜態函式Assemblies, GetAssemblyFiles主要是獲取當前應用程式下的所有DLL程式集,以供其它方法從這個程式集集合裡遍歷相應的特性宣告。

// 載入所有獲取的程式集
private static IEnumerable<Assembly> Assemblies
{
    get
    {
        if (_assemblies == null)
        {
            // Cache the list of relevant assemblies, since we need it for both Pre and Post
            _assemblies = new List<Assembly>();
            foreach (var assemblyFile in GetAssemblyFiles())
            {
                try
                {
                    // Ignore assemblies we can't load. They could be native, etc...
                    _assemblies.Add(Assembly.LoadFrom(assemblyFile));
                }
                catch
                {
                }
            }
        }

        return _assemblies;
    }
}

// 獲取程式集檔案路徑集合
private static IEnumerable<string> GetAssemblyFiles()
{
    // When running under ASP.NET, find assemblies in the bin folder.
    // Outside of ASP.NET, use whatever folder WebActivator itself is in
    string directory = HostingEnvironment.IsHosted
        ? HttpRuntime.BinDirectory
        : Path.GetDirectoryName(typeof(ActivationManager).Assembly.Location);
    return Directory.GetFiles(directory, "*.dll");
}

2 獲取所有AppCode資料夾下程式碼編譯後的程式集。

// Return all the App_Code assemblies
private static IEnumerable<Assembly> AppCodeAssemblies
{
    get
    {
        // Return an empty list if we;re not hosted or there aren't any
        if (!HostingEnvironment.IsHosted || !_hasInited || BuildManager.CodeAssemblies == null)
        {
            return Enumerable.Empty<Assembly>();
        }

        return BuildManager.CodeAssemblies.OfType<Assembly>();
    }
}

3 執行3種特性裡所指定的方法

public static void RunPreStartMethods()
{
    RunActivationMethods<PreApplicationStartMethodAttribute>();
}

public static void RunPostStartMethods()
{
    RunActivationMethods<PostApplicationStartMethodAttribute>();
}

public static void RunShutdownMethods()
{
    RunActivationMethods<ApplicationShutdownMethodAttribute>();
}

// Call the relevant activation method from all assemblies
private static void RunActivationMethods<T>() where T : BaseActivationMethodAttribute
{
    foreach (var assembly in Assemblies.Concat(AppCodeAssemblies))
    {
        foreach (BaseActivationMethodAttribute activationAttrib in assembly.GetActivationAttributes<T>().OrderBy(att => att.Order))
        {
            activationAttrib.InvokeMethod();
        }
    }
}

從程式碼可以看出,3個特性執行方法呼叫的都是同一個泛型方法RunActivationMethods<T>,在這個方法裡,主要是從所有的程式集裡,通過泛型方法查詢所有標記的特性(按Order排序),並且執行每個特性聲明裡指定的方法。另外從Assemblies.Concat(AppCodeAssemblies)可以發現,所有的程式集還要包括App_Code目錄下程式碼編譯的程式集哦。

4 自定義HttpModule

class StartMethodCallingModule : IHttpModule
{
    private static object _lock = new object();
    private static int _initializedModuleCount;

    public void Init(HttpApplication context)
    {
        lock (_lock)
        {
            // Keep track of the number of modules initialized and
            // make sure we only call the post start methods once per app domain
            if (_initializedModuleCount++ == 0)
            {
                RunPostStartMethods();
            }
        }
    }

    public void Dispose()
    {
        lock (_lock)
        {
            // Call the shutdown methods when the last module is disposed
            if (--_initializedModuleCount == 0)
            {
                RunShutdownMethods();
            }
        }
    }
}

該Module主要是用於在 Init的時候執行PostStart型別的方法,並且在Dispose的時候執行Shutdown型別的方法,並且只執行一次。

5.最重要的入口方法

public static void Run()
{
    if (!_hasInited)
    {
        RunPreStartMethods();

        // Register our module to handle any Post Start methods. But outside of ASP.NET, just run them now
        if (HostingEnvironment.IsHosted)
        {
            Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(StartMethodCallingModule));
        }
        else
        {
            RunPostStartMethods();
        }

        _hasInited = true;
    }
}

Run方法看起來很容易理解了,首先執行PreStart型別的方法,然後判斷HostingEnvironment是否Host成功,如果成功就動態註冊我們上面自定義的HttpModule,以便讓該Module在HttpApplication初始化和Dispose的時候分別執行PostStart型別的方法和ShutDown型別的方法,如果沒有Host成功,那隻執行PostStart型別的方法。

注:由程式碼實現可以看出,在PreStart型別的方法裡,不能使用HttpContext物件進行輸入輸出,因為該物件在此時還沒用建立成功呢。

6.誰呼叫了入口方法Run()

這個就不用多說了吧,肯定是使用.Net4.0自帶的PreApplicationStartMethodAttribute特性,程式碼如下:

[assembly: PreApplicationStartMethod(typeof(WebActivator.ActivationManager), "Run")]

你可以讓這段程式碼放在WebActivator專案裡任何類檔案的namespace外部,但為了統一起見,一般都是放在Properties目錄下的AssemblyInfo類檔案裡,WebActivator就是這麼做的。

總結,好了,這就是WebActivator的全部原始碼,實現起來其實很簡單,對吧?那以後專案再有類似需求的時候,就大膽使用這個類庫吧,另外NInject.MVC也是基於這個類庫來實現的。

參考資料:

同步與推薦

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

相關推薦

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

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

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

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

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之前事兒系列6動態註冊HttpModule

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

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

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

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

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

Quartz.Net系列Trigger之SimpleScheduleBuilder

所有方法圖    SimpleScheduleBuilder方法 RepeatForever:指定觸發器將無限期重複。 WithRepeatCount:指定重複次數 var trigger = TriggerBuilder.Create().WithSimpleSchedule(s=&

Quartz.Net系列Trigger之DailyTimeIntervalScheduleBuilder

1.介紹 中文意義就是每日時間間隔計劃生成 2.API講解 (1)WithInterval、WithIntervalInHours、WithIntervalInMinutes、WithIntervalInSeconds WithInterval:指定要生成觸發器的時間單位和間隔。 WithIntervalIn

arcgis jsapi介面入門系列7滑鼠在地圖畫線

初始化,每個map執行一次就行 drawPolylineInit: function () { //畫幾何物件初始化 //新建一個圖形圖層用於存放畫圖過程中的圖形 let layer = new t

python快速學習系列7迭代器

迭代器協議 1.迭代器協議: ·迭代器是一個物件 ·迭代器可以被next()函式呼叫,並返回一個值 ·迭代器可以被iter()函式呼叫,並返回迭代器自己 ·連續被next()呼叫時返回一系列的值 ·如果到了迭代的末尾,則丟擲StopIteration異常 ·迭代器也可以沒有末尾,只要被nex

解讀ASP.NET 5 & MVC6系列7依賴注入

在前面的章節(Middleware章節)中,我們提到了依賴注入功能(Dependency Injection),ASP.NET 5正式將依賴注入進行了全功能的實現,以便開發人員能夠開發更具彈性的元件程式,MVC6也利用了依賴注入的功能重新對Controller和View的服務注入功能進行了重新設計;未來的依賴

ABP入門系列7——分頁實現

完成了任務清單的增刪改查,咱們來講一講必不可少的的分頁功能。 首先很慶幸ABP已經幫我們封裝了分頁實現,實在是貼心啊。 來來來,這一節咱們就來捋一捋如何使用ABP的進行分頁吧。 一、分頁請求DTO定義 資料傳輸物件(Data Transfer Objects)用於應用層和展現層的資料傳輸。 展現層傳入資料

springCloud7Ribbon實現客戶端側負載均衡-消費者整合Ribbon

spring cloud ribbon 消費者整合ribbon 一、簡介 Ribbon是Netfix發布的負載均衡器,它有助於控制HTTP和TCP客戶端的行為。為Ribbon配置服務提供者地址列表後,Ribbon就可基於某種負載均衡算法,自動地幫助服務消費者去請求。Ribbon默認為我們提供了很

WebAssembly 系列WebAssembly 工作原理

WebAssembly 是除了 JavaScript 以外,另一種可以在網頁中執行的程式語言。過去如果你想在瀏覽器中執行程式碼來對網頁中各種元素進行控制,只有 JavaScript 這一種選擇。 所以當人們談論 WebAssembly 的時候,往往會拿 JavaScript 來進行比較。但

Tensorflow入門系列--tutorials/image/mnist程式

mnist官方程式詳解 在之前的文章中,我們在github上clone了TensorFlow/model這一個專案,這一次讓我們一起來看一下其下tutorials/image/mnist的程式。 首先,讓我們從程式的起始點開始看起。 parser =

python網路爬蟲7爬取靜態資料

目的 爬取http://seputu.com/資料並存儲csv檔案 匯入庫 lxml用於解析解析網頁HTML等原始碼,提取資料。一些參考:https://www.cnblogs.com/zhangxinqi/p/9210211.html requests請求網頁 chardet用於判斷網頁中的字元編