.NET Core DI, các cách truyền tham số cho hàm tạo


102

Có hàm tạo dịch vụ sau

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

     }
}

Các lựa chọn truyền tham số bằng cơ chế .NET Core IOC là gì

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

Còn cách nào khác không ?


3
Thay đổi thiết kế của bạn. Trích xuất đối số vào một Đối tượng tham số và đưa nó vào.
Steven

Câu trả lời:


121

Tham số biểu thức ( x trong trường hợp này), của đại biểu nhà máy là a IServiceProvider.

Sử dụng nó để giải quyết các phụ thuộc,

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

Đại biểu nhà máy là một lệnh gọi bị trì hoãn. Khi nào kiểu được giải quyết, nó sẽ chuyển trình cung cấp đã hoàn thành làm tham số ủy quyền.


1
vâng, đây là cách tôi đang làm ngay bây giờ, nhưng có cách nào khác không? thanh lịch hơn có thể? Ý tôi là Sẽ hơi kỳ lạ nếu có các thông số khác là các dịch vụ đã đăng ký. Tôi đang tìm kiếm một cái gì đó giống như đăng ký các dịch vụ bình thường và chỉ chuyển các đối số không phải dịch vụ, trong trường hợp này là đối số. Một cái gì đó giống như Autofac làm .WithParameter("argument", "");
boris

1
Không, bạn đang xây dựng nhà cung cấp theo cách thủ công, điều đó thật tệ. Đại biểu là một lời gọi bị trì hoãn. Khi nào kiểu được giải quyết, nó sẽ chuyển trình cung cấp đã hoàn thành làm tham số ủy quyền.
Nkosi

@MCR đó là cách tiếp cận mặc định với Core DI ra khỏi hộp.
Nkosi

11
@Nkosi: Hãy xem ActivatorUtilities.CreateInstance , một phần của Microsoft.Extensions.DependencyInjection.Abstractionsgói (vì vậy không có phụ thuộc cụ thể cho vùng chứa)
Tseng

Cảm ơn, @Tseng, đây có vẻ như là câu trả lời thực sự mà chúng tôi đang tìm kiếm.
BrainSlugs83

59

Cần lưu ý rằng cách được khuyến nghị là sử dụng Mẫu tùy chọn . Nhưng có những trường hợp sử dụng mà nó không thực tế (khi các tham số chỉ được biết trong thời gian chạy, không phải lúc khởi động / biên dịch) hoặc bạn cần thay thế động một phụ thuộc.

Nó rất hữu ích khi bạn cần thay thế một phụ thuộc duy nhất (có thể là một chuỗi, số nguyên hoặc một loại phụ thuộc khác) hoặc khi sử dụng thư viện bên thứ 3 chỉ chấp nhận các tham số chuỗi / số nguyên và bạn yêu cầu tham số thời gian chạy.

Bạn có thể thử CreateInstance (IServiceProvider, Object []) như một tay tắt (không chắc nó hoạt động với tham số chuỗi / kiểu giá trị / nguyên thủy (int, float, string), chưa được kiểm tra) (Chỉ cần dùng thử và xác nhận nó hoạt động, ngay cả với nhiều tham số chuỗi) thay vì giải quyết mọi phụ thuộc bằng tay:

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

Các tham số (tham số cuối cùng của CreateInstance<T>/ CreateInstance) xác định các tham số sẽ được thay thế (không được giải quyết từ trình cung cấp). Chúng được áp dụng từ trái sang phải khi chúng xuất hiện (nghĩa là chuỗi đầu tiên sẽ được thay thế bằng tham số kiểu chuỗi đầu tiên của kiểu sẽ được khởi tạo).

ActivatorUtilities.CreateInstance<Service> được sử dụng ở nhiều nơi để giải quyết một dịch vụ và thay thế một trong các đăng ký mặc định cho lần kích hoạt duy nhất này.

Ví dụ: nếu bạn có một lớp được đặt tên MyService, và nó có IOtherService, ILogger<MyService>là phụ thuộc và bạn muốn giải quyết dịch vụ nhưng thay thế dịch vụ mặc định của IOtherService(giả sử nó OtherServiceA) bằng OtherServiceB, bạn có thể làm điều gì đó như:

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

Sau đó, tham số đầu tiên của IOtherServicesẽ được OtherServiceBđưa vào, thay vì OtherServiceAnhưng các tham số còn lại sẽ đến từ vùng chứa.

Điều này hữu ích khi bạn có nhiều phụ thuộc và chỉ muốn xử lý đặc biệt một phụ thuộc duy nhất (tức là thay thế một nhà cung cấp cơ sở dữ liệu cụ thể bằng một giá trị được định cấu hình trong khi yêu cầu hoặc cho một người dùng cụ thể, điều mà bạn chỉ biết vào thời gian chạy và trong khi yêu cầu và không phải khi ứng dụng được xây dựng / khởi động).

Thay vào đó, bạn cũng có thể sử dụng phương thức ActivatorUtilities.CreateFactory (Type, Type []) để tạo phương thức gốc vì nó mang lại hiệu suất tốt hơn Tham chiếu GitHubĐiểm chuẩn .

Sau này hữu ích khi loại được giải quyết rất thường xuyên (chẳng hạn như trong SignalR và các tình huống yêu cầu cao khác). Về cơ bản, bạn sẽ tạo ObjectFactoryqua

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

sau đó lưu vào bộ nhớ cache (dưới dạng một biến, v.v.) và gọi nó khi cần thiết

MyService myService = myServiceFactory(serviceProvider, myServiceOrParameterTypeToReplace);

## Cập nhật: Tôi vừa thử tự mình xác nhận rằng nó cũng hoạt động với chuỗi và số nguyên, và nó thực sự hoạt động. Đây là ví dụ cụ thể mà tôi đã thử nghiệm:

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

Bản in

Output: Hello Tseng Stackoverflow

6
Đây cũng là cách ASP.NET Lõi instantiates Controllers theo mặc định ControllerActivatorProvider , họ không được giải quyết trực tiếp từ IoC (trừ khi .AddControllersAsServicesđược sử dụng, thay thế ControllerActivatorProvidervớiServiceBasedControllerActivator
Tseng

1
ActivatorUtilities.CreateInstance()là chính xác những gì tôi cần. Cảm ơn!
Billy Jo

1
@Tseng Bạn có vui lòng xem lại mã đã đăng của mình và đăng cập nhật không. Sau khi tạo tiện ích mở rộng và các lớp cấp cao nhất của HellloWorldService, tôi vẫn phải đối mặt với demoservice.HelloWorld như một người chưa xác định. Tôi không hiểu làm thế nào điều này được cho là hoạt động đủ để sửa chữa nó. Mục tiêu của tôi là hiểu cơ chế này hoạt động như thế nào khi tôi cần.
Nhà phát triển SOHO

1
@SOHODeveloper: Rõ ràng là việc public string HelloWorld()triển khai phương pháp bị thiếu
Tseng

Câu trả lời này thanh lịch hơn và nên được chấp nhận ... Cảm ơn!
Exodium

15

Nếu bạn cảm thấy không thoải mái với việc làm mới dịch vụ, bạn có thể sử dụng Parameter Objectmẫu này.

Vì vậy, trích xuất tham số chuỗi thành kiểu riêng của nó

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

Và hàm tạo bây giờ nó sẽ giống như

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

}

Và thiết lập

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

Lợi ích đầu tiên là nếu bạn cần thay đổi phương thức khởi tạo Dịch vụ và thêm các dịch vụ mới vào nó, thì bạn không phải thay đổi các new Service(...lệnh gọi. Một lợi ích khác là thiết lập sạch hơn một chút.

Đối với một phương thức khởi tạo có một hoặc hai tham số, điều này có thể là quá nhiều.


2
Nó sẽ là trực quan hơn cho các thông số phức tạp để sử dụng các mô hình lựa chọn và là cách đề nghị cho các tùy chọn mô hình, tuy nhiên là ít thích hợp cho các thông số chỉ biết trong thời gian chạy (tức là từ một yêu cầu hoặc yêu cầu bồi thường)
Tseng
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.