.NET7动态加载程序集后IoC容器提示服务未注册问题求助
.NET7插件式方案中IoC容器无法解析已注册服务的问题
基于C# .NET7实现插件式解决方案,采用动态程序集加载机制。实际已在插件中注册TestPlugins.Plugin1.Service1服务,但IoC容器抛出异常:'No service for type 'TestPlugins.Plugin1.Service1' has been registered.'
解决方案架构
- TestPlugins.PluginInterface
- IPlugin.cs
- TestPlugins.Plugin1
- Plugin1.cs
- Service1.cs
- TestPlugins.Plugin2
- Plugin2.cs
- Service2.cs
- TestPlugins.App
- Program.cs
- Plugins/TestPlugins.Plugin1.deps.json
- Plugins/TestPlugins.Plugin1.dll
- Plugins/TestPlugins.Plugin2.deps.json
- Plugins/TestPlugins.Plugin2.dll
核心代码
接口代码(IPlugin.cs)
using Microsoft.Extensions.DependencyInjection; namespace TestPlugins.PluginInterface { public interface IPlugin { void Init(IServiceCollection services); void Run(IServiceProvider serviceProvider); } }
插件代码(Plugin1.cs)
using Microsoft.Extensions.DependencyInjection; using TestPlugins.PluginInterface; namespace TestPlugins.Plugin1 { public class Plugin1 : IPlugin { public void Init(IServiceCollection services) { Console.WriteLine("Plugin1 Initing."); services.AddTransient<IService1, Service1>(); Console.WriteLine("Plugin1 Inited."); } public void Run(IServiceProvider serviceProvider) { var service1 = serviceProvider.GetRequiredService<Service1>(); } } }
服务代码(Service1.cs)
namespace TestPlugins.Plugin1 { public interface IService1 { } public class Service1 : IService1 { public Service1() { Console.WriteLine("Service1 Ctor."); } } }
主程序代码(Program.cs)
using Microsoft.Extensions.Hosting; using System.Diagnostics; using System.Reflection; using System.Runtime.Loader; using System.Text; using TestPlugins.PluginInterface; namespace TestPlugins.App { internal class Program { private static async Task Main(string[] args) { Console.WriteLine("TestPlugins.App Starting."); using (IHost host = CreateHostBuilder(args).Build()) { foreach (var plugin in _plugins) { plugin.Run(host.Services); // 异常抛出位置 } await host.RunAsync().ConfigureAwait(false); } Console.WriteLine("TestPlugins.App Ending."); } private static readonly HashSet<IPlugin> _plugins = new(); private static IHostBuilder CreateHostBuilder(string[] args) { var assemblies = GetPlugins(); return Host.CreateDefaultBuilder(args) .ConfigureServices((context, services) => { foreach (var assembly in assemblies) { var obj = Activator.CreateInstance(assembly.Value) as IPlugin; if (obj != null) { obj.Init(services); _plugins.Add(obj); } } }); } private static Dictionary<Assembly, Type> GetPlugins() { var ipluginType = typeof(IPlugin); var pluginDir = Path.Combine(Environment.CurrentDirectory, "Plugins"); var pluginsDlls = Directory.GetFiles(pluginDir, "*.dll", SearchOption.AllDirectories).ToList(); return pluginsDlls.Select(dll => { return ReflectionsExcCatch(() => { var alc = new AssemblyLoadContext(dll); var asm = alc.LoadFromAssemblyPath(dll); var types = asm.GetTypes(); var ipluginTypes = types.Where(t => ipluginType.IsAssignableFrom(t) && t != ipluginType); return new KeyValuePair<Assembly, Type?>( asm, ipluginTypes.FirstOrDefault()); }); }) .Where(x => x.Value != null) .ToDictionary(x => x.Key, y => y.Value)!; } private static KeyValuePair<Assembly, Type?> ReflectionsExcCatch(Func<KeyValuePair<Assembly, Type?>> action) { try { return action(); } catch (ReflectionTypeLoadException ex) { var sb = new StringBuilder(); foreach (Exception exSub in ex.LoaderExceptions) { sb.AppendLine(exSub.Message); FileNotFoundException exFileNotFound = exSub as FileNotFoundException; if (exFileNotFound != null) { if (!string.IsNullOrEmpty(exFileNotFound.FusionLog)) { sb.AppendLine("Fusion Log:"); sb.AppendLine(exFileNotFound.FusionLog); } } sb.AppendLine(); } string errorMessage = sb.ToString(); Debug.WriteLine(errorMessage); throw; } } } }
异常位置
异常在主程序的以下代码处抛出:
using (IHost host = CreateHostBuilder(args).Build()) { foreach (var plugin in _plugins) { plugin.Run(host.Services); // Exception: 'No service for type 'TestPlugins.Plugin1.Service1' has been registered.' } await host.RunAsync().ConfigureAwait(false); }
解决思路
1. 修复注册与获取的类型不匹配
当前插件中注册的是接口与实现类的映射:services.AddTransient<IService1, Service1>(),但Run方法中直接尝试获取实现类类型Service1,IoC容器并未直接注册Service1类型,只注册了IService1的映射。
修改Plugin1的Run方法,改为通过接口获取服务:
public void Run(IServiceProvider serviceProvider) { var service1 = serviceProvider.GetRequiredService<IService1>(); }
2. 解决跨程序集加载上下文的类型隔离问题
由于插件程序集通过AssemblyLoadContext动态加载,主程序与插件中的IService1、Service1类型可能被CLR视为不同类型(即使全限定名相同,不同ALC加载的类型会被判定为不同类型)。
解决步骤:
- 将
IService1接口移到共享的TestPlugins.PluginInterface项目中,确保主程序和插件引用同一个接口类型,避免类型不匹配。 - 统一插件的程序集加载上下文:当前每个插件DLL都创建了独立的
AssemblyLoadContext,可改为创建一个共享ALC加载所有插件,确保插件间和主程序间的类型兼容性。
3. 验证服务注册的正确性
可在Init方法后添加调试代码,确认服务是否被正确注册:
public void Init(IServiceCollection services) { Console.WriteLine("Plugin1 Initing."); services.AddTransient<IService1, Service1>(); // 调试输出注册的服务 var serviceDescriptors = services.Where(sd => sd.ServiceType == typeof(IService1)).ToList(); Console.WriteLine($"Registered IService1 implementations: {serviceDescriptors.Count}"); Console.WriteLine("Plugin1 Inited."); }
内容的提问来源于stack exchange,提问作者AmD
相关产品推荐
相关产品推荐

