.NET Core DI使用工厂方法时的容器校验问题
服务定义
class OtherService : IOtherService { } public class ParameterizedService { public ParameterizedService(IOtherService otherService) => Console.WriteLine("无参构造函数"); public ParameterizedService(IOtherService otherService, string parameter) => Console.WriteLine($"带参数的构造函数: {parameter}"); }
场景1:直接注册服务(校验生效)
当直接注册ParameterizedService但未注册其依赖IOtherService时:
var services = new ServiceCollection(); // 未注册IOtherService,导致无法实例化ParameterizedService // services.AddSingleton<IOtherService, OtherService>(); services.AddSingleton<ParameterizedService>(); // 执行该行会抛出错误 using var provider = services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true, });
此时容器校验失败,调用BuildServiceProvider()时抛出异常:
Error while validating the service descriptor 'ServiceType: ParameterizedService
Lifetime: Singleton
ImplementationType: ParameterizedService':
No constructor for type 'ParameterizedService' can be instantiated using services from the service container and default values.
这种提前校验的效果很理想。
场景2:使用工厂方法注册(校验失效)
如果要使用带string参数的构造函数,不得不改用工厂方法:
var services = new ServiceCollection(); // 仍未注册IOtherService // services.AddSingleton<IOtherService, OtherService>(); services.AddSingleton<ParameterizedService>(sp => { return ActivatorUtilities.CreateInstance<ParameterizedService>(sp, "来自工厂的问候"); }); // 该行执行成功无报错,但后续获取服务时会崩溃 using var provider = services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true, });
这种情况下,BuildServiceProvider()能成功执行,但后续从容器获取ParameterizedService时应用会崩溃,校验失效的结果并不理想。
疑问
在使用工厂方法注册时,有没有办法让容器知道ParameterizedService依赖IOtherService,从而保持校验生效?或者有没有其他注册方式,既能保留显式传递parameter参数的能力,又能让容器正常执行校验?
方案1:手动触发依赖校验
在工厂注册逻辑中,主动从服务容器获取IOtherService,容器在构建时会自动校验该依赖是否存在:
var services = new ServiceCollection(); // services.AddSingleton<IOtherService, OtherService>(); services.AddSingleton<ParameterizedService>(sp => { // 显式获取依赖,触发容器校验 var otherService = sp.GetRequiredService<IOtherService>(); return new ParameterizedService(otherService, "来自工厂的问候"); }); // 未注册IOtherService时,BuildServiceProvider会直接抛出异常 using var provider = services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true, });
这种方式简单直接,通过主动获取依赖让容器提前检查缺失项,避免运行时崩溃。
方案2:使用ActivatorUtilities.CreateFactory声明依赖
通过ActivatorUtilities.CreateFactory预定义构造函数的参数类型,让容器识别到所需依赖,从而触发校验:
var services = new ServiceCollection(); // services.AddSingleton<IOtherService, OtherService>(); // 创建工厂时明确构造函数参数类型,告知容器需要IOtherService var factory = ActivatorUtilities.CreateFactory( typeof(ParameterizedService), new[] { typeof(IOtherService), typeof(string) }); services.AddSingleton<ParameterizedService>(sp => { return (ParameterizedService)factory(sp, new object[] { "来自工厂的问候" }); }); // 未注册IOtherService时,BuildServiceProvider会抛出异常 using var provider = services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true, });
CreateFactory会向容器暴露依赖信息,容器在构建阶段就能完成校验。
方案3:封装参数为配置类(推荐)
如果string参数属于配置项,可将其封装为配置类并注册到容器,这样就能直接使用构造函数注入,同时保留容器的自动校验能力:
// 定义配置类 public class ParameterizedServiceSettings { public string Parameter { get; set; } } // 修改ParameterizedService的构造函数 public class ParameterizedService { public ParameterizedService(IOtherService otherService, ParameterizedServiceSettings settings) => Console.WriteLine($"带参数的构造函数: {settings.Parameter}"); } // 注册代码 var services = new ServiceCollection(); // services.AddSingleton<IOtherService, OtherService>(); services.AddSingleton(new ParameterizedServiceSettings { Parameter = "来自配置的问候" }); services.AddSingleton<ParameterizedService>(); // 未注册IOtherService时,BuildServiceProvider会抛出异常 using var provider = services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true, });
这种方式符合依赖注入的最佳实践,既能清晰管理参数,又能让容器自动完成所有依赖的校验。
内容的提问来源于stack exchange,提问作者Serg

