简体   繁体   English

.NET Core DI,将参数传递给构造函数的方法

[英].NET Core DI, ways of passing parameters to constructor

Having the following service constructor具有以下服务构造函数

public class Service : IService
{
     public Service(IOtherService service1, IAnotherOne service2, string arg)
     {    
     }
}

What are the choices of passing the parameters using .NET Core IOC mechanism .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 >(),
        ""));

Is there any other way ?还有其他方法吗?

The expression parameter ( x in this case) of the factory delegate is an IServiceProvider .工厂委托的表达式参数(在本例中为x )是一个IServiceProvider

Use that to resolve the dependencies:使用它来解决依赖关系:

_serviceCollection.AddSingleton<IService>(x => 
    new Service(x.GetRequiredService<IOtherService>(),
                x.GetRequiredService<IAnotherOne>(), 
                ""));

The factory delegate is a delayed invocation.工厂委托是延迟调用。 Whenever the type is to be resolved, it will pass the completed provider as the delegate parameter.每当要解析类型时,它都会将完成的提供程序作为委托参数传递。

The recommended way to achieve this is to use the Options pattern - note that this applies to any .NET Core/5 application, not just ASP.NET Core.实现此目的的推荐方法是使用选项模式- 请注意,这适用于任何 .NET Core/5 应用程序,而不仅仅是 ASP.NET Core。 But there are use cases where it's impractical (eg when parameters are only known at runtime, not at startup/compile-time) or you need to dynamically replace a dependency.但是有些用例是不切实际的(例如,当参数仅在运行时已知,而不是在启动/编译时已知时)或者您需要动态替换依赖项。

It's very useful when you need to replace a single dependency (be it a string, integer or another type of dependency) or when using a 3rd-party library which accepts only string/integer parameters and you require runtime parameters.当您需要替换单个依赖项(无论是字符串、整数还是其他类型的依赖项)或使用仅接受字符串/整数参数且需要运行时参数的第 3 方库时,它非常有用。

You could try CreateInstance<T>(IServiceProvider, Object[]) as a shortcut rather than resolving every single dependency manually:您可以尝试使用CreateInstance<T>(IServiceProvider, Object[])作为快捷方式,而不是手动解析每个依赖项:

_serviceCollection.AddSingleton<IService>(x => 
    ActivatorUtilities.CreateInstance<Service>(x, "");
);

The parameters to pass to your service's constructor (the object[] parameter to CreateInstance<T> / CreateInstance ) allows you to specify parameters that should be injected directly, as opposed to resolved from the service provider.传递给服务构造函数的参数( CreateInstance<T> / CreateInstanceobject[]参数)允许您指定应该直接注入的参数,而不是从服务提供者解析。 They are applied from left to right as they appear (ie first string will be replaced with the first string-typed parameter of the type to be instantiated).它们在出现时从左到右应用(即第一个字符串将被替换为要实例化的类型的第一个字符串类型参数)。

ActivatorUtilities.CreateInstance<Service> is used in many places to resolve service and replace one of the default registrations for this single activation. ActivatorUtilities.CreateInstance<Service>在许多地方用于解析服务并替换此单个激活的默认注册之一。

For example, if you have a class named MyService , and it has IOtherService , ILogger<MyService> as dependencies and you want to resolve the service but replace the default service of IOtherService (say it's OtherServiceA ) with OtherServiceB , you could do something like:例如,如果您有一个名为MyService的类,并且它有IOtherServiceILogger<MyService>作为依赖项,并且您想解析该服务但将IOtherService的默认服务(假设它是OtherServiceA )替换为OtherServiceB ,则您可以执行以下操作:

myService = ActivatorUtilities.CreateInstance<Service>(serviceProvider,
    new OtherServiceB());

Then the first parameter of IOtherService will get OtherServiceB injected, rather than OtherServiceA - but the remaining parameters will come from the service provider.然后IOtherService的第一个参数将被注入OtherServiceB ,而不是OtherServiceA - 但其余参数将来自服务提供者。

This is helpful when you have many dependencies and want just to treat a single one specially (ie replace a database-specific provider with a value configured during the request or for a specific user, something you only know at runtime and/or during a request - not when the application is built/started).当您有许多依赖项并且只想特别处理单个依赖项时,这很有用(即,将特定于数据库的提供程序替换为在请求期间或为特定用户配置的值,您仅在运行时和/或请求期间知道的值) - 不是在构建/启动应用程序时)。

If performance is a concern, you can use ActivatorUtilities.CreateFactory(Type, Type[]) to create a factory method instead.如果性能是一个问题,您可以使用ActivatorUtilities.CreateFactory(Type, Type[])来创建工厂方法。 GitHub reference and benchmark . GitHub 参考基准测试

This is useful when the type is resolved very frequently (such as in SignalR and other high request scenarios).这在非常频繁地解析类型时很有用(例如在 SignalR 和其他高请求场景中)。 Basically, you'd create an ObjectFactory via基本上,您将通过创建一个ObjectFactory

var myServiceFactory = ActivatorUtilities.CreateFactory(typeof(MyService), new object[] { typeof(IOtherService), });

then cache it (as a variable etc.) and invoke it where needed:然后缓存它(作为变量等)并在需要时调用它:

MyService myService = myServiceFactory(serviceProvider, myServiceOrParameterTypeToReplace);

This all works perfectly with primitive types too - here's an example I tested with:这一切也适用于原始类型 - 这是我测试过的示例:

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);
}

Prints印刷

Output: Hello Tseng Stackoverflow

If you feel uncomfortable with newing the service, you could use the Parameter Object pattern.如果您对新服务感到不舒服,可以使用Parameter Object模式。

So extract the string parameter into its own type所以把字符串参数提取成它自己的类型

public class ServiceArgs
{
   public string Arg1 {get; set;}
}

And the constructor will now look like构造函数现在看起来像

public Service(IOtherService service1, 
               IAnotherOne service2, 
               ServiceArgs args)
{

}

And the setup和设置

_serviceCollection.AddSingleton<ServiceArgs>(_ => new ServiceArgs { Arg1 = ""; });
_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService, Service>();

The first benefit is if you need to change the Service constructor and add new services to it, then you don't have to change the new Service(... calls. Another benefit is the setup is a bit cleaner.第一个好处是如果您需要更改 Service 构造函数并向其添加新服务,那么您不必更改new Service(...调用。另一个好处是设置更简洁一些。

For a constructor with a single parameter or two, this might be too much though.对于只有一个或两个参数的构造函数,这可能太多了。

You can inject dependencies with this process also您也可以使用此过程注入依赖项

_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService>(x=>new Service( x.GetService<IOtherService>(), x.GetService<IAnotherOne >(), "" ));

Following way will be work even you have multiple parameters 即使您有多个参数,也可以使用以下方法

services.AddSingleton(_ => new YourService("yourvalue1","yourvalue2") as IYourService);

But I feels its not efficient way of implementation 但是我觉得它的执行方式不是很有效

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在 .NET Core 中使用不同的构造函数参数(DI 和非 DI)注册多个 IHostedService 实例 - Register multiple instances of IHostedService with different constructor parameters (DI and non-DI) in .NET Core IHostEnvironment 和启动构造函数中没有 DI .NET Core 3+ - IHostEnvironment and no DI in Startup constructor .NET Core 3+ .NET Core DI 没有构造函数参数 - .NET Core DI without constructor arguments 通过.NET Core上的MEF将参数传递给插件构造函数? - Passing parameters to plugin constructor via MEF on .NET Core? 使用.NET Core DI和运行时参数? - Using .NET Core DI with runtime parameters? 将 DI 与构造函数参数结合起来? - Combining DI with constructor parameters? 没有用于ASP.NET MVC的DI容器的控制器的构造函数参数 - Constructor parameters for controllers without a DI container for ASP.NET MVC MassTransit和.NET Core DI - 如何使用无参数构造函数解决依赖关系? - MassTransit and .NET Core DI - how to resolve dependencies with parameterless constructor? .Net Core DI - 通过构造函数注入与使用 scope 解析 - .Net Core DI - inject via constructor vs resolve using scope .NET Core 2和DI - 在构造函数中使用appsettings.json中的值? - .NET Core 2 & DI - use values from appsettings.json in the constructor?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM