Разрешение экземпляров с помощью ASP.NET Core DI

Как вручную разрешить тип с помощью встроенной среды ввода-вывода ASP.NET Core MVC?

Настройка контейнера достаточно проста:

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

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

Но как я могу разрешить ISomeService без выполнения инъекции? Например, я хочу сделать это:

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

В IServiceCollection таких методов нет.

Ответ 1

Интерфейс IServiceCollection используется для построения контейнера внедрения зависимостей. После полной сборки он собирается в экземпляр IServiceProvider который можно использовать для разрешения служб. Вы можете внедрить IServiceProvider в любой класс. IApplicationBuilder и HttpContext могут предоставлять поставщика услуг через свойства ApplicationServices или RequestServices соответственно.

IServiceProvider определяет GetService(Type type) для разрешения службы:

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

Существует также несколько удобных методов расширения, таких как serviceProvider.GetService<IFooService>() (добавьте using для Microsoft.Extensions.DependencyInjection).

Разрешение сервисов внутри класса запуска

Внедрение зависимостей

Среда выполнения может внедрить службы в конструктор класса Startup, такие как IHostingEnvironment, IConfiguration и IServiceProvider. Обратите внимание, что этот поставщик услуг является экземпляром, созданным на уровне хостинга, и содержит только службы для запуска приложения.

Службы также могут быть добавлены в метод Configure(). Вы можете добавить произвольный список параметров после параметра IApplicationBuilder. Вы также можете добавить свои собственные сервисы, которые зарегистрированы в методе ConfigureServices() здесь они будут решаться не поставщиком услуг хостинга, а поставщиком услуг приложения.

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

Однако метод ConfigureServices() не позволяет внедрять сервисы, он принимает IServiceCollection аргумент IServiceCollection. Это метод, в котором вы конфигурируете свой контейнер внедрения зависимости приложения. Вы можете использовать сервисы, добавленные в конструктор автозагрузки здесь. Например:

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

public IConfiguration Configuration { get; }

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

Разрешение зависимостей вручную

Если вы хотите разрешить службы вручную, вы можете позволить среде выполнения внедрить экземпляр IServiceProvider в конструкторе или использовать ApplicationServices предоставляемые IApplicationBuilder в IApplicationBuilder Configure():

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

или же

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

Однако если вам нужно разрешить службы в методе ConfigureServices(), вам нужен другой подход. Вы можете создать промежуточный IServiceProvider из экземпляра IServiceCollection который содержит службы, которые зарегистрированы до тех пор:

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

    // Build the intermediate service provider
    var sp = services.BuildServiceProvider();
    var fooService = sp.GetService<IFooService>();
}

Для этого вам необходим пакет Microsoft.Extensions.DependencyInjection.

Пожалуйста, обратите внимание:
Как правило, вам не следует разрешать службы внутри метода ConfigureServices(), поскольку именно здесь вы конфигурируете службы приложений. Иногда вам просто нужен доступ к некоторому IOptions<MyOptions>. Вы можете сделать это, IConfiguration значения из экземпляра IConfiguration с экземпляром MyOptions (что, по сути, и делает инфраструктура параметров):

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

Разрешение сервисов вручную (иначе, Service Locator) обычно называется анти-паттерном. Хотя у него есть свои варианты использования (для каркасов и/или инфраструктурных уровней), вы должны избегать его в максимально возможной степени.

Ответ 2

IServiceProvider экземпляров вручную включает использование интерфейса IServiceProvider:

Разрешение зависимости в Startup.ConfigureServices

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

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

Разрешение зависимостей в 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>();

    // ...
}

Использование Runtime Injected Services

Некоторые типы могут быть введены как параметры метода:

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

Разрешение зависимостей в действиях контроллера

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

Ответ 3

Если вы создаете приложение с шаблоном, у вас будет что-то вроде этого в классе Startup:

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

    services.AddMvc();
}

Затем вы можете добавить туда зависимости:

services.AddTransient<ITestService, TestService>();

Если вы хотите получить доступ к ITestService на своем контроллере, вы можете добавить IServiceProvider в конструктор, и он будет введен:

public HomeController(IServiceProvider serviceProvider)

Затем вы можете разрешить добавленную службу:

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

Обратите внимание, что для использования общей версии вам необходимо включить пространство имен с расширениями:

using Microsoft.Extensions.DependencyInjection;

ITestService.cs

public interface ITestService
{
    int GenerateRandom();
}

TestService.cs

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

Startup.cs(ConfigureServices)

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

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

HomeController.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();
        }

Ответ 4

Если вам просто нужно разрешить одну зависимость с целью передачи ее конструктору другой зависимости, которую вы регистрируете, вы можете сделать это.

Допустим, у вас есть служба, которая принимает строку и ISomeService.

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

Когда вы зарегистрируетесь в Startup.cs, вам нужно будет сделать следующее:

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

Ответ 5

Таким способом вы можете внедрить зависимости в такие атрибуты, как AuthorizeAttribute.

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

Ответ 6

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

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

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