You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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特殊的多上下文加载机制下被放大:

  1. 无差别拦截所有程序集解析请求:AssemblyResolve事件触发时仅代表CLR在当前探测路径下未找到目标程序集,原代码没有做任何过滤,对所有解析请求都强行尝试从执行程序集的嵌入资源中查找对应DLL。ASP.NET本身有多层程序集探测逻辑(包括GAC、站点bin目录、临时ASP.NET文件目录等),很多时候触发AssemblyResolve只是探测流程的一环,后续框架本身可以正常加载目标程序集。但原代码在资源不存在时,GetManifestResourceStream会返回null,后续直接访问resFilestream.Length会抛出空引用异常,直接打断CLR的默认加载流程,导致本来可以被正常加载的Microsoft.IdentityModel等依赖加载失败。
  2. 无缓存重复加载+加载上下文不匹配:原代码每次请求都调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 01:24:25