DynamicModuleUtility对象在.net不同版本下的兼容性问题

      Asp.Net MVC3 框架包含了一个Microsoft.Web.Infrastructure程序集,里面有个DynamicModuleUtility对象及其RegisterModule方法.用于在程序中动态注册IHttpModule.一般来讲模块需在要在程序启动之前注册完成,所以调用这方法的程序一般都会在最开始处作PreApplicationStartMethod标记,比如:

using System;
using System.Web;
using Microsoft.Web.Infrastructure.DynamicModuleHelper;

[assembly:PreApplicationStartMethod(typeof(MyAppStart), "Start")]

public class CoolModule : IHttpModule
{
    // implementation not important 
    // imagine something cool here
}

public static class MyAppStart
{
    public static void Start()
    {
        DynamicModuleUtility.RegisterModule(typeof(CoolModule));
    }
}

      所以,如果直接将此调用写在Global.asax.cs文件的Application_Start方法里,正常情况下会报"This method cannot be called during the application's pre-start initialization stage"异常,如下所示:

      但实际情况比上面所述要复杂一些.我有一套代码,在公司里与家中同时开发.而将注册代码直接写在Application_Start方法里的程序,在公司的电脑上居然可以正常执行,即不报错,所有模块都能正确的被注册!这就让我百思不得其解.由于代码一样,所引用的类库也不曾发生变化,这让我感觉是否是执行环境所造成的.我公司的电脑是64位win7,安装了vs08,10,12,家里则是32位win7,只安装了10.于是让同事找了一台64位的电脑重新部署运行,依旧报错.正当我毫无头绪的时候,忽然想起之前dudu发过的一篇文章,说是将园子从.Net 4.0升级到.Net 4.5之后遇到的一系列问题.会不会是这个原因呢?于是将家里的机器新装了2012,再次运行,居然成功了.至此答案已经明了:是.Net 4.0与.Net 4.5.的兼容性问题.

      在详细分析止问题之前,有几个需要掌握的预备知识.

      1.CLR 目录结构

      安装一个特定版本的.Net后,会在三个位置布署这些dll.

      一个位于%Windows%\Microsoft.NET\Framework,跟据版本号的不同安装到不同的目录中去,又叫CSC目录.默认情况下,使用CSC命令编译程序进,程序所引用程序集的查找路径为:程序的根目录,CSC目录,GAC目录.

      自.Net 3.0开始,.Net安装程序会在%Program Files%\Reference Assemblies\Microsoft\Framework处也布署一份相同的程序集.Visual Studio会优先从此处引用程序集.所以,使用VS编译程序时,程序所引用程序集的查找路径为:程序的根目录,Reference Assemblies目录,GAC目录.

      最后一个位于%Windows%\Microsoft.NET\assembly,又叫GAC目录,其本质是一个有多级子目录的目录,能够同时布署相同文件名不同版本的dll,其主要为程序运行时服务.默认情况下,程序运行时,程序所引用程序集的查找路径为:程序的根目录,GAC目录.

      2..Net版本对应关系

      我们一般所说的.Net2.0, 3.0, 3.5, 4.0, 4.5,是指它发布时候的打包版本,实际其由类库,编译器,运行时三部分组成,如下表:

.NET打包版本 1 1.1 2 3 3.5 4 4.5
类库版本 1 1.1 2 3 3.5 4 4.5
C#编译器版本 1 1.1 2 2 3 4 4
CLR版本 1 1.1 2 2 2 4 4

      3..Net更新策略

      到目前为止.Net使用过两种更新策略.

      4.0及其之前的版本使用的是并存(side-by-side)更新.所有的版本都会存在于各自的目录中.需要特别说明的是,.Net2.0, 3.0, 3.5是增量更新,即并没有对已存在的程序集作出修改,而仅仅是新增了部分功能.所以对于公共部分,它们使用的都是相同的程序集.

      4.5使用的是覆盖(in-place)更新,它会将自己所有文件覆盖进4.0文件夹.也就是说,一旦更新至4.5,就一定会运行在4.5环境下.

      下面,我们再来仔细看一下为什么DynamicModuleUtility对象的RegisterModule方法在4.0环境下报错,而在4.5环境下能正常执行.

      首先,反编译方法

public static void RegisterModule(Type moduleType)
{
    if (DynamicModuleReflectionUtil.Fx45RegisterModuleDelegate != null)
        DynamicModuleReflectionUtil.Fx45RegisterModuleDelegate(moduleType);
    else
        LegacyModuleRegistrar.RegisterModule(moduleType);
}

      从变量命名我们也可以猜出,其实此方法对于不同的执行环境也是不同的处理.下面来看看DynamicModuleReflectionUtil.Fx45RegisterModuleDelegate是如何生成的

public static readonly Action<Type> Fx45RegisterModuleDelegate = GetFx45RegisterModuleDelegate();

private static Action<Type> GetFx45RegisterModuleDelegate()
{
    MethodInfo method = typeof(HttpApplication).GetMethod("RegisterModule", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(Type) }, null);
    if (method == null) return null;
    return (Action<Type>) Delegate.CreateDelegate(typeof(Action<Type>), method);
}

      这样就明白了,原来程序是通过反射查看HttpApplication对象是否有RegisterModule方法.如果有则直接使用此方法.

      下面我们就分别查看.Net 4.0的HttpApplication与4.5的HttpApplication

      4.0的如下

      4.5的如下

      现在就明白了,原来4.5新增了两个API,原生支持动态注册IHttpModule,且可以在Application_Start()方法中注册成功.

      现在回过头来看如果是4.0环境,程序是如何处理的,他调用了LegacyModuleRegistrar内部类的RegisterModule方法

private static readonly DynamicModuleReflectionUtil _reflectionUtil = DynamicModuleReflectionUtil.Instance;

public static void RegisterModule(Type moduleType)
{
    VerifyParameters(moduleType);
    if (_reflectionUtil != null)
    {
        lock (_lockObj)
        {
            _reflectionUtil.ThrowIfPreAppStartNotRunning.Invoke();
            AddModuleToClassicPipeline(moduleType);
            AddModuleToIntegratedPipeline(moduleType);
        }
    }
}

      哈哈,看名字就能明白,貌似如果不是在程序运行前注册就会抛异常,是不是这样呢?再次着看DynamicModuleReflectionUtil

[CompilerGenerated]
private Action <ThrowIfPreAppStartNotRunning>k__BackingField;

public Action ThrowIfPreAppStartNotRunning
{
    [CompilerGenerated]
    get
    {
        return this.<ThrowIfPreAppStartNotRunning>k__BackingField;
    }
    [CompilerGenerated]
    private set
    {
        this.<ThrowIfPreAppStartNotRunning>k__BackingField = value;
    }
}

public static readonly DynamicModuleReflectionUtil Instance = GetInstance();

private static DynamicModuleReflectionUtil GetInstance()
{
    try
    {
        if (Fx45RegisterModuleDelegate != null) return null;
        DynamicModuleReflectionUtil util = new DynamicModuleReflectionUtil();
        MethodInfo method = typeof(BuildManager).GetMethod("ThrowIfPreAppStartNotRunning", BindingFlags.NonPublic | BindingFlags.Static, null, Type.EmptyTypes, null);
        util.ThrowIfPreAppStartNotRunning = CommonReflectionUtil.MakeDelegate<Action>(method);
        CommonReflectionUtil.Assert(util.ThrowIfPreAppStartNotRunning != null);
        
        ......
        
        return util;
    }
    catch
    {
        return null;
    }
}

      它将ThrowIfPreAppStartNotRunning委托,委托给了BuildManager类的ThrowIfPreAppStartNotRunning方法.

internal static void ThrowIfPreAppStartNotRunning()
{
    if (PreStartInitStage != PreStartInitStage.DuringPreStartInit) throw new InvalidOperationException(SR.GetString("Method_can_only_be_called_during_pre_start_init"));
}

      原来,如果程序不是运行前状态,就会抛异常!

      现在,整个运行逻辑就一目了然了.如果是运行于4.5环境,则会将此方法委托给新增的API,此API支持运行时动态增加IHttpModule.如果运行于4.0环境,则会检查注册时是哪一个阶段.如果是运行时注册,则会抛异常!

      PS:吐个槽,感觉4.5的覆盖式更新,不是很稳啊,为什么就不能让我们手动选择运行环境呢?

      参考的文章:

      百年一遇的奇怪问题:当IE遇上.NET Framework 4.5

      善意提醒Dudu和其他打算升级到.NET Framework 4.5的同学

      What's New in ASP.NET 4.5 and Visual Studio 2012

      Missing Referenced Assemblies Folder for .NET 4.0

      C:\Program Files\Reference Assemblies for assemblies to reference in your code

      New Reference Assemblies Location

      再谈CLR:GAC目录的构造

      .NET Framework版本解析

原文地址:https://www.cnblogs.com/ljzforever/p/2887125.html