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也是基于这个类库来实现的。

参考资料:

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之前的那点事儿系列文章,包括了原创,翻译,转载等各类型的文章,如果对你有用,请推荐支持一把,给大叔写作的动力。

原文链接

本文由豆约翰博客备份专家远程一键发布

时间: 2024-10-19 12:53:19

MVC之前的那点事儿系列(7):WebActivator的实现原理详解(转载)的相关文章

MVC之前的那点事儿系列(3):HttpRuntime详解分析(下)(转载)

MVC之前的那点事儿系列(3):HttpRuntime详解分析(下) 文章内容 话说,经过各种各样复杂的我们不知道的内部处理,非托管代码正式开始调用ISPAIRuntime的ProcessRequest方法了(ISPAIRuntime继承了IISPAIRuntime接口,该接口可以和COM进行交互,并且暴露了ProcessRequest接口方法).至于为什么要调用这个方法,大叔也不太清楚,找不到微软相关的资料哦.但大叔确定该方法就是我们进入HttpRuntime的正式大门,接着看吧. publi

MVC之前的那点事儿系列(3):HttpRuntime详解分析(下)

今天有幸被召回母校给即将毕业的学弟学妹们讲我这两年的工作史,看了下母校没啥特别的变化,就是寝室都安了空调,学妹们都非常漂亮而已..好了不扯蛋了,说下今天的主题吧.这些天我在深度定制语法高亮功能的同时发现了博客园提供的一些有意思的函数,甚至有几个博客园都没用到,我也不知道怎么才能触发那些功能..打开这个js就可以看到很多好用的东西了,虽然写的不怎么样,但是至少有这些功能. ps: 推荐安装一个代码格式化的插件,否则一坨看着蛋疼.比如第一个就是 log,方便调试. http://www.qidian

MVC之前的那点事儿系列(2):HttpRuntime详解分析(上)

position:static(静态定位) 当position属性定义为static时,可以将元素定义为静态位置,所谓静态位置就是各个元素在HTML文档流中应有的位置 podisition定位问题.所以当没有定义position属性时,并不说明该元素没有自己的位置,它会遵循默认显示为静态位置,在静态定位状态下无法通过坐标值(top,left,right,bottom)来改变它的位置. position:absolute(绝对定位) 当position属性定义为absolute时,元素会脱离文档流

MVC之前的那点事儿系列(8):UrlRouting的理解(转载)

MVC之前的那点事儿系列(8):UrlRouting的理解 文章内容 根据对Http Runtime和Http Pipeline的分析,我们知道一个ASP.NET应用程序可以有多个HttpModuel,但是只能有一个HttpHandler,并且通过这个HttpHandler的BeginProcessRequest(或ProcessRequest)来处理并返回请求,前面的章节将到了再MapHttpHandler这个周期将会根据请求的URL来查询对应的HttpHandler,那么它是如何查找的呢?

MVC之前的那点事儿系列(6):动态注册HttpModule(转载)

MVC之前的那点事儿系列(6):动态注册HttpModule 文章内容 通过前面的章节,我们知道HttpApplication在初始化的时候会初始化所有配置文件里注册的HttpModules,那么有一个疑问,能否初始化之前动态加载HttpModule,而不是只从Web.config里读取? 答案是肯定的, ASP.NET MVC3发布的时候提供了一个Microsoft.Web.Infrastructure.dll文件,这个文件就是提供了动态注册HttpModule的功能,那么它是如何以注册的呢?

MVC之前的那点事儿系列

MVC之前的那点事儿系列,是笔者在2012年初阅读MVC3源码的时候整理的,主要讲述的是从HTTP请求道进入MVCHandler之前的内容,包括了原创,翻译,转载,整理等各类型文章,当然也参考了博客园多位大牛的文章,对此表示感谢,这次有时间贴出来,希望对大家有用. MVC之前的那点事儿系列(1):进入CLR MVC之前的那点事儿系列(2):HttpRuntime详解分析(上) MVC之前的那点事儿系列(3):HttpRuntime详解分析(下) MVC之前的那点事儿系列(4):Http Pipe

MVC之前的那点事儿 ---- 系列文章

MVC之前的那点事儿系列,是笔者在2012年初阅读MVC3源码的时候整理的,主要讲述的是从HTTP请求道进入MVCHandler之前的内容,包括了原创,翻译,转载,整理等各类型文章,当然也参考了博客园多位大牛的文章,对此表示感谢,这次有时间贴出来,希望对大家有用. MVC之前的那点事儿系列(1):进入CLR MVC之前的那点事儿系列(2):HttpRuntime详解分析(上) MVC之前的那点事儿系列(3):HttpRuntime详解分析(下) MVC之前的那点事儿系列(4):Http Pipe

MVC之前的那点事儿系列(5):HttpPipeline详细分析(下)(转载)

MVC之前的那点事儿系列(5):HttpPipeline详细分析(下) 文章内容 接上面的章节,我们这篇要讲解的是Pipeline是执行的各种事件,我们知道,在自定义的HttpModule的Init方法里,我们可以添加自己的事件,比如如下代码: public class Test : IHttpModule { public void Init(HttpApplication context) { context.BeginRequest += new EventHandler(context_

MVC之前的那点事儿系列(9):MVC如何在Pipeline中接管请求的?(转载)

MVC之前的那点事儿系列(9):MVC如何在Pipeline中接管请求的? 文章内容 上个章节我们讲到了,可以在HttpModules初始化之前动态添加Route的方式来自定义自己的HttpHandler,最终接管请求的,那MVC是这么实现的么?本章节我们就来分析一下相关的MVC源码来验证一下我们的这个问题. 先创建一个MVC3的Web Application,选择默认的模板以便创建以后就默认包含HomeController和AccountController.我们知道MVC要先接管请求才能通过