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

.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 12:47:57