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