ASP.NET Core注册Planning依赖注入无法解析System.Int32错误
核心结论
构造函数中可以同时声明依赖注入的接口服务与其他类型参数,但默认DI注册逻辑要求构造函数所有参数都能从容器中解析,非服务类型的业务参数如果没有手动提供取值,就会触发你遇到的解析异常。
异常产生原因
ASP.NET Core内置依赖注入容器的默认实例化规则是:使用AddScoped<T>()这类不带自定义工厂的注册方式时,容器会自动查找目标类型的构造函数,要求选中的构造函数的所有参数都必须是已经在容器中注册、可被解析的服务。
你的Planning类构造函数中,除7个DAL接口属于已注册的服务外,int id、string name、DateTime startDate、DateTime? endDate、Participant leader都是业务动态参数,没有在容器中注册对应取值,容器无法凭空生成这些参数的值,因此在尝试解析第一个非服务参数System.Int32时直接抛出异常。
额外说明:你当前将Planning这种带动态业务属性的实体模型直接注册为DI服务的设计本身不符合DI的使用规范——DI容器用于管理无状态的服务、仓储、逻辑组件的依赖关系,不适合承载业务运行时动态生成的、带可变状态的实体对象,这类设计很容易引发生命周期错乱、状态污染问题。
修复方案
你可以根据自己的业务场景选以下任意一种方案:
方案1:拆分DI专用构造函数(保留私有setter设计)
给Planning类新增一个仅包含依赖注入服务参数的构造函数,用[ActivatorUtilitiesConstructor]特性标记这个构造函数供DI容器使用,原有的全参数构造函数保留给业务代码手动创建实例使用。
示例代码:// DI容器使用的构造函数,仅包含可被容器解析的服务参数 [ActivatorUtilitiesConstructor] public Planning( IPlanningDAL planningDAL, IPlanningParticipantDAL planningParticipantDAL, ICategoryCollectionDAL categoryCollectionDAL, ITaskCollectionDAL taskCollectionDAL, ITaskDAL taskDAL, IParticipantDAL participantDAL, ICategoryDAL categoryDAL) { this.planningDAL = planningDAL; this.planningParticipantDAL = planningParticipantDAL; this.categoryCollectionDAL = categoryCollectionDAL; this.taskCollectionDAL = taskCollectionDAL; this.taskDAL = taskDAL; this.participantDAL = participantDAL; this.categoryDAL = categoryDAL; } // 业务代码手动创建实例时使用的全参数构造函数 public Planning( IPlanningDAL planningDAL, IPlanningParticipantDAL planningParticipantDAL, ICategoryCollectionDAL categoryCollectionDAL, ITaskCollectionDAL taskCollectionDAL, ITaskDAL taskDAL, IParticipantDAL participantDAL, ICategoryDAL categoryDAL, int id, string name, DateTime startDate, DateTime? endDate, Participant leader) : this(planningDAL, planningParticipantDAL, categoryCollectionDAL, taskCollectionDAL, taskDAL, participantDAL, categoryDAL) { Id = id; Name = name; StartDate = startDate; EndDate = endDate; Leader = leader; }这种方式不需要修改你原来的私有setter设计,也不需要调整服务注册代码。
方案2:不注册实体到DI,手动创建实例时自动解析依赖(推荐)
移除builder.Services.AddScoped<Planning>();这段注册代码,DI容器只注册各个DAL、服务类等无状态组件。需要创建Planning实例时,通过ActivatorUtilities.CreateInstance方法手动传入业务参数,剩余的服务参数会由容器自动解析注入。
示例代码(在业务服务/控制器中创建实例):public class PlanningService { private readonly IServiceProvider _serviceProvider; // 注入IServiceProvider用于解析依赖 public PlanningService(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public Planning CreateNewPlanning(int id, string name, DateTime startDate, DateTime? endDate, Participant leader) { // 传入业务参数,其余DAL依赖自动从容器解析 var planningInstance = ActivatorUtilities.CreateInstance<Planning>(_serviceProvider, id, name, startDate, endDate, leader); return planningInstance; } }这种方式符合DI的设计规范,也不需要修改
Planning类的原有构造函数逻辑。方案3:注册时使用工厂委托传入固定参数(仅适用于固定值场景)
如果Planning的非服务参数是全局固定值,可以在注册时通过工厂委托手动从容器获取服务、传入固定参数。但这个方案不适合你当前的场景——你的id、name等参数是每个实例不同的动态业务值,用这个方式会导致所有请求拿到的都是同一个固定参数的实例。
示例代码:builder.Services.AddScoped(sp => { return new Planning( sp.GetRequiredService<IPlanningDAL>(), sp.GetRequiredService<IPlanningParticipantDAL>(), sp.GetRequiredService<ICategoryCollectionDAL>(), sp.GetRequiredService<ITaskCollectionDAL>(), sp.GetRequiredService<ITaskDAL>(), sp.GetRequiredService<IParticipantDAL>(), sp.GetRequiredService<ICategoryDAL>(), 1, "固定规划名称", DateTime.Now, null, new Participant() ); });
内容的提问来源于stack exchange,提问作者abus

