.NET插件项目配置服务时AddAutoMapper方法找不到异常排查
场景
我的情况如下:
- 根项目(.NET 6.0 - Microsoft.NET.Sdk.Web)
- 插件项目(.NET 6.0 - Microsoft.NET.Sdk.Razor)
插件项目通过程序集加载的方式被加载到根项目中:
private void LoadModuleAssemblies(IMvcBuilder mvcBuilder) { var modules = Modules.Select(module => { if (!File.Exists(module.ModulePath)) { throw new Exception($"The DLL of module {module.Name} could not be found at ({module.ModulePath})"); } var loadContext = new ModuleLoadContext(module.ModulePath); var assembly = loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(module.ModulePath))); mvcBuilder.AddApplicationPart(assembly); module.Assembly = assembly; return module; }).ToList(); _hbsModulesAccessor.Modules = modules; }
ModuleLoadContext用于在不同上下文加载依赖程序集,以避免版本冲突:
public class ModuleLoadContext : AssemblyLoadContext { private readonly AssemblyDependencyResolver _resolver; public ModuleLoadContext(string pluginPath) { _resolver = new AssemblyDependencyResolver(pluginPath); } protected override Assembly? Load(AssemblyName assemblyName) { var existingAssembly = Default.Assemblies.FirstOrDefault(x => x.GetName().Name == assemblyName.Name && x.GetName().Version == assemblyName.Version); if (existingAssembly != null) { return existingAssembly; } string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); if (assemblyPath != null) { return LoadFromAssemblyPath(assemblyPath); } return null; } protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) { string? libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); if (libraryPath != null) { return LoadUnmanagedDllFromPath(libraryPath); } return IntPtr.Zero; } }
插件加载完成后,我尝试为插件配置服务:
IEnumerable<Type> moduleStartupConfigurations = AppDomainTypeFinder.FindClassesOfType<IHBSStartup>(); IOrderedEnumerable<IStartup?> instances = moduleStartupConfigurations .Select(s => Activator.CreateInstance(s) as IStartup) .OrderBy(s => s?.Order); foreach (IStartup? instance in instances) { if (instance == null) throw new Exception("Could not load plugin"); instance.ConfigureServices(services); }
AppDomainTypeFinder用于查找实现了IStartup的类,创建实例后会调用插件中定义的ConfigureServices方法。
问题
调用ConfigureServices方法时抛出以下异常:
Method not found: 'Microsoft.Extensions.DependencyInjection.IServiceCollection Microsoft.Extensions.DependencyInjection.ServiceCollectionExtensions.AddAutoMapper(Microsoft.Extensions.DependencyInjection.IServiceCollection, System.Reflection.Assembly[])'
插件中实现IStartup的类代码如下:
public class Startup : IStartup { public int Order { get; } = 10; public void Configure(IApplicationBuilder application) { // Add application parts } public void ConfigureServices(IServiceCollection services) { services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); } }
AddAutoMapper通过IServiceCollection注册自身服务,这个问题是否是由独立的AssemblyLoadContext导致的?
我是按照官方插件系统指南创建的这套机制。
这个问题确实和独立的AssemblyLoadContext有关,核心原因是程序集加载上下文不匹配:
- 根项目中的
AutoMapper相关程序集加载在默认上下文(Default)中,而插件程序集在自定义的ModuleLoadContext里。当插件调用services.AddAutoMapper(...)时,插件上下文无法找到正确的AddAutoMapper方法——要么是AutoMapper.Extensions.DependencyInjection程序集没被加载到插件上下文,要么是加载的版本和根项目不一致,导致方法签名不匹配。 - 另外,
AppDomain.CurrentDomain.GetAssemblies()仅返回默认加载上下文的程序集,插件上下文加载的程序集不会被包含,这也会导致后续AutoMapper无法找到插件内的映射配置类,但当前直接触发的是方法找不到的异常,所以重点还是加载上下文的问题。
解决办法:
1. 让插件上下文优先共享根项目的依赖程序集
修改ModuleLoadContext的Load方法,对AutoMapper、依赖注入扩展这类共享组件,直接从默认上下文加载,避免插件上下文重复加载:
protected override Assembly? Load(AssemblyName assemblyName) { // 指定需要共享的依赖前缀 var sharedDependencies = new[] { "AutoMapper", "AutoMapper.Extensions.DependencyInjection", "Microsoft.Extensions.DependencyInjection" }; if (sharedDependencies.Any(prefix => assemblyName.Name.StartsWith(prefix))) { return Default.LoadFromAssemblyName(assemblyName); } var existingAssembly = Default.Assemblies.FirstOrDefault(x => x.GetName().Name == assemblyName.Name && x.GetName().Version == assemblyName.Version); if (existingAssembly != null) { return existingAssembly; } string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); if (assemblyPath != null) { return LoadFromAssemblyPath(assemblyPath); } return null; }
2. 遍历所有加载上下文获取程序集
插件中调用AddAutoMapper时,不要只拿默认上下文的程序集,而是遍历所有AssemblyLoadContext收集程序集:
public void ConfigureServices(IServiceCollection services) { var allAssemblies = AssemblyLoadContext.All .SelectMany(ctx => ctx.Assemblies) .Distinct(); services.AddAutoMapper(allAssemblies); }
3. 统一插件和根项目的依赖版本
确保插件项目中AutoMapper和AutoMapper.Extensions.DependencyInjection的版本与根项目完全一致,避免因版本差异导致方法签名不匹配。
内容的提问来源于stack exchange,提问作者Jim
相关产品推荐
相关产品推荐

