zoukankan      html  css  js  c++  java
  • 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版本解析

  • 相关阅读:
    游标cursor
    SQL: EXISTS
    LeetCode Reverse Integer
    LeetCode Same Tree
    LeetCode Maximum Depth of Binary Tree
    LeetCode 3Sum Closest
    LeetCode Linked List Cycle
    LeetCode Best Time to Buy and Sell Stock II
    LeetCode Balanced Binary Tree
    LeetCode Validate Binary Search Tree
  • 原文地址:https://www.cnblogs.com/ljzforever/p/2887125.html
Copyright © 2011-2022 走看看