ASP.NET Core DI로 인스턴스 해결


302

ASP.NET Core MVC 기본 제공 종속성 주입 프레임 워크를 사용하여 형식을 수동으로 확인하려면 어떻게합니까?

컨테이너 설정은 충분히 쉽습니다.

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

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

그러나 ISomeService주입을 수행하지 않고 어떻게 해결할 수 있습니까? 예를 들어, 나는 이것을하고 싶다 :

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

에 그러한 방법이 없습니다 IServiceCollection.



3
ConfigureServices()방법으로 ( IServiceCollection) 또는 응용 프로그램의 어느 곳에서나 해결 하시겠습니까?
Henk Mollema

2
@HenkMollema : 실제로 Startup 내 어느 곳.
데이브 뉴

답변:


484

IServiceCollection인터페이스에 사용되는 건물 의존성 주입 용기. 완전히 구축 된 후에는 IServiceProvider서비스를 해결하는 데 사용할 수 있는 인스턴스 로 구성됩니다 . IServiceProvider어떤 클래스 에나 주입 할 수 있습니다 . IApplicationBuilderHttpContext 클래스는 통해뿐만 아니라 서비스 제공 업체에 제공 할 수 있습니다 ApplicationServices또는 RequestServices각각 특성.

IServiceProvider 정의 GetService(Type type)서비스를 해결 방법을 .

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

다음과 같은 몇 가지 편리한 확장 방법이 있습니다. serviceProvider.GetService<IFooService>()( usingfor a 추가)Microsoft.Extensions.DependencyInjection .

시작 클래스 내에서 서비스 해결

의존성 주입

런타임의 호스팅 서비스 제공 업체의 생성자에 특정 서비스를 삽입 할 수 Startup와 같은 클래스 IConfiguration, IWebHostEnvironment( IHostingEnvironment3.0 이전 버전), ILoggerFactory그리고 IServiceProvider. 후자는 호스팅 계층에 의해 구축 된 인스턴스 이며 응용 프로그램을 시작하는 데 필요한 서비스 만 포함합니다 .

ConfigureServices()방법은 서비스 주입을 허용하지 않으며 IServiceCollection인수 만 허용합니다 . ConfigureServices()응용 프로그램에 필요한 서비스를 등록하는 위치 이기 때문에 이치에 맞습니다 . 그러나 스타트 업의 생성자에 삽입 된 서비스를 여기에서 사용할 수 있습니다 (예 :

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

public IConfiguration Configuration { get; }

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

등록 된 모든 서비스 ConfigureServices()Configure()메소드에 주입 될 수 있습니다 . 이후에 임의의 수의 서비스를 추가 할 수 있습니다.IApplicationBuilder매개 변수 다음에 .

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

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

종속성 수동 해결

수동 해결 서비스에 필요한 경우, 바람직하게 사용해야 ApplicationServices에서 제공을 IApplicationBuilderConfigure()방법 :

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

클래스 IServiceProvider의 생성자에서 in 을 전달하고 직접 사용할 수 Startup있지만 위와 같이 제한된 서비스 하위 집합이 포함 되므로 유틸리티가 제한적입니다.

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

ConfigureServices()분석법 에서 서비스를 해결해야하는 경우 다른 접근 방식이 필요합니다. 해당 시점까지 등록 된 서비스를 포함 IServiceProvider하는 IServiceCollection인스턴스 에서 중간체 를 빌드 할 수 있습니다 .

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

참고 : 일반적으로 ConfigureServices()메소드 내에서 서비스를 분석하지 마십시오. 실제로 애플리케이션 서비스를 구성 하는 위치 입니다. 때로는 IOptions<MyOptions>인스턴스에 액세스해야 합니다. IConfiguration인스턴스 의 값을 인스턴스 에 바인딩하여이를 수행 할 수 있습니다 MyOptions(기본적으로 옵션 프레임 워크의 기능 임).

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

서비스 수동 해결 (일명 서비스 로케이터)은 일반적으로 안티 패턴으로 간주됩니다 . 프레임 워크 및 / 또는 인프라 계층에 대한 사용 사례가 있지만 가능한 많이 사용하지 않아야합니다.


14
@HenkMollema하지만 어떤 것도 주입 할 수 없다면, 나는 주입 할 수 없다는 것을 의미합니다. IServiceCollection수동으로 생성되는 클래스 ( 중간 범위에서 벗어남 ), 내 경우에는 스케줄러이며 정기적으로 생성하는 서비스가 필요합니다 이메일을 보내십시오.
메르 단 Gochmuradov

52
경고 서비스를 해결해야 ConfigureServices하고 해당 서비스가 싱글 톤 인 경우 경고 는 사용하는 싱글 톤과 다릅니다 Controller. 나는 이것이 다른 것을 사용하기 때문이라고 생각합니다 IServiceProvider-이것을 피하기 위해 해결하지 않고 BuildServiceProvider대신 싱글 톤의 조회 ConfigureServicesConfigure(..other params, IServiceProvider serviceProvider)안으로 이동하십시오Startup.cs
wal

3
@wal 좋은 지적. 다른 IServiceProvider인스턴스 이기 때문에 새로운 싱글 톤 인스턴스를 만듭니다. ConfigureServices애플리케이션에서 사용하는 컨테이너가되도록 메소드 에서 서비스 제공자 인스턴스를 리턴하여이를 피할 수 있습니다.
Henk Mollema 2016 년

1
호출 collection.BuildServiceProvider();은 내가 필요한 것입니다, 감사합니다!
Chris Marisic 17

2
@HenkMollema 하나의 서비스 제공 업체 인스턴스에서만 작동하게하려면 어떻게해야합니까? 일반적으로 1) 일부 종속성을 등록하십시오. 2) 임시 서비스 제공자 인스턴스를 작성하십시오. 3) 해당 서비스 제공자를 사용하여 다른 종속성을 등록하는 데 필요한 사항을 해결하십시오. 이후에 일부 종속 항목이 누락되어 (3에 등록 된) 중간 인스턴스를 리턴 할 수 없습니다. 뭔가 빠졌습니까?
Filip

109

인스턴스를 수동으로 해결하려면 IServiceProvider 인터페이스를 .

Startup.ConfigureServices에서 종속성 해결

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

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

시작시 종속성 해결

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

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

    // ...
}

ASP.NET Core 3에서 Startup.Configure의 종속성 해결

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

런타임 주입 서비스 사용

일부 유형은 메소드 매개 변수로 주입 될 수 있습니다.

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";

1
@AfsharMohebbi GetService는 일반적인 Microsoft.Extensions.DependencyInjection네임 스페이스 의 확장 메소드입니다 .
ahmadali shafiee 16시

확장 메서드 정보 : 확장 메서드는 클래스에 기능을 추가하는 정적 메서드입니다. public static TheReturnType TheMethodName (this TheTypeYouExtend theTypeYouExtend {// BODY}를 선언하면 다음과 같이 사용할 수 있습니다. TheTypeYouExtend.TheMethodName (); 개발자가 기본 기능을 확장 할 수 있도록 .NET Core에서 매우 일반적인 도구가되었습니다 ... 여기에 좋은 예가 있습니다. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
Juan

17

템플릿으로 응용 프로그램을 생성하면 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();
        }

10

등록중인 다른 종속성의 생성자에게 전달하기 위해 하나의 종속성을 해결해야하는 경우이를 수행 할 수 있습니다.

문자열과 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/")
);

OP는 ConfigureService 메소드에서 서비스를 해결해야 할 이유를 밝히지 않았지만, 아마도 누군가가 그렇게 생각하는 이유 일 것입니다
kilkfoe

1
실제로 이것은 받아 들일 수있는 대답이어야합니다 ... Henk Mollema의 대답은 매우 잘 설명되어 있지만 요즘 귀하의 대답은 깨끗하고 중간 IServiceProvider (다른 싱글 톤 인스턴스)를 구축하는 것과 관련된 문제는 발생하지 않습니다. 아마도이 솔루션은 2015 년에 헨크가 물러 났을 때 사용할 수 없었지만 지금은 그 길을 가고 있습니다.
Vi100

이것을 시도했지만 ISomeService여전히 나를 위해 널이었다.
ajbeaven

2 질문 : 1) 서비스 클래스 AnotherService의 매개 변수 생성자가 변경되거나 (제거되거나 추가 된 서비스), 서비스 IAnotherService의 레지스터 세그먼트를 수정해야합니까? 2) 대신 public AnotherService (IServiceProvider serviceProvider)와 같은 1 매개 변수로 AnotherService에 대해 하나의 생성자를 추가하고 생성자에서 필요한 서비스를 얻을 수 있습니다. 그리고 services.AddTransient <IAnotherService, AnotherService> (sp => {var service = new AnotherService (sp); return service;});와 같은 Startup 클래스에 서비스 클래스 AnotherService를 등록해야합니다.
토마스 벤츠

2

이런 방식으로 AuthorizeAttribute와 같은 속성에 종속성을 삽입 할 수 있습니다.

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

이것은 내가
찾던 것이다

0

나는 이것이 오래된 질문이라는 것을 알고 있지만 오히려 명백하고 역겨운 해킹이 여기에 없다는 것에 놀랐습니다.

정의한대로 서비스에서 필요한 값을 가져 오기 위해 고유 한 ctor 함수를 정의 할 수있는 기능을 활용할 수 있습니다. 분명히 명시 적으로 정의를 제거 / 삭제하고 다시 추가하지 않는 한 서비스가 요청 될 때마다 실행됩니다. 이 서비스 는 악용 ctor의 첫 번째 구성 내에서 수행 됩니다.

이 방법은 서비스 구성 중에 서비스 트리를 구축하거나 사용하지 않아도되는 이점이 있습니다. 여전히 서비스 구성 방법을 정의하고 있습니다.

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

이 패턴을 수정하는 방법은 에 대한 암시 적으로 또는 메서드의 반환 값에 의존하지 않고 다른 방식으로 해당 종속성을 명시 적으로 해결하는 것이 아니라 OtherService에 대한 명시 적 종속성 을 제공 하는 IServiceINeedToUse것입니다.


-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
코드 스 니펫뿐만 아니라 좋은 답변 인지에 대한 간단한 설명을 제공하면 답변이 승인되고 공표 될 가능성이 높습니다 . 또한 실제로 질문 한 질문에 대한 답변인지 확인하는 데 도움이됩니다.
Jim L

누군가가 귀하의 답변을 저품질로 잘못 표시했습니다. 추가 플래그 및 / 또는 다운 보트를 방지하기 위해 답변이 어떻게 작동하는지 설명하기 위해 함께 제공되는 텍스트를 추가해야합니다. 코드 전용 답변 은 품질이 좋지 않습니다 . 질문에 대답하려고합니까? 그렇지 않은 경우 '답변 아님'으로 플래그를 지정하거나 검토 대기열에있는 경우 삭제를 권장합니다. b) 기술적으로 잘못 되었습니까? 공감 또는 의견. 검토에서 .
Wai Ha Lee
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.