英文:
Exception 'No service for type 'TestPlugins.Plugin1.Service1' has been registered.' after dynamic assembly loading in .NET7
问题
I have a plugin based solution using C# on .NET 7, using dynamic assembly loading.
Actually, the IoC container says that the service has not been registered, but in fact it has!
Exception 'No service for type 'TestPlugins.Plugin1.Service1' has
been registered.'
The solution architecture is as follows:
- 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
The interface:
using Microsoft.Extensions.DependencyInjection;
namespace TestPlugins.PluginInterface
{
public interface IPlugin
{
void Init(IServiceCollection services);
void Run(IServiceProvider serviceProvider);
}
}
The plugin:
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>();
}
}
}
The service:
namespace TestPlugins.Plugin1
{
public interface IService1
{ }
public class Service1 : IService1
{
public Service1()
{
Console.WriteLine("Service1 Ctor.");
}
}
}
The program:
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.");
}
// ... (remaining code)
}
}
The exception occurs there:
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);
}
This is the repo with the solution, you can check it.
Any idea to solve the problem.
Best regards
英文:
I have a plugin based solution using C# on .NET 7, using dynamic assembly loading.
Actually, the IoC container says that the service has not been registered, but in fact it has!
> Exception 'No service for type 'TestPlugins.Plugin1.Service1' has
> been registered.'
The solution architecture like:
- 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
The interface:
using Microsoft.Extensions.DependencyInjection;
namespace TestPlugins.PluginInterface
{
public interface IPlugin
{
void Init(IServiceCollection services);
void Run(IServiceProvider serviceProvider);
}
}
The plugin:
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>();
}
}
}
The service:
namespace TestPlugins.Plugin1
{
public interface IService1
{ }
public class Service1 : IService1
{
public Service1()
{
Console.WriteLine("Service1 Ctor.");
}
}
}
The program:
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;
}
}
}
}
The exception occurs there:
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);
}
This is the repo with the solution, you can check it.
Any idea to solve the problem.
Best regards
答案1
得分: 1
这与动态加载程序集无关。问题仅仅是由于 Plugin1
中的编程错误引起的。
您正在注册:
services.AddTransient<IService1, Service1>();
但是您在解析时使用了:
serviceProvider.GetRequiredService<Service1>();
换句话说,您正在注册 IService1
抽象,但正在解析 Service1
实现。您应该将解析更改为:
serviceProvider.GetRequiredService<IService1>();
英文:
This has little to do with the fact that you are loading assemblies dynamically. The problem is simply caused by a programming error in Plugin1
.
You are registering:
services.AddTransient<IService1, Service1>();
But you are resolving:
serviceProvider.GetRequiredService<Service1>();
In other words, you are registering the IService1
abstraction, but are resolving Service1
implementation. You should change the resolve to:
serviceProvider.GetRequiredService<IService1>();
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论