• 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版本解析

  • 相关阅读:
    K8S-安全机制
    K8S-Service
    K8S-Pod资源管理
    K8S-kubelet启动过程
    每日进度
    每日进度
    每日进度
    每日进度
    每日进度
    归纳及计划
  • 原文地址:https://www.cnblogs.com/ljzforever/p/2887125.html
Copyright © 2020-2023  润新知