.NET 6中如何为反射加载的模块实现依赖注入
我正尝试将若干程序集加载到主应用中,这些程序集需要使用主应用中已配置依赖注入(DI)的DbContext。主应用中通过以下代码从bin目录获取对应程序集:
var rulesList = Directory .GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll") .Select(x => System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(x)) .SelectMany(a => a .GetTypes() .Where(p => p.IsClass && p.IsAssignableTo(typeof(IMyRuleService)) ) .Select(Activator.CreateInstance) .Cast<IMyRuleService>() ) .ToList();
这段代码能正常加载程序集,但我不清楚如何将DbContext注入到这些程序集中。由于无法使用带参数的构造函数,我考虑过方法注入,但可能存在需注入DbContext数量不固定的问题,或许可以通过方法注入传入所有可用DbContext的数组,但不确定这是否是合理方案。
补充被调用程序集的代码:
public class RuleService : IRedFlagRuleService { private readonly MyDbContext _myDbContext ; public RuleService(MyDbContext myDbContext) { _myDbContext = myDbContext; } public Rule GetRuleInfo() { var ruleAssembly = System.Reflection.Assembly.GetExecutingAssembly(); var rule = _myDbContext.Rules.Where(a => a.AssemblyName == ruleAssembly.GetName().Name).FirstOrDefault() ?? new Rule(); return rule; } }
1. 利用DI容器自动创建实例(推荐)
不要手动用Activator.CreateInstance,而是把扫描到的服务类型注册到DI容器,让容器负责解析构造函数依赖(包括DbContext),再从容器中获取实例。
步骤示例(以ASP.NET Core为例):
- 扫描程序集并注册服务类型:
// 扫描bin目录下的程序集 var assemblies = Directory .GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll") .Select(x => AssemblyLoadContext.Default.LoadFromAssemblyPath(x)); // 遍历所有程序集,找到IMyRuleService的实现类并注册到DI foreach (var assembly in assemblies) { var ruleTypes = assembly.GetTypes() .Where(p => p.IsClass && p.IsAssignableTo(typeof(IMyRuleService))); foreach (var type in ruleTypes) { // 根据需求选择生命周期:AddScoped/AddTransient/AddSingleton services.AddScoped(typeof(IMyRuleService), type); } }
- 在需要使用的地方,通过
IServiceProvider获取所有实例:
// 注入IServiceProvider或者在构造函数中接收IEnumerable<IMyRuleService> var rulesList = serviceProvider.GetServices<IMyRuleService>().ToList();
这种方式完全利用DI容器的能力,自动处理构造函数中的DbContext注入,无需手动管理依赖。
2. 手动解析构造函数依赖创建实例
如果不想批量注册到DI容器,可以在找到类型后,用DI容器解析构造函数需要的参数,再调用带参构造创建实例:
// 假设已经获取到IServiceProvider实例 var serviceProvider = ...; var rulesList = Directory .GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll") .Select(x => AssemblyLoadContext.Default.LoadFromAssemblyPath(x)) .SelectMany(a => a .GetTypes() .Where(p => p.IsClass && p.IsAssignableTo(typeof(IMyRuleService))) .Select(type => { // 获取类型的构造函数(这里假设只有一个带DbContext的构造) var constructor = type.GetConstructor(new[] { typeof(MyDbContext) }); if (constructor == null) { // 处理无匹配构造的情况,比如抛出异常或返回null return null; } // 从DI容器获取DbContext实例 var dbContext = serviceProvider.GetRequiredService<MyDbContext>(); // 创建实例并转换为IMyRuleService return (IMyRuleService)constructor.Invoke(new[] { dbContext }); }) .Where(service => service != null) ) .ToList();
这种方式适合不需要长期注册服务,仅临时创建实例的场景,但需要自己处理构造函数的匹配逻辑。
3. 方法注入(备选方案)
如果必须使用无参构造创建实例,可以在IMyRuleService接口中定义初始化方法,创建实例后手动传入DbContext:
首先修改接口:
public interface IMyRuleService { void Initialize(MyDbContext dbContext); // 其他原有方法 }
然后在实现类中实现该方法:
public class RuleService : IRedFlagRuleService { private MyDbContext _myDbContext; // 保留无参构造 public RuleService() { } public void Initialize(MyDbContext dbContext) { _myDbContext = dbContext; } public Rule GetRuleInfo() { if (_myDbContext == null) { throw new InvalidOperationException("未初始化DbContext"); } var ruleAssembly = Assembly.GetExecutingAssembly(); var rule = _myDbContext.Rules.Where(a => a.AssemblyName == ruleAssembly.GetName().Name).FirstOrDefault() ?? new Rule(); return rule; } }
最后在主应用中创建实例后调用初始化方法:
var dbContext = serviceProvider.GetRequiredService<MyDbContext>(); var rulesList = Directory .GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll") .Select(x => AssemblyLoadContext.Default.LoadFromAssemblyPath(x)) .SelectMany(a => a .GetTypes() .Where(p => p.IsClass && p.IsAssignableTo(typeof(IMyRuleService))) .Select(type => { var service = (IMyRuleService)Activator.CreateInstance(type); service.Initialize(dbContext); return service; }) ) .ToList();
如果有多个DbContext,可以修改Initialize方法接收IEnumerable<DbContext>或者多个具体的DbContext类型,根据实际需求调整。
内容的提问来源于stack exchange,提问作者John S

