asp.net-core
Внедрение зависимости
Поиск…
Вступление
Ядро Aspnet построено с использованием Dependency Injection как одной из ключевых концепций ядра. Он вводит одну соответствующую абстракцию контейнера, поэтому вы можете заменить встроенный контейнер стороннего производителя по вашему выбору.
Синтаксис
-
IServiceCollection.Add(ServiceDescriptor item);
-
IServiceCollection.AddScoped(Type serviceType);
-
IServiceCollection.AddScoped(Type serviceType, Type implementationType);
-
IServiceCollection.AddScoped(Type serviceType, Func<IServiceProvider, object> implementationFactory);
-
IServiceCollection.AddScoped<TService>()
-
IServiceCollection.AddScoped<TService>(Func<IServiceProvider, TService> implementationFactory)
-
IServiceCollection.AddScoped<TService, TImplementation>()
-
IServiceCollection.AddScoped<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory)
-
IServiceCollection.AddSingleton(Type serviceType);
-
IServiceCollection.AddSingleton(Type serviceType, Func<IServiceProvider, object> implementationFactory);
-
IServiceCollection.AddSingleton(Type serviceType, Type implementationType);
-
IServiceCollection.AddSingleton(Type serviceType, object implementationInstance);
-
IServiceCollection.AddSingleton<TService>()
-
IServiceCollection.AddSingleton<TService>(Func<IServiceProvider, TService> implementationFactory)
-
IServiceCollection.AddSingleton<TService>(TService implementationInstance)
-
IServiceCollection.AddSingleton<TService, TImplementation>()
-
IServiceCollection.AddSingleton<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory)
-
IServiceCollection.AddTransient(Type serviceType);
-
IServiceCollection.AddTransient(Type serviceType, Func<IServiceProvider, object> implementationFactory);
-
IServiceCollection.AddTransient(Type serviceType, Type implementationType);
-
IServiceCollection.AddTransient<TService>()
-
IServiceCollection.AddTransient<TService>(Func<IServiceProvider, TService> implementationFactory)
-
IServiceCollection.AddTransient<TService, TImplementation>()
-
IServiceCollection.AddTransient<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory)
-
IServiceProvider.GetService(Type serviceType)
-
IServiceProvider.GetService<T>()
-
IServiceProvider.GetServices(Type serviceType)
-
IServiceProvider.GetServices<T>()
замечания
Чтобы использовать общие варианты методов IServiceProvider
вы должны включить следующее пространство имен:
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.AddTransient<ITestService, TestService>();
}
HomeController.cs
using Microsoft.Extensions.DependencyInjection;
namespace Core.Controllers
{
public class HomeController : Controller
{
public HomeController(ITestService service)
{
int rnd = service.GenerateRandom();
}
}
}
Регистрировать зависимости
Встроенный контейнер поставляется с набором встроенных функций:
Контроль времени жизни
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddTransient<ITestService, TestService>();
// or
services.AddScoped<ITestService, TestService>();
// or
services.AddSingleton<ITestService, TestService>();
// or
services.AddSingleton<ITestService>(new TestService());
}
- AddTransient : создается каждый раз, когда он разрешен
- AddScoped : Создано один раз за запрос
- AddSingleton : лениво создается один раз для приложения
- AddSingleton (экземпляр) : Предоставляет ранее созданный экземпляр для каждого приложения
Перечислимые зависимости
Также возможно зарегистрировать перечислимые зависимости:
services.TryAddEnumerable(ServiceDescriptor.Transient<ITestService, TestServiceImpl1>());
services.TryAddEnumerable(ServiceDescriptor.Transient<ITestService, TestServiceImpl2>());
Затем вы можете использовать их следующим образом:
public class HomeController : Controller
{
public HomeController(IEnumerable<ITestService> services)
{
// do something with services.
}
}
Общие зависимости
Вы также можете регистрировать общие зависимости:
services.Add(ServiceDescriptor.Singleton(typeof(IKeyValueStore<>), typeof(KeyValueStore<>)));
И затем используйте его следующим образом:
public class HomeController : Controller
{
public HomeController(IKeyValueStore<UserSettings> userSettings)
{
// do something with services.
}
}
Получить зависимости от контроллера
После регистрации зависимость может быть получена путем добавления параметров в конструктор контроллера.
// ...
using System;
using Microsoft.Extensions.DependencyInjection;
namespace Core.Controllers
{
public class HomeController : Controller
{
public HomeController(ITestService service)
{
int rnd = service.GenerateRandom();
}
}
}
Включение зависимости в действие контроллера
Менее известной встроенной функцией является инъекция Controller Action с использованием FromServicesAttribute
.
[HttpGet]
public async Task<IActionResult> GetAllAsync([FromServices]IProductService products)
{
return Ok(await products.GetAllAsync());
}
Важно отметить, что [FromServices]
нельзя использовать в качестве общего механизма «Инъекция свойств» или «Метод инъекции»! Его можно использовать только для параметров метода контроллера или конструктора контроллера (в конструкторе он устарел, хотя, поскольку система ASP.NET Core DI уже использует инъекцию конструктора, и дополнительных маркеров не требуется).
Он не может использоваться нигде вне контроллеров, действий контроллера . Также он очень специфичен для ASP.NET Core MVC и находится в сборке Microsoft.AspNetCore.Mvc.Core
.
Исходная цитата из проблемы ASP.NET Core MVC GitHub ( Limit [FromServices] для применения только к параметрам ) относительно этого атрибута:
@rynowak:
@Eilon:
Проблема со свойствами заключается в том, что многим кажется, что он может применяться к любому свойству любого объекта.
Договорились, что у нас было множество проблем, возникших у пользователей с путаницей в том, как эта функция должна использоваться. Там действительно было довольно большое количество отзывов обоим типах «[FromServices] странно, и мне это не нравится» и «[FromServices] меня смутило». Это похоже на ловушку, и что-то, что команда все равно будет отвечать на вопросы через несколько лет.
Мы считаем, что наиболее ценный сценарий для [FromServices] - это параметр метода для действия для службы, которая вам нужна только в одном месте.
/ cc @ danroth27 - изменения в документах
Любой, кто влюблен в текущую [FromServices], я настоятельно рекомендую изучить систему DI, которая может выполнять инъекцию свойств (например, Autofac).
Заметки:
Любые сервисы, зарегистрированные в системе
[FromServices]
.NET Core Dependency Injection, могут быть введены внутри действия контроллера с помощью[FromServices]
.Наиболее важным случаем является то, когда вам нужна услуга только в одном действии, и вы не хотите загромождать конструктор вашего контроллера другой зависимостью, которая будет использоваться только один раз.
Нельзя использовать за пределами ASP.NET Core MVC (то есть чистых приложений .NET Framework или .NET Core), поскольку он находится в сборке
Microsoft.AspNetCore.Mvc.Core
.Для ввода свойств или метода вы должны использовать один из доступных контейнеров IoC сторонних производителей (Autofac, Unity и т. Д.).
Параметр «Параметры» / «Параметры ввода» в сервисы
С помощью ASP.NET Core команда Microsoft также представила шаблон «Параметры», который позволяет иметь сильные типизированные параметры и однажды настроил возможность вводить параметры в свои сервисы.
Сначала мы начинаем с сильного типизированного класса, который будет содержать нашу конфигурацию.
public class MySettings
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
И запись в appsettings.json
.
{
"mysettings" : {
"value1": "Hello",
"value2": "World"
}
}
Затем мы инициализируем его в классе Startup. Есть два способа сделать это
Загрузите его непосредственно из
appsettings.json
«mysettings»services.Configure<MySettings>(Configuration.GetSection("mysettings"));
Сделайте это вручную
services.Configure<MySettings>(new MySettings { Value1 = "Hello", Value2 = Configuration["mysettings:value2"] });
Каждый уровень иерархии
appsettings.json
разделяется:
Посколькуvalue2
является свойством объектаmysettings
, мы обращаемся к нему черезmysettings:value2
.
Наконец, мы можем ввести варианты в наши сервисы, используя интерфейс IOptions<T>
public class MyService : IMyService
{
private readonly MySettings settings;
public MyService(IOptions<MySettings> mysettings)
{
this.settings = mySettings.Value;
}
}
замечания
Если IOptions<T>
не настроен во время запуска, то при вводе IOptions<T>
будет IOptions<T>
экземпляр класса T
по умолчанию.
Использование служб с областью во время запуска приложения / Посещение базы данных
Устранение разрешенных сервисов во время запуска приложения может быть затруднено, потому что нет запроса и, следовательно, нет службы с ограниченным доступом.
Устранение службы с app.ApplicationServices.GetService<AppDbContext>()
во время запуска приложения через app.ApplicationServices.GetService<AppDbContext>()
может вызвать проблемы, поскольку он будет создан в области глобального контейнера, эффективно сделав его одноэлементным с временем жизни приложения, что может привести к к исключениям типа Cannot access a disposed object in ASP.NET Core when injecting DbContext
.
Следующий шаблон решает проблему, сначала создавая новую область и затем разрешая от нее облачные службы, а затем, как только работа будет выполнена, удалите контейнер с областью.
public Configure(IApplicationBuilder app)
{
// serviceProvider is app.ApplicationServices from Configure(IApplicationBuilder app) method
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var db = serviceScope.ServiceProvider.GetService<AppDbContext>();
if (await db.Database.EnsureCreatedAsync())
{
await SeedDatabase(db);
}
}
}
Это полуофициальный способ основной команды Entity Framework для загрузки данных во время запуска приложения и отражается в примере приложения MusicStore .
Разрешить контроллеры, ViewComponents и TagHelpers посредством инъекции зависимостей
По умолчанию контроллеры, ViewComponents и TagHelpers не регистрируются и не разрешаются через контейнер инъекции зависимостей. Это приводит к невозможности выполнения, т. Е. Введению свойств при использовании стороннего контейнера Inversion of Control (IoC), такого как AutoFac.
Чтобы заставить ASP.NET Core MVC разрешать эти типы с помощью IoC, необходимо добавить следующие регистрации в Startup.cs
(взятые из официального образца ControllersFromService на GitHub)
public void ConfigureServices(IServiceCollection services)
{
var builder = services
.AddMvc()
.ConfigureApplicationPartManager(manager => manager.ApplicationParts.Clear())
.AddApplicationPart(typeof(TimeScheduleController).GetTypeInfo().Assembly)
.ConfigureApplicationPartManager(manager =>
{
manager.ApplicationParts.Add(new TypesPart(
typeof(AnotherController),
typeof(ComponentFromServicesViewComponent),
typeof(InServicesTagHelper)));
manager.FeatureProviders.Add(new AssemblyMetadataReferenceFeatureProvider());
})
.AddControllersAsServices()
.AddViewComponentsAsServices()
.AddTagHelpersAsServices();
services.AddTransient<QueryValueService>();
services.AddTransient<ValueService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
Пример внедрения простой зависимости (без Startup.cs)
Это покажет вам, как использовать пакет Microsoft.Extensions.DependencyInjection nuget без использования WebHostBuilder
из пустельги (например, если вы хотите создать что-то еще, а затем webApp):
internal class Program
{
public static void Main(string[] args)
{
var services = new ServiceCollection(); //Creates the service registry
services.AddTransient<IMyInterface, MyClass>(); //Add registration of IMyInterface (should create an new instance of MyClass every time)
var serviceProvider = services.BuildServiceProvider(); //Build dependencies into an IOC container
var implementation = serviceProvider.GetService<IMyInterface>(); //Gets a dependency
//serviceProvider.GetService<ServiceDependingOnIMyInterface>(); //Would throw an error since ServiceDependingOnIMyInterface is not registered
var manualyInstaniate = new ServiceDependingOnIMyInterface(implementation);
services.AddTransient<ServiceDependingOnIMyInterface>();
var spWithService = services.BuildServiceProvider(); //Generaly its bad practise to rebuild the container because its heavey and promotes use of anti-pattern.
spWithService.GetService<ServiceDependingOnIMyInterface>(); //only now i can resolve
}
}
interface IMyInterface
{
}
class MyClass : IMyInterface
{
}
class ServiceDependingOnIMyInterface
{
private readonly IMyInterface _dependency;
public ServiceDependingOnIMyInterface(IMyInterface dependency)
{
_dependency = dependency;
}
}
Внутренняя работа Microsoft.Extensions.DependencyInjection
IServiceCollection
Чтобы начать создание контейнера IOC с пакетом DI nuget от Microsoft, вы начинаете с создания IServiceCollection
. Вы можете использовать уже предоставленную Collection: ServiceCollection
:
var services = new ServiceCollection();
Этот IServiceCollection
представляет собой не что иное, как реализацию: IList<ServiceDescriptor>, ICollection<ServiceDescriptor>, IEnumerable<ServiceDescriptor>, IEnumerable
Все следующие методы - это только методы расширения для добавления экземпляров ServiceDescriptor
в список:
services.AddTransient<Class>(); //add registration that is always recreated
services.AddSingleton<Class>(); // add registration that is only created once and then re-used
services.AddTransient<Abstract, Implementation>(); //specify implementation for interface
services.AddTransient<Interface>(serviceProvider=> new Class(serviceProvider.GetService<IDependency>())); //specify your own resolve function/ factory method.
services.AddMvc(); //extension method by the MVC nuget package, to add a whole bunch of registrations.
// etc..
//when not using an extension method:
services.Add(new ServiceDescriptor(typeof(Interface), typeof(Class)));
IServiceProvider
Serviceprovider - это «компиляция» всех регистраций, чтобы их можно было быстро использовать, это можно сделать с помощью services.BuildServiceProvider()
который в основном является расширением mehtod для:
var provider = new ServiceProvider( services, false); //false is if it should validate scopes
За кулисами каждый ServiceDescriptor
в IServiceCollection
компилируется на фабричный метод Func<ServiceProvider, object>
где object является типом возвращаемого значения и представляет собой: созданный экземпляр типа реализации, Singleton или собственный собственный заводский метод.
Эти регистрации добавляются в ServiceTable
который является в основном ConcurrentDictionary
при этом ключ является ServiceType
и значением, описанным выше.
Результат
Теперь у нас есть ConcurrentDictionary<Type, Func<ServiceProvider, object>>
который мы можем использовать одновременно, чтобы попросить создать Сервисы для нас. Чтобы показать базовый пример того, как это могло выглядеть.
var serviceProvider = new ConcurrentDictionary<Type, Func<ServiceProvider, object>>();
var factoryMethod = serviceProvider[typeof(MyService)];
var myServiceInstance = factoryMethod(serviceProvider)
Это не так, как это работает!
Этот ConcurrentDictionary
является свойством ServiceTable
который является свойством ServiceProvider