簡體   English   中英

.NET Core DI,將參數傳遞給構造函數的方法

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

具有以下服務構造函數

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>(), 
                ""));

工廠委托是延遲調用。 每當要解析類型時,它都會將完成的提供程序作為委托參數傳遞。

實現此目的的推薦方法是使用選項模式- 請注意,這適用於任何 .NET Core/5 應用程序,而不僅僅是 ASP.NET Core。 但是有些用例是不切實際的(例如,當參數僅在運行時已知,而不是在啟動/編譯時已知時)或者您需要動態替換依賴項。

當您需要替換單個依賴項(無論是字符串、整數還是其他類型的依賴項)或使用僅接受字符串/整數參數且需要運行時參數的第 3 方庫時,它非常有用。

您可以嘗試使用CreateInstance<T>(IServiceProvider, Object[])作為快捷方式,而不是手動解析每個依賴項:

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

傳遞給服務構造函數的參數( CreateInstance<T> / CreateInstanceobject[]參數)允許您指定應該直接注入的參數,而不是從服務提供者解析。 它們在出現時從左到右應用(即第一個字符串將被替換為要實例化的類型的第一個字符串類型參數)。

ActivatorUtilities.CreateInstance<Service>在許多地方用於解析服務並替換此單個激活的默認注冊之一。

例如,如果您有一個名為MyService的類,並且它有IOtherServiceILogger<MyService>作為依賴項,並且您想解析該服務但將IOtherService的默認服務(假設它是OtherServiceA )替換為OtherServiceB ,則您可以執行以下操作:

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

然后IOtherService的第一個參數將被注入OtherServiceB ,而不是OtherServiceA - 但其余參數將來自服務提供者。

當您有許多依賴項並且只想特別處理單個依賴項時,這很有用(即,將特定於數據庫的提供程序替換為在請求期間或為特定用戶配置的值,您僅在運行時和/或請求期間知道的值) - 不是在構建/啟動應用程序時)。

如果性能是一個問題,您可以使用ActivatorUtilities.CreateFactory(Type, Type[])來創建工廠方法。 GitHub 參考基准測試

這在非常頻繁地解析類型時很有用(例如在 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);
}

印刷

Output: Hello Tseng Stackoverflow

如果您對新服務感到不舒服,可以使用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 >(), "" ));

即使您有多個參數,也可以使用以下方法

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

但是我覺得它的執行方式不是很有效

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM