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

浙公網安備 33010602011771號