Разрешение экземпляров с помощью 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>();
}