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();
}
}
}
종속성 등록
Builtin 컨테이너에는 다음과 같은 기본 기능 세트가 있습니다.
평생 제어
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();
}
}
}
컨트롤러 동작에 종속성 주입
잘 알려지지 않은 내장 기능은 FromServicesAttribute
사용하는 Controller Action 주입입니다.
[HttpGet]
public async Task<IActionResult> GetAllAsync([FromServices]IProductService products)
{
return Ok(await products.GetAllAsync());
}
중요한 것은 [FromServices]
가 일반적인 "Property Injection"또는 "Method Injection"메커니즘으로 사용될 수 없다는 것입니다. 컨트롤러 액션 또는 컨트롤러 생성자의 메서드 매개 변수에서만 사용할 수 있습니다 (ASP.NET Core DI 시스템에서 이미 생성자 주입을 사용하고 있으므로 별도의 마커가 필요하지 않으므로 생성자에서는 더 이상 사용되지 않습니다).
컨트롤러 외부의 어느 곳에서도 사용할 수 없습니다 . 또한 ASP.NET Core MVC에만 적용되며 Microsoft.AspNetCore.Mvc.Core
어셈블리에 있습니다.
이 속성과 관련하여 ASP.NET 코어 MVC GitHub 문제 ( [FromServices]를 매개 변수에만 적용)로 제한하십시오 .
@rynowak :
@ 아이론 :
속성의 문제는 많은 개체에게 모든 개체의 모든 속성에 적용 할 수있는 것으로 보입니다.
우리는 사용자가이 기능의 사용 방법을 혼동스럽게 생각하여 여러 가지 문제를 게시했습니다. "[FromServices]가 이상하고 나는 그것을 좋아하지 않는다는 것과"[FromServices]가 나를 혼란에 빠뜨렸다 "라는 두 가지 종류의 피드백이 실제로 많이있었습니다. 함정과 팀이 지금부터 몇 년 후에도 계속 질문에 대답 할 것이라고 생각합니다.
[FromServices]가 메소드 매개 변수에 가장 가치있는 시나리오 인 것처럼 느껴집니다. 한 곳에서만 필요한 서비스에 대한 작업입니다.
/ cc @ danroth27 - docs 변경
현재 [FromServices]와 사랑에 빠진 누구에게나 속성 주입을 할 수있는 DI 시스템 (Autofac)을 살펴 보는 것이 좋습니다.
노트:
.NET Core Dependency Injection 시스템에 등록 된 모든 서비스는
[FromServices]
속성을 사용하여 컨트롤러의 동작 내에 삽입 될 수 있습니다.가장 관련있는 경우는 단일 작업 메서드에서만 서비스가 필요하고 컨트롤러의 생성자를 다른 종속성과 함께 혼란스럽게 만들고 싶지 않을 때입니다.이 종속성은 한 번만 사용됩니다.
Microsoft.AspNetCore.Mvc.Core
어셈블리에 있기 때문에 ASP.NET Core MVC (순수한 .NET Framework 또는 .NET Core 콘솔 응용 프로그램) 외부에서는 사용할 수 없습니다.속성 또는 메소드 주입을 위해서는 사용 가능한 타사 IoC 컨테이너 (Autofac, Unity 등) 중 하나를 사용해야합니다.
옵션 패턴 / 서비스에 옵션 주입
Microsoft 팀은 ASP.NET 코어를 통해 강력한 형식화 된 옵션을 제공하고 한 번 옵션을 서비스에 삽입 할 수있는 옵션 패턴을 도입했습니다.
먼저 우리의 설정을 유지할 강력한 타입의 클래스로 시작합니다.
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>
를 주입하면 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 샘플 응용 프로그램에 반영됩니다.
Dependency Injection을 통해 컨트롤러, ViewComponents 및 TagHelpers 해결
기본적으로 컨트롤러, ViewComponents 및 TagHelpers는 종속성 주입 컨테이너를 통해 등록 및 해결되지 않습니다. AutoFac과 같은 제 3 자 Inversion of Control (IoC) 컨테이너를 사용할 때 속성 주입을 수행 할 수 없게됩니다.
ASP.NET 코어 MVC가 IoC를 통해 이러한 유형을 해결할 수있게하려면 Startup.cs
다음 등록을 추가해야합니다 (GitHub의 공식 ControllersFromService 샘플 에서 가져옴)
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>();
}
Plain Dependency Injection 예제 (Startup.cs없이)
이것은 kestrel의 WebHostBuilder
를 사용하지 않고 Microsoft.Extensions.DependencyInjection nuget 패키지를 사용하는 방법을 보여줍니다 (예 : 다른 웹 애플리케이션을 빌드하려는 경우).
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
Microsoft의 DI nuget 패키지로 IOC 컨테이너를 작성하려면 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()
를 services.BuildServiceProvider()
합니다 .BuildServiceProvider services.BuildServiceProvider()
는 기본적으로 다음에 대한 확장 메트로 (mehtod)입니다.
var provider = new ServiceProvider( services, false); //false is if it should validate scopes
백그라운드에서 IServiceCollection
모든 ServiceDescriptor
가 팩터 리 메서드 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
는 ServiceProvider
의 속성 인 ServiceTable
의 속성입니다.