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

运行时动态依赖注入:如何正确注册带参数的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容器无法正确识别类型。请问该如何解决?


解决方案

核心问题分析

你的代码存在两个关键问题:

  1. 提前调用services.BuildServiceProvider()会创建临时DI容器实例,和最终应用使用的容器不一致,导致依赖注入失效,还会造成资源浪费。
  2. 使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 12:13:17