Giải quyết các trường hợp với ASP.NET Core DI


302

Làm cách nào để giải quyết thủ công một loại bằng cách sử dụng khung tiêm phụ thuộc tích hợp ASP.NET Core MVC?

Thiết lập container là đủ dễ dàng:

public void ConfigureServices(IServiceCollection services)
{
    // ...

    services.AddTransient<ISomeService, SomeConcreteService>();
}

Nhưng làm thế nào tôi có thể giải quyết ISomeServicemà không thực hiện tiêm? Ví dụ, tôi muốn làm điều này:

ISomeService service = services.Resolve<ISomeService>();

Không có phương pháp như vậy trong IServiceCollection.



3
Bạn có muốn giải quyết chúng trong ConfigureServices()phương thức (với IServiceCollection) hoặc bất cứ nơi nào trong ứng dụng không?
Henk Mollema

2
@HenkMollema: Bất cứ nơi nào trong Startup thực sự.
Dave New

Câu trả lời:


485

Các IServiceCollectiongiao diện được sử dụng để xây dựng một container dependency injection. Sau khi được xây dựng hoàn chỉnh, nó được soạn thành một IServiceProviderthể hiện mà bạn có thể sử dụng để giải quyết các dịch vụ. Bạn có thể tiêm một IServiceProvidervào bất kỳ lớp học. Các lớp IApplicationBuilderHttpContextcũng có thể cung cấp nhà cung cấp dịch vụ, thông qua các thuộc tính ApplicationServiceshoặc RequestServicesthuộc tính của chúng.

IServiceProviderđịnh nghĩa một GetService(Type type)phương thức để giải quyết một dịch vụ:

var service = (IFooService)serviceProvider.GetService(typeof(IFooService));

Ngoài ra còn có một số phương thức mở rộng tiện lợi có sẵn, chẳng hạn như serviceProvider.GetService<IFooService>()(thêm một usingcho Microsoft.Extensions.DependencyInjection).

Giải quyết các dịch vụ bên trong lớp khởi động

Tiêm phụ thuộc

Nhà cung cấp dịch vụ lưu trữ của bộ thực thi có thể đưa một số dịch vụ nhất định vào hàm tạo của Startuplớp, chẳng hạn như IConfiguration, IWebHostEnvironment( IHostingEnvironmenttrong các phiên bản trước 3.0) ILoggerFactoryIServiceProvider. Lưu ý rằng cái sau là một thể hiện được tạo bởi lớp lưu trữ và chỉ chứa các dịch vụ thiết yếu để khởi động một ứng dụng .

Các ConfigureServices()phương pháp không cho phép các dịch vụ tiêm, nó chỉ chấp nhận một IServiceCollectioncuộc tranh cãi. Điều này có ý nghĩa bởi vì đó ConfigureServices()là nơi bạn đăng ký các dịch vụ theo yêu cầu của ứng dụng của bạn. Tuy nhiên, bạn có thể sử dụng các dịch vụ được chèn trong hàm tạo của phần khởi động tại đây, ví dụ:

public Startup(IConfiguration configuration)
{
    Configuration = configuration;
}

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
    // Use Configuration here
}

Bất kỳ dịch vụ nào được đăng ký ConfigureServices()sau đó đều có thể được đưa vào Configure()phương thức; bạn có thể thêm một số lượng dịch vụ tùy ý sau IApplicationBuildertham số:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IFooService>();
}

public void Configure(IApplicationBuilder app, IFooService fooService)
{
    fooService.Bar();
}

Giải quyết phụ thuộc thủ công

Nếu bạn cần giải quyết thủ công các dịch vụ, tốt nhất bạn nên sử dụng ApplicationServicesđược cung cấp bởi IApplicationBuildertrong Configure()phương thức:

public void Configure(IApplicationBuilder app)
{
    var serviceProvider = app.ApplicationServices;
    var hostingEnv = serviceProvider.GetService<IHostingEnvironment>();
}

Có thể vượt qua và sử dụng trực tiếp một IServiceProviderhàm tạo trong Startuplớp của bạn , nhưng như trên, điều này sẽ chứa một tập hợp con các dịch vụ hạn chế và do đó có tiện ích hạn chế:

public Startup(IServiceProvider serviceProvider)
{
    var hostingEnv = serviceProvider.GetService<IWebHostEnvironment>();
}

Nếu bạn phải giải quyết các dịch vụ trong ConfigureServices()phương thức, một cách tiếp cận khác là bắt buộc. Bạn có thể xây dựng một trung gian IServiceProvidertừ IServiceCollectiontrường hợp có chứa các dịch vụ đã được đăng ký cho đến thời điểm đó :

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IFooService, FooService>();

    // Build the intermediate service provider
    var sp = services.BuildServiceProvider();

    // This will succeed.
    var fooService = sp.GetService<IFooService>();
    // This will fail (return null), as IBarService hasn't been registered yet.
    var barService = sp.GetService<IBarService>();
}

Xin lưu ý: Nói chung, bạn nên tránh giải quyết các dịch vụ bên trong ConfigureServices()phương thức, vì đây thực sự là nơi bạn định cấu hình các dịch vụ ứng dụng. Đôi khi bạn chỉ cần truy cập vào một IOptions<MyOptions>ví dụ. Bạn có thể thực hiện điều này bằng cách ràng buộc các giá trị từ IConfigurationthể hiện với một thể hiện của MyOptions(về cơ bản là những gì khung tùy chọn thực hiện):

public void ConfigureServices(IServiceCollection services)
{
    var myOptions = new MyOptions();
    Configuration.GetSection("SomeSection").Bind(myOptions);
}

Các dịch vụ phân giải thủ công (còn gọi là Bộ định vị dịch vụ) thường được coi là một mẫu chống . Mặc dù nó có các trường hợp sử dụng (đối với khung và / hoặc các lớp cơ sở hạ tầng), bạn nên tránh nó càng nhiều càng tốt.


14
@HenkMollema nhưng nếu tôi không thể tiêm bất cứ thứ gì, ý tôi là tôi không thể IServiceCollectiontiêm, một số lớp được tạo thủ công ( ngoài phạm vi kho trung gian ), một trình lập lịch trong trường hợp của tôi, định kỳ cần một số dịch vụ để tạo và gửi email.
Merdan Gochmuradov

52
cảnh báo nếu bạn cần giải quyết các dịch vụ trong ConfigureServicesđó và dịch vụ đó là một singleton thì nó sẽ là một singleton khác với ứng dụng của bạn Controller! Tôi giả định này là bởi vì nó sử dụng một khác nhau IServiceProvider- để tránh điều này không giải quyết được thông qua BuildServiceProvidervà thay vào đó di chuyển tra cứu của bạn của singleton từ ConfigureServicesđể Configure(..other params, IServiceProvider serviceProvider)Startup.cs
wal

3
@wal điểm tốt. Bởi vì nó là một thể hiện khác, IServiceProvidernó sẽ tạo ra một thể hiện singleton mới. Bạn có thể tránh điều này bằng cách trả về thể hiện của nhà cung cấp dịch vụ từ ConfigureServicesphương thức để đó cũng là nơi chứa ứng dụng của bạn.
Henk Mollema

1
Gọi collection.BuildServiceProvider();là những gì tôi cần, cảm ơn!
Chris Marisic

2
@HenkMollema làm thế nào để bạn làm cho nó hoạt động chỉ với một trường hợp nhà cung cấp dịch vụ? Thông thường, bạn sẽ 1) Đăng ký một số phụ thuộc của bạn 2) xây dựng một ví dụ nhà cung cấp dịch vụ tạm thời 3) Sử dụng nhà cung cấp dịch vụ đó để giải quyết vấn đề bạn cần để đăng ký một số phụ thuộc khác. Sau đó, bạn không thể trả lại phiên bản tạm thời, vì nó thiếu một số phụ thuộc của bạn (đã đăng ký trong 3). Tui bỏ lỡ điều gì vậy?
Filip

109

Giải quyết thủ công các trường hợp liên quan đến việc sử dụng IServiceProvidergiao diện:

Giải quyết sự phụ thuộc trong Startup.ConfigureService

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IMyService, MyService>();

    var serviceProvider = services.BuildServiceProvider();
    var service = serviceProvider.GetService<IMyService>();
}

Giải quyết các phụ thuộc trong Startup.Configure

public void Configure(
    IApplicationBuilder application,
    IServiceProvider serviceProvider)
{
    // By type.
    var service1 = (MyService)serviceProvider.GetService(typeof(MyService));

    // Using extension method.
    var service2 = serviceProvider.GetService<MyService>();

    // ...
}

Giải quyết các phụ thuộc trong Startup. Cấu hình trong ASP.NET Core 3

public void Configure(
    IApplicationBuilder application,
    IWebHostEnvironment webHostEnvironment)
{
    app.ApplicationServices.GetService<MyService>();
}

Sử dụng dịch vụ tiêm Runtime

Một số loại có thể được tiêm dưới dạng tham số phương thức:

public class Startup
{
    public Startup(
        IHostingEnvironment hostingEnvironment,
        ILoggerFactory loggerFactory)
    {
    }

    public void ConfigureServices(
        IServiceCollection services)
    {
    }

    public void Configure(
        IApplicationBuilder application,
        IHostingEnvironment hostingEnvironment,
        IServiceProvider serviceProvider,
        ILoggerFactory loggerfactory,
        IApplicationLifetime applicationLifetime)
    {
    }
}

Giải quyết các phụ thuộc trong hành động của bộ điều khiển

[HttpGet("/some-action")]
public string SomeAction([FromServices] IMyService myService) => "Hello";

1
@AfsharMohebbi GetServicecái chung là một phương thức mở rộng trong Microsoft.Extensions.DependencyInjectionkhông gian tên.
ahmadali shafiee

Giới thiệu về Phương thức tiện ích mở rộng: Phương thức tiện ích mở rộng là phương thức tĩnh bổ sung funcionality cho một lớp, bạn có thể khai báo tĩnh công khai TheReturnType TheMethodName (this TheTypeYouExtend theTypeYouExtend {// BODY} và sau đó bạn có thể sử dụng nó như: TheTypeYouExtend. trở thành một aproach rất phổ biến với .NET Core, để các nhà phát triển có thể mở rộng chức năng cơ bản ... ví dụ hay ở đây: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/ tựa
Juan

17

Nếu bạn tạo một ứng dụng với một mẫu, bạn sẽ có một cái gì đó như thế này trên Startuplớp:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddApplicationInsightsTelemetry(Configuration);

    services.AddMvc();
}

Sau đó, bạn có thể thêm phụ thuộc vào đó, ví dụ:

services.AddTransient<ITestService, TestService>();

Nếu bạn muốn truy cập ITestServicevào bộ điều khiển của mình, bạn có thể thêm IServiceProvidervào hàm tạo và nó sẽ được thêm vào:

public HomeController(IServiceProvider serviceProvider)

Sau đó, bạn có thể giải quyết dịch vụ bạn đã thêm:

var service = serviceProvider.GetService<ITestService>();

Lưu ý rằng để sử dụng phiên bản chung, bạn phải bao gồm không gian tên với các tiện ích mở rộng:

using Microsoft.Extensions.DependencyInjection;

ITestService.cs

public interface ITestService
{
    int GenerateRandom();
}

TestService.cs

public class TestService : ITestService
{
    public int GenerateRandom()
    {
        return 4;
    }
}

Startup.cs (ConfigureService)

public void ConfigureServices(IServiceCollection services)
{
    services.AddApplicationInsightsTelemetry(Configuration);
    services.AddMvc();

    services.AddTransient<ITestService, TestService>();
}

HomeControll.cs

using Microsoft.Extensions.DependencyInjection;

namespace Core.Controllers
{
    public class HomeController : Controller
    {
        public HomeController(IServiceProvider serviceProvider)
        {
            var service = serviceProvider.GetService<ITestService>();
            int rnd = service.GenerateRandom();
        }

10

Nếu bạn chỉ cần giải quyết một phụ thuộc cho mục đích chuyển nó cho nhà xây dựng của một phụ thuộc khác mà bạn đang đăng ký, bạn có thể làm điều này.

Giả sử bạn có một dịch vụ lấy chuỗi và ISomeService.

public class AnotherService : IAnotherService
{
    public AnotherService(ISomeService someService, string serviceUrl)
    {
        ...
    }
}

Khi bạn đi đăng ký cái này bên trong Startup.cs, bạn sẽ cần phải làm điều này:

services.AddScoped<IAnotherService>(ctx => 
      new AnotherService(ctx.GetService<ISomeService>(), "https://someservice.com/")
);

OP không nêu lý do cần giải quyết một dịch vụ trong phương thức ConfigureService, nhưng đây rất có thể là lý do mà ai đó sẽ nghĩ để làm điều đó
kilkfoe

1
Trên thực tế, đây phải là câu trả lời được chấp nhận ... Mặc dù câu trả lời của Henk Mollema rất minh họa, nhưng hiện tại câu trả lời của bạn sạch hơn và không đưa ra các vấn đề liên quan đến việc xây dựng IServiceProvider trung gian (các trường hợp khác nhau của singletons ...). Có lẽ, giải pháp này không có sẵn vào năm 2015 khi Henk ngơ ngác, nhưng bây giờ là cách để đi.
Vi100

Đã thử điều này nhưng ISomeServicevẫn vô hiệu đối với tôi.
ajbeaven

2 câu hỏi: 1) Nếu hàm tạo tham số của lớp dịch vụ AnotherService thay đổi (đã xóa hoặc thêm dịch vụ), thì tôi cần sửa đổi phân đoạn đăng ký của dịch vụ IAnotherService và nó có thay đổi không? 2) Thay vào đó, tôi chỉ có thể thêm một hàm tạo cho AnotherService với 1 tham số như công khai AnotherService (IServiceProvider serviceProvider) và nhận các dịch vụ tôi cần từ hàm tạo. Và tôi chỉ cần đăng ký lớp dịch vụ AnotherService trong lớp Startup như services.AddTransient <IAnotherService, AnotherService> (sp => {var service = new AnotherService (sp); return service;});
Thomas.Benz

2

Bạn có thể thêm các phụ thuộc vào các thuộc tính như AuthorizeAttribution theo cách này

var someservice = (ISomeService)context.HttpContext.RequestServices.GetService(typeof(ISomeService));

Đây là những gì tôi đang tìm kiếm .. Cảm ơn
Reyan Chougle

0

Tôi biết đây là một câu hỏi cũ nhưng tôi ngạc nhiên rằng một vụ hack khá rõ ràng và kinh tởm không có ở đây.

Bạn có thể khai thác khả năng xác định chức năng ctor của riêng mình để lấy các giá trị cần thiết ra khỏi dịch vụ của bạn khi bạn xác định chúng ... rõ ràng điều này sẽ được chạy mỗi khi dịch vụ được yêu cầu trừ khi bạn xóa / xóa rõ ràng và thêm lại định nghĩa của dịch vụ này trong quá trình xây dựng đầu tiên của ctor khai thác .

Phương pháp này có ưu điểm là không yêu cầu bạn xây dựng cây dịch vụ hoặc sử dụng nó trong quá trình cấu hình dịch vụ. Bạn vẫn đang xác định cách các dịch vụ sẽ được cấu hình.

public void ConfigureServices(IServiceCollection services)
{
    //Prey this doesn't get GC'd or promote to a static class var
    string? somevalue = null;

    services.AddSingleton<IServiceINeedToUse, ServiceINeedToUse>(scope => {
         //create service you need
         var service = new ServiceINeedToUse(scope.GetService<IDependantService>())
         //get the values you need
         somevalue = somevalue ?? service.MyDirtyHack();
         //return the instance
         return service;
    });
    services.AddTransient<IOtherService, OtherService>(scope => {
         //Explicitly ensuring the ctor function above is called, and also showcasing why this is an anti-pattern.
         scope.GetService<IServiceINeedToUse>();
         //TODO: Clean up both the IServiceINeedToUse and IOtherService configuration here, then somehow rebuild the service tree.
         //Wow!
         return new OtherService(somevalue);
    });
}

Cách khắc phục mô hình này sẽ là đưa ra OtherServicemột sự phụ thuộc rõ ràng IServiceINeedToUse, thay vì hoàn toàn phụ thuộc vào giá trị trả về của nó hoặc phương thức của nó ... hoặc giải quyết rõ ràng sự phụ thuộc đó theo một cách thức khác.


-4
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddDbContext<ConfigurationRepository>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("SqlConnectionString")));

    services.AddScoped<IConfigurationBL, ConfigurationBL>();
    services.AddScoped<IConfigurationRepository, ConfigurationRepository>();
}

5
Câu trả lời của bạn có nhiều khả năng được chấp nhận và nâng cao nếu bạn cung cấp một lời giải thích ngắn gọn về lý do tại sao đó là một câu trả lời tốt, không chỉ là một đoạn mã. Nó cũng giúp người hỏi chắc chắn rằng điều này thực sự đang trả lời câu hỏi họ đã hỏi.
Jim L

Ai đó đã đánh dấu không chính xác câu trả lời của bạn là chất lượng thấp. Bạn nên thêm một số văn bản kèm theo để giải thích cách câu trả lời của bạn hoạt động để ngăn chặn việc gắn cờ và / hoặc downvote tiếp theo. Một câu trả lời chỉ có mã không phải là chất lượng thấp . Nó có cố gắng trả lời câu hỏi không? Nếu không, gắn cờ là 'không phải là câu trả lời' hoặc đề nghị xóa (nếu trong hàng đánh giá). b) Có đúng về mặt kỹ thuật không? Downvote hoặc bình luận. Từ đánh giá .
Wai Ha Lee
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.