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


105

具有以下服务构造函数

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

     }
}

使用.NET Core IOC机制传递参数的选择有哪些

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

还有其他办法吗?


3
更改您的设计。将arg提取到参数对象中并将其注入。
史蒂文

Answers:


124

工厂委托的表达式参数(在这种情况下为x)为IServiceProvider

用它来解决依赖关系,

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

工厂委托是延迟调用。每当要解析类型时,它将通过完整的提供程序作为委托参数。


1
是的,这就是我现在的做法,但是还有其他办法吗?也许更优雅?我的意思是,拥有注册服务的其他参数看起来有点奇怪。我正在寻找更类似于正常注册服务并且仅传递非服务参数(在这种情况下为arg)的东西。像Autofac有些地方工作 .WithParameter("argument", "");
鲍里斯·

1
不,您是手动构建提供程序,这很糟糕。委托是延迟的调用。每当要解析类型时,它将通过完整的提供程序作为委托参数。
Nkosi

@MCR是开箱即用的核心DI的默认方法。
Nkosi

12
@Nkosi:看看ActivatorUtilities.CreateInstance,它是Microsoft.Extensions.DependencyInjection.Abstractions程序包的一部分(因此没有特定于容器的依赖项)
Tseng

谢谢@Tseng,它看起来像是我们在这里寻找的实际答案。
BrainSlugs83 '20

63

应该注意的是,推荐的方法是使用选项模式。但是在某些情况下,它不切实际(仅在运行时才知道参数,而在启动/编译时才知道),或者您需要动态替换依赖项。

当您需要替换单个依赖项(字符串,整数或其他类型的依赖项)或使用仅接受字符串/整数参数且需要运行时参数的第三方库时,它非常有用。

您可以尝试使用CreateInstance(IServiceProvider,Object [])作为快捷方式(不确定它是否适用于字符串参数/值类型/基元(int,float,字符串),未经测试) (只需尝试一下并确认其工作原理,即使多个字符串参数),而不是手动解决每个依赖关系:

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

参数(CreateInstance<T>/的最后一个参数CreateInstance)定义了应替换的参数(未从提供者处解析)。它们在出现时从左到右应用(即,第一个字符串将被要实例化的类型的第一个字符串类型的参数替换)。

ActivatorUtilities.CreateInstance<Service> 在许多地方都可以使用来解析服务并替换此一次激活的默认注册之一。

例如,如果你有一个类命名的MyService,它有IOtherServiceILogger<MyService>作为依赖和要解决的业务,但替换的默认服务IOtherService(说其OtherServiceA有)OtherServiceB,你可以这样做:

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

然后,IOtherServiceOtherServiceB注入的第一个参数,而不是OtherServiceA其余参数将来自容器。

当您有很多依赖项并且只想专门对待一个依赖项时(例如,用请求期间或为特定用户配置的值替换特定于数据库的提供程序,这仅在运行时,在请求期间和而不是在构建/启动应用程序时)。

您还可以改用ActivatorUtilities.CreateFactory(Type,Type [])方法创建工厂方法,因为它提供了更好的性能GitHub ReferenceBenchmark

当类型非常频繁地解析时(例如在SignalR和其他高请求场景中),后面的一个很有用。基本上,您将创建一个ObjectFactoryVia

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

然后将其缓存(作为变量等)并在需要时调用它

MyService myService = myServiceFactory(serviceProvider, myServiceOrParameterTypeToReplace);

## Update:我自己尝试过一次,以确认它也可以用于字符串和整数,并且确实可以工作。这是我测试过的具体示例:

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

版画

Output: Hello Tseng Stackoverflow

6
这也是ASP.NET核心如何实例默认情况下,控制器ControllerActivatorProvider,他们不是直接从国际奥委会(除非解决了.AddControllersAsServices使用,它取代了ControllerActivatorProviderServiceBasedControllerActivator
曾国藩

16

如果您不喜欢更新服务,则可以使用该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(...调用。另一个好处是设置有点清洁。

对于只有一个或两个参数的构造函数,这可能太多了。


2
对于复杂的参数,使用“选项”模式会更直观,这是选项模式的推荐方式,但是不适用于仅在运行时才知道的参数(即从请求或声明中获得)
Tseng

0

您也可以通过此过程注入依赖项

_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService>(x=>new Service( x.GetService<IOtherService>(), x.GetService<IAnotherOne >(), "" ));
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.