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

湯姆大叔發表於2014-06-06

文章內容

上篇文章,我們分析如何動態註冊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也是基於這個類庫來實現的。

參考資料:

http://blogs.msdn.com/b/davidebb/archive/2010/10/11/light-up-your-nupacks-with-startup-code-and-webactivator.aspx

https://bitbucket.org/davidebbo/webactivator/src

同步與推薦

本文已同步至目錄索引:MVC之前的那點事兒系列

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

相關文章