文章內容
上篇文章,我們分析如何動態註冊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也是基於這個類庫來實現的。
參考資料:
https://bitbucket.org/davidebbo/webactivator/src
同步與推薦
本文已同步至目錄索引:MVC之前的那點事兒系列
MVC之前的那點事兒系列文章,包括了原創,翻譯,轉載等各型別的文章,如果對你有用,請推薦支援一把,給大叔寫作的動力。