具有以下服务构造函数
public class Service : IService
{
public Service(IOtherService service1, IAnotherOne service2, string arg)
{
}
}
使用 .NET Core IOC 机制传递参数有哪些选择
services.AddSingleton<IOtherService , OtherService>();
services.AddSingleton<IAnotherOne , AnotherOne>();
services.AddSingleton<IService>(x =>
new Service(
services.BuildServiceProvider().GetService<IOtherService>(),
services.BuildServiceProvider().GetService<IAnotherOne >(),
""));
还有其他方法吗?
工厂委托的表达式参数(在本例中为 x)是 IServiceProvider
。
使用它来解决依赖关系:
_serviceCollection.AddSingleton<IService>(x =>
new Service(x.GetRequiredService<IOtherService>(),
x.GetRequiredService<IAnotherOne>(),
""));
工厂委托是延迟调用。每当要解析类型时,它将完整的提供者作为委托参数传递。
实现此目的的推荐方法是使用 Options pattern - 请注意,这适用于任何 .NET Core/5 应用程序,而不仅仅是 ASP.NET Core。但是在某些用例中它是不切实际的(例如,当参数仅在运行时知道,而不是在启动/编译时知道时)或者您需要动态替换依赖项。
当您需要替换单个依赖项(无论是字符串、整数或其他类型的依赖项)或使用仅接受字符串/整数参数并且需要运行时参数的 3rd-party 库时,它非常有用。
您可以尝试 CreateInstance<T>(IServiceProvider, Object[])
作为快捷方式,而不是手动解决每个依赖项:
_serviceCollection.AddSingleton<IService>(x =>
ActivatorUtilities.CreateInstance<Service>(x, "");
);
传递给服务构造函数的参数(传递给 CreateInstance<T>
/CreateInstance
的 object[]
参数)允许您指定应直接注入的参数,而不是从服务提供者解析的参数。它们在出现时从左到右应用(即第一个字符串将替换为要实例化的类型的第一个字符串类型参数)。
ActivatorUtilities.CreateInstance<Service>
在许多地方用于解析服务并替换此单一激活的默认注册之一。
例如,如果您有一个名为 MyService
的类,并且它具有 IOtherService
、ILogger<MyService>
作为依赖项,并且您想要解析服务但将 IOtherService
的默认服务(例如 OtherServiceA
)替换为 { 6},您可以执行以下操作:
myService = ActivatorUtilities.CreateInstance<Service>(serviceProvider,
new OtherServiceB());
然后 IOtherService
的第一个参数将被注入 OtherServiceB
,而不是 OtherServiceA
- 但其余参数将来自服务提供者。
当您有许多依赖项并且只想特别对待一个依赖项时,这很有帮助(即用在请求期间或为特定用户配置的值替换特定于数据库的提供程序,这是您仅在运行时和/或请求期间才知道的- 不是在构建/启动应用程序时)。
如果性能是一个问题,您可以使用 ActivatorUtilities.CreateFactory(Type, Type[])
创建工厂方法。 GitHub reference 和 benchmark。
当类型被非常频繁地解析时(例如在 SignalR 和其他高请求场景中),这很有用。基本上,您会创建一个 ObjectFactory
通过
var myServiceFactory = ActivatorUtilities.CreateFactory(typeof(MyService), new object[] { typeof(IOtherService), });
然后缓存它(作为变量等)并在需要时调用它:
MyService myService = myServiceFactory(serviceProvider, myServiceOrParameterTypeToReplace);
这一切也适用于原始类型 - 这是我测试过的一个例子:
class Program
{
static void Main(string[] args)
{
var services = new ServiceCollection();
services.AddTransient<HelloWorldService>();
services.AddTransient(p => p.ResolveWith<DemoService>("Tseng", "Stackoverflow"));
var provider = services.BuildServiceProvider();
var demoService = provider.GetRequiredService<DemoService>();
Console.WriteLine($"Output: {demoService.HelloWorld()}");
Console.ReadKey();
}
}
public class DemoService
{
private readonly HelloWorldService helloWorldService;
private readonly string firstname;
private readonly string lastname;
public DemoService(HelloWorldService helloWorldService, string firstname, string lastname)
{
this.helloWorldService = helloWorldService ?? throw new ArgumentNullException(nameof(helloWorldService));
this.firstname = firstname ?? throw new ArgumentNullException(nameof(firstname));
this.lastname = lastname ?? throw new ArgumentNullException(nameof(lastname));
}
public string HelloWorld()
{
return this.helloWorldService.Hello(firstname, lastname);
}
}
public class HelloWorldService
{
public string Hello(string name) => $"Hello {name}";
public string Hello(string firstname, string lastname) => $"Hello {firstname} {lastname}";
}
// Just a helper method to shorten code registration code
static class ServiceProviderExtensions
{
public static T ResolveWith<T>(this IServiceProvider provider, params object[] parameters) where T : class =>
ActivatorUtilities.CreateInstance<T>(provider, parameters);
}
印刷
输出:Hello Tseng Stackoverflow
.AddControllersAsServices
,它将 ControllerActivatorProvider
替换为 ServiceBasedControllerActivator
new
它时,您需要传递每个构造函数参数。使用 ActivatorUtils,您只传递不应来自容器的参数。当 string/int 参数是在运行时而不是在启动时确定时(这是配置 DI 容器并且之后无法更改),这尤其有用。该代码用于演示,以表明您可以注入运行时参数(甚至是服务实例),而其余的来自 DI 容器
provider.GetRequiredService<T>()
给定类的每个依赖项,当您向构造函数添加附加参数时,这也会破坏您的代码
MyServiceFactory
,注入 IServiceProvider
,然后从那里解决它(ActivatorUtils 需要 ServiceProvder 来解决依赖关系)。如果可以从服务中获取参数(即IHttpContextAccessor
,则可以在注册时调用工厂方法:services.AddScoped<MyService>(provider => provider.GetRequiredService<MyServiceFactory>().Create())
如果您对新服务感到不舒服,可以使用 Parameter Object
模式。
所以把字符串参数提取成自己的类型
public class ServiceArgs
{
public string Arg1 {get; set;}
}
构造函数现在看起来像
public Service(IOtherService service1,
IAnotherOne service2,
ServiceArgs args)
{
}
和设置
_serviceCollection.AddSingleton<ServiceArgs>(_ => new ServiceArgs { Arg1 = ""; });
_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService, Service>();
第一个好处是,如果您需要更改 Service 构造函数并向其添加新服务,则无需更改 new Service(...
调用。另一个好处是设置更干净。
对于具有一两个参数的构造函数,这可能太多了。
您也可以使用此过程注入依赖项
_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService>(x=>new Service( x.GetService<IOtherService>(), x.GetService<IAnotherOne >(), "" ));
.WithParameter("argument", "");
Microsoft.Extensions.DependencyInjection.Abstractions
包的一部分(因此没有特定于容器的依赖项)