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
相关产品推荐
相关产品推荐

