ASP.NET MVC 4.7.2注册AssemblyResolve引发AntiForgery报错问题
ASP.NET 4.7.2 MVC自定义AssemblyResolve加载器导致AntiForgery功能异常
问题描述
我在.NET类库中为AssemblyResolve事件添加自定义加载器,用于加载部分嵌入资源,实现代码如下:
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => { return Domain.Assemblies.LoadResource(e.Name, System.Reflection.Assembly.GetExecutingAssembly()); };
public static System.Reflection.Assembly LoadResource(string fileName, System.Reflection.Assembly assembly) { fileName = assembly.GetName().Name + "." + fileName.Split(',')[0] + ".dll"; var resFilestream = assembly.GetManifestResourceStream(fileName); byte[] ba = new byte[resFilestream.Length]; resFilestream.Read(ba, 0, ba.Length); var byteArray = ba; resFilestream.Close(); resFilestream.Dispose(); return System.Reflection.Assembly.Load(byteArray); }
该实现可在Winforms、Azure应用/WebJobs/Functions等多种环境下正常运行,但在ASP.NET(4.7.2)MVC站点中执行时会导致AntiForgery防伪功能异常。问题与DLL加载不正确相关,会触发三类独立报错:
TypeInitializationException: The type initializer for 'System.Web.Helpers.Claims.ClaimsIdentityConverter' threw an exception.
FileLoadException: Could not load file or assembly 'Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. Invalid pointer (Exception from HRESULT: 0x80004003 (E_POINTER))
NullReferenceException: Object reference not set to an instance of an object.
若移除程序集加载DLL中的类初始化对象,站点可正常运行无报错;恢复该逻辑则故障复现。我也尝试过将代码从Global.asax迁移至内联C#中,但只要执行该代码,后续加载包含防伪令牌的页面就会触发相同错误。
更新
我使用Fusion++检查DLL加载情况,发现出错的DLL在两种场景下存在完全相同的加载失败记录(通用的“找不到程序集”报错),但仅在加载我的自定义DLL时才会抛出可见异常。
问题原因
核心问题出在自定义AssemblyResolve处理逻辑的两个致命缺陷,在ASP.NET特殊的多上下文加载机制下被放大:
- 无差别拦截所有程序集解析请求:
AssemblyResolve事件触发时仅代表CLR在当前探测路径下未找到目标程序集,原代码没有做任何过滤,对所有解析请求都强行尝试从执行程序集的嵌入资源中查找对应DLL。ASP.NET本身有多层程序集探测逻辑(包括GAC、站点bin目录、临时ASP.NET文件目录等),很多时候触发AssemblyResolve只是探测流程的一环,后续框架本身可以正常加载目标程序集。但原代码在资源不存在时,GetManifestResourceStream会返回null,后续直接访问resFilestream.Length会抛出空引用异常,直接打断CLR的默认加载流程,导致本来可以被正常加载的Microsoft.IdentityModel等依赖加载失败。 - 无缓存重复加载+加载上下文不匹配:原代码每次请求都调用
Assembly.Load(byte[])加载程序集,这种方式加载的程序集会进入无上下文加载域,不会和ASP.NET默认加载上下文中的同版本程序集做复用。同一个程序集被重复加载后会出现类型不匹配问题,直接导致ClaimsIdentityConverter这类依赖类型一致性的组件初始化失败,抛出类型初始化异常、空引用等连锁错误。
这也是为什么同样的代码在Winform、Azure应用/WebJobs/Functions等程序集加载逻辑简单的环境下可以正常运行,在ASP.NET中就会触发异常的原因。
修复方案
按以下几点修改加载逻辑即可解决问题:
- 增加前置过滤逻辑,仅对明确嵌入到资源中的程序集做处理,其余解析请求直接返回
null交还给CLR默认加载流程处理 - 读取资源流前先做判空校验,资源不存在时直接返回
null,不要强行执行后续逻辑 - 增加线程安全的缓存字典,已经加载过的程序集直接返回缓存实例,避免重复加载同个程序集导致类型不匹配
- 使用
using语法自动释放资源流,替代手动调用Close/Dispose,避免资源泄漏 - 尽可能早注册
AssemblyResolve事件,ASP.NET MVC场景下建议用PreApplicationStartMethod特性标记程序集启动方法,在应用初始化最早期完成注册,不要等到Global.asax的Application_Start阶段才注册,避免部分早期加载的程序集无法走到自定义加载逻辑。
修复后的参考代码:
using System.Collections.Concurrent; using System.Reflection; public static class DomainAssemblies { // 已加载程序集缓存,避免重复加载 private static readonly ConcurrentDictionary<string, Assembly> _assemblyCache = new ConcurrentDictionary<string, Assembly>(); public static void InitAssemblyLoader() { AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => { return LoadResource(e.Name, Assembly.GetExecutingAssembly()); }; } public static Assembly LoadResource(string assemblyFullName, Assembly sourceAssembly) { // 提取程序集简单名称,剔除版本、公钥标记、文化等后缀 string assemblyShortName = assemblyFullName.Split(',')[0]; // 优先返回缓存中已加载的程序集 if (_assemblyCache.TryGetValue(assemblyShortName, out Assembly cachedAssembly)) { return cachedAssembly; } // 配置你实际嵌入到资源中的程序集短名称列表,不在列表内的请求直接放行 HashSet<string> embeddedAssemblyNames = new HashSet<string> { // 示例:"MyCustomBusinessLib", "Common.Utils" 替换为你实际嵌入的DLL名称 }; if (embeddedAssemblyNames.Count > 0 && !embeddedAssemblyNames.Contains(assemblyShortName)) { return null; } string resourceName = $"{sourceAssembly.GetName().Name}.{assemblyShortName}.dll"; // 自动释放资源流,先判断资源是否存在 using (Stream resourceStream = sourceAssembly.GetManifestResourceStream(resourceName)) { if (resourceStream == null) { return null; } byte[] assemblyBytes = new byte[resourceStream.Length]; resourceStream.Read(assemblyBytes, 0, assemblyBytes.Length); Assembly loadedAssembly = Assembly.Load(assemblyBytes); // 写入缓存 _assemblyCache.TryAdd(assemblyShortName, loadedAssembly); return loadedAssembly; } } }
内容的提问来源于stack exchange,提问作者Tony Cheetham
相关产品推荐
相关产品推荐

