运行时动态依赖注入:如何正确注册带参数的ICqrsQuery实现类
问题描述
我有以下两个实现ICqrsQuery<T>的查询类:
GetTestByIdQuery
public class GetTestByIdQuery : ICqrsQuery<JustTestDto> { public Guid JustTestId { get; set; } public GetTestByIdQuery(Guid justTestId) { JustTestId = justTestId; } }
ListTestsQuery
public class ListTestsQuery : ICqrsQuery<List<JustTestDto>> { }
我想要通过反射获取所有实现ICqrsQuery<T>接口的类,在运行时动态注册到DI容器中。我写了如下扩展方法:
public static IServiceCollection AddCqrsQueries(this IServiceCollection services) { var assembly = Assembly.GetExecutingAssembly(); var queries = assembly.GetTypes() .Where(t => t.IsClass && !t.IsAbstract && t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICqrsQuery<>))); foreach (var query in queries) { var interfaces = query.GetInterfaces(); foreach (var @interface in interfaces) { if (@interface.IsGenericType && @interface.GetGenericTypeDefinition() == typeof(ICqrsQuery<>)) { var constructor = query.GetConstructors().FirstOrDefault(); var constParameters = constructor?.GetParameters() ?? null; var parameterValues = constParameters != null ? new object[constParameters.Length] : null; if (parameterValues?.Length > 0 && parameterValues != null) { for (int i = 0; i < constParameters.Length; i++) { parameterValues[i] = services.BuildServiceProvider() .GetService(constParameters[i].ParameterType); } services.AddTransient(serviceProvider => { var instance = Activator.CreateInstance(query, parameterValues); return instance; }); } else { ///no parameters services.AddTransient(query); } } } } return services; }
这段代码对无参构造的ListTestsQuery能正常运行,但对带构造参数的GetTestByIdQuery无法正常工作。问题出在var instance = Activator.CreateInstance(query, parameterValues);这一行,它的返回类型是object?,虽然运行时实际是GetTestByIdQuery类型,但必须显式转换才能正常工作,否则DI容器无法正确识别类型。请问该如何解决?
解决方案
核心问题分析
你的代码存在两个关键问题:
- 提前调用
services.BuildServiceProvider()会创建临时DI容器实例,和最终应用使用的容器不一致,导致依赖注入失效,还会造成资源浪费。 - 使用
Activator.CreateInstance返回object时,未明确指定注册的服务类型(即ICqrsQuery<T>),DI容器无法正确关联实现类与接口。
修正后的代码
public static IServiceCollection AddCqrsQueries(this IServiceCollection services) { var assembly = Assembly.GetExecutingAssembly(); var queryTypes = assembly.GetTypes() .Where(t => t.IsClass && !t.IsAbstract && t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICqrsQuery<>))); foreach (var queryType in queryTypes) { // 获取当前查询类实现的所有ICqrsQuery<T>接口 var queryInterfaces = queryType.GetInterfaces() .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICqrsQuery<>)); foreach (var queryInterface in queryInterfaces) { // 使用传入的服务提供者解析构造参数,避免提前构建容器 services.AddTransient(queryInterface, serviceProvider => { var constructor = queryType.GetConstructors().FirstOrDefault(); if (constructor == null) { throw new InvalidOperationException($"类型 {queryType.FullName} 没有可访问的构造函数"); } // 动态解析构造参数 var parameters = constructor.GetParameters() .Select(p => serviceProvider.GetService(p.ParameterType)) .ToArray(); // 创建实例并转换为对应的接口类型 var instance = Activator.CreateInstance(queryType, parameters); return Convert.ChangeType(instance, queryInterface); }); // 可选:注册查询类自身类型(如果需要直接解析查询类实例) services.AddTransient(queryType, serviceProvider => { var constructor = queryType.GetConstructors().FirstOrDefault(); if (constructor == null) { throw new InvalidOperationException($"类型 {queryType.FullName} 没有可访问的构造函数"); } var parameters = constructor.GetParameters() .Select(p => serviceProvider.GetService(p.ParameterType)) .ToArray(); return Activator.CreateInstance(queryType, parameters); }); } } return services; }
关键改进点
- 避免提前构建ServiceProvider:在注册委托中使用传入的
serviceProvider解析依赖,确保使用最终的DI容器实例,保证依赖注入的正确性。 - 明确接口与实现类的关联:调用
AddTransient(queryInterface, ...)时,指定服务类型为ICqrsQuery<T>,并用Convert.ChangeType将实例转换为接口类型,让DI容器能正确识别服务类型。 - 可选注册自身类型:如果需要直接解析查询类本身(比如
GetTestByIdQuery),额外注册自身类型,满足不同的解析需求。 - 异常处理:添加构造函数不存在的异常抛出,便于调试定位问题。
补充说明
对于GetTestByIdQuery这类需要外部传入参数(如Guid justTestId)的查询类,CQRS场景中通常由调用方传入参数,而非依赖DI容器解析。如果确实需要DI注入部分参数,同时外部传入其他参数,建议使用工厂模式创建查询实例:
public interface IQueryFactory { GetTestByIdQuery CreateGetTestByIdQuery(Guid testId); // 其他查询工厂方法 } public class QueryFactory : IQueryFactory { // 可注入需要的依赖 public GetTestByIdQuery CreateGetTestByIdQuery(Guid testId) { return new GetTestByIdQuery(testId); } }
注册工厂:
services.AddTransient<IQueryFactory, QueryFactory>();
调用方通过工厂创建带参数的查询实例,同时满足依赖注入需求。
内容的提问来源于stack exchange,提问作者ibr
相关产品推荐
相关产品推荐

