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

ASP.Net Core Razor应用动态加载程序集无法卸载求助

动态加载Razor应用程序集的卸载解决方案

你的问题核心在于:ASP.NET Core的路由系统、视图引擎会缓存动态加载插件的路由和视图信息,仅移除ApplicationPart和卸载AssemblyLoadContext(ALC)不足以清除这些缓存,导致仍能访问插件页面。以下是可行的分步解决方案:

1. 完整移除插件的ApplicationPart并刷新缓存

加载插件时务必记录所有添加的ApplicationPart实例,卸载时不仅从ApplicationPartManager中移除,还要触发内部缓存更新:

// 加载插件时保存添加的部件(建议全局或插件实例级存储)
var addedParts = new List<ApplicationPart>();
var assemblyPart = new AssemblyPart(assembly);
var razorPart = new CompiledRazorAssemblyPart(assembly);
_builder.AddApplicationPart(assemblyPart);
_builder.AddApplicationPart(razorPart);
addedParts.Add(assemblyPart);
addedParts.Add(razorPart);

// 卸载时执行
var partManager = app.Services.GetRequiredService<ApplicationPartManager>();
foreach (var part in addedParts)
{
    partManager.ApplicationParts.Remove(part);
}

// 触发ApplicationPartManager内部缓存更新(反射调用)
var updateMethod = typeof(ApplicationPartManager).GetMethod("UpdateCache", BindingFlags.NonPublic | BindingFlags.Instance);
updateMethod?.Invoke(partManager, null);

2. 清理插件的路由缓存

动态加载的插件路由会被加入ASP.NET Core端点数据源缓存,需手动移除对应条目:

var endpointDataSources = app.Services.GetServices<EndpointDataSource>();
foreach (var dataSource in endpointDataSources)
{
    if (dataSource is DefaultEndpointDataSource defaultDs)
    {
        // 反射获取端点列表并移除插件相关路由
        var endpointsField = typeof(DefaultEndpointDataSource).GetField("_endpoints", BindingFlags.NonPublic | BindingFlags.Instance);
        if (endpointsField != null)
        {
            var endpoints = (List<Endpoint>)endpointsField.GetValue(defaultDs);
            endpoints.RemoveAll(endpoint => 
                endpoint.Metadata.OfType<ControllerActionDescriptor>()
                          .Any(desc => desc.ControllerTypeInfo.Assembly == assembly));
        }
    }
}

// 刷新路由匹配缓存
var routeMatcherCache = app.Services.GetRequiredService<IRouteMatcherCache>();
var clearMethod = typeof(RouteMatcherCache).GetMethod("Clear", BindingFlags.NonPublic | BindingFlags.Instance);
clearMethod?.Invoke(routeMatcherCache, null);

3. 清除Razor视图编译与位置缓存

Razor视图编译后会被缓存,需清理对应插件的视图缓存条目:

// 清理视图编译缓存
var viewCompilerCache = app.Services.GetRequiredService<IViewCompilerCache>();
var cacheField = typeof(ViewCompilerCache).GetField("_cache", BindingFlags.NonPublic | BindingFlags.Instance);
if (cacheField != null)
{
    var cache = (Dictionary<string, object>)cacheField.GetValue(viewCompilerCache);
    cache.RemoveAll(kv => kv.Value.GetType().Assembly == assembly);
}

// 清理视图位置缓存
var viewLocationCache = app.Services.GetRequiredService<IViewLocationCache>();
var locationCacheField = typeof(ViewLocationCache).GetField("_cache", BindingFlags.NonPublic | BindingFlags.Instance);
if (locationCacheField != null)
{
    var cache = (Dictionary<string, string>)locationCacheField.GetValue(viewLocationCache);
    cache.RemoveAll(kv => kv.Value.Contains(assembly.GetName().Name));
}

4. 确保ALC完全卸载

在调用Unload()前,必须释放所有持有插件程序集引用的对象(如DI中的服务实例、未完成的请求上下文),然后强制触发垃圾回收:

// 释放所有插件相关的DI服务实例(若插件注册了自定义服务)
var serviceProvider = app.Services;
// 示例:若使用了Scoped服务,需确保所有作用域已释放
// ...

// 卸载ALC
_loadContext.Unload();

// 强制垃圾回收,确保无残留引用
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

额外优化建议

  • 自定义隔离机制:加载插件时使用独立的ServiceCollection构建子容器,避免插件服务与主应用DI容器交叉引用,卸载时直接释放子容器。
  • 路由隔离:自定义PluginEndpointDataSource,加载插件时将其添加到端点数据源集合,卸载时直接移除该数据源,无需反射操作。

内容的提问来源于stack exchange,提问作者DevCoder

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 05:42:56