애플리케이션 내에서 종속성 주입을 관리하는 방법에는 여러 가지가 있으며, 다양한 접근 방식의 이점과 한계를 이해하는 것이 중요하다고 생각합니다. 제안된 AutofacServiceProviderFactory
사용하는 대신 종속성을 구조화하는 기본 방법으로 ASP.NET Core에서 Autofac ContainerBuilder
사용하는 것이 우리가 탐색할 수 있는 경로 중 하나입니다!
이 문서에서는 AutofacServiceProviderFactory 대신 ASP.NET Core 애플리케이션에서 Autofac의 ContainerBuilder를 사용하는 방법을 강조합니다. 종속성 주입을 통해 액세스할 수 있는 다른 접근 방식과 비교하여 이 접근 방식으로 수행할 수 있는 작업과 수행할 수 없는 작업을 살펴보겠습니다.
이는 ASP.NET Core 내부에서 Autofac을 사용하여 종속성 해결을 탐색하는 시리즈의 일부입니다. 이슈가 게시되면 아래 시리즈를 반드시 포함하겠습니다.
이 시리즈가 끝나면 ASP.NET Core 및 Blazor 내부의 플러그인 아키텍처를 더 자신있게 탐색할 수 있으며 탐색할 수 있는 더 많은 콘텐츠가 제공됩니다.
공평하게 말하면 섹션 제목은 *거의* 클릭 미끼입니다. ASP.NET Core 애플리케이션에서 Autofac을 설정하기 위해 제안된 방법 으로 AutofacServiceProviderFactory
사용하는 것이 대부분의 애플리케이션에 적합하다고 생각합니다. Autofac을 종속성 주입 프레임워크로 사용하려는 대다수의 개발자는 이런 식으로 많은 문제에 직면하지 않을 것입니다.
이는 우리에게 다음과 같은 능력을 제공합니다.
WebApplicationBuilder
(및 현재 사용 가능한 모든 항목)에 액세스합니다.IConfiguration
인스턴스에 대한 액세스( WebApplicationBuilder
인스턴스에서도 사용 가능)
하지만 저에게 가장 큰 문제는 WebApplication
인스턴스에 액세스할 수 없다는 것입니다. C#으로 플러그인 아키텍처를 구축할 때, 특히 ASP.NET Core 애플리케이션을 구축할 때 경로를 등록하기 위해 WebApplication
인스턴스에 액세스하는 것을 좋아합니다. 이를 통해 플러그인에서 최소한의 API를 쉽게 등록할 수 있으며, 기술적으로는 IEndpointRouteBuilder
구현에 액세스해야만 편리한 구문을 얻을 수 있습니다.
이 없이도 최소가 아닌 API를 등록할 수 있나요? 전적으로. 유사한 구문을 제공하고 WebApplication
인스턴스가 필요하지 않은 다른 방법이 있습니까? 가능성이 매우 높다. 하지만 그 문제를 해결하려고 노력하는 대신, 내가 관심 있는 종속성에 액세스할 수 있는지 확인하고 싶었습니다.
이제 종속성 컨테이너를 설정하는 방법에 대한 계획을 변경할 시간이었습니다!
몇 가지 공통점을 알아볼 수 있도록 샘플 애플리케이션을 살펴보겠습니다. 이전 기사를 읽었 다면 이는 샘플 날씨 애플리케이션의 변형인 유사하게 보일 것입니다.
using Autofac; using Microsoft.AspNetCore.Mvc; // personal opinion: // I absolutely love having the entry point of my // applications being essentially: // - make my dependencies // - give me the primary dependency // - use it // - ... nothing else :) ContainerBuilder containerBuilder = new(); containerBuilder.RegisterModule<MyModule>(); using var container = containerBuilder.Build(); using var scope = container.BeginLifetimeScope(); var app = scope.Resolve<WebApplication>(); app.Run(); internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) { public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); } internal sealed class MyModule : Module { protected override void Load(ContainerBuilder containerBuilder) { containerBuilder.RegisterType<DependencyA>().SingleInstance(); containerBuilder.RegisterType<DependencyB>().SingleInstance(); containerBuilder.RegisterType<DependencyC>().SingleInstance(); containerBuilder .Register(ctx => { var builder = WebApplication.CreateBuilder(Environment.GetCommandLineArgs()); return builder; }) .SingleInstance(); containerBuilder .Register(ctx => ctx.Resolve<WebApplicationBuilder>().Configuration) .As<IConfiguration>() .SingleInstance(); containerBuilder .Register(ctx => { var builder = ctx.Resolve<WebApplicationBuilder>(); var app = builder.Build(); app.UseHttpsRedirection(); // FIXME: the problem is that the Autofac ContainerBuilder // was used to put all of these pieces together, // but we never told the web stack to use Autofac as the // service provider. // this means that the minimal API will never be able to // find services off the container. we would need to resolve // them BEFORE the API is called, like in this registration // method itself, from the context that is passed in. //DependencyA dependencyA = ctx.Resolve<DependencyA>(); // FIXME: But... What happens if something wants to take a // dependency on the WebApplication instance itself? Once the // web application has been built, there's no more adding // dependencies to it! var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; app.MapGet( "/weatherforecast", ( [FromServices] DependencyA dependencyA , [FromServices] DependencyB dependencyB , [FromServices] DependencyC dependencyC ) => { var forecast = Enumerable .Range(1, 5) .Select(index => new WeatherForecast ( DateOnly.FromDateTime(DateTime.Now.AddDays(index)), Random.Shared.Next(-20, 55), summaries[Random.Shared.Next(summaries.Length)] )) .ToArray(); return forecast; }); return app; }) .SingleInstance(); } } internal sealed class DependencyA( WebApplicationBuilder _webApplicationBuilder); internal sealed class DependencyB( WebApplication _webApplication); internal sealed class DependencyC( IConfiguration _configuration);
이 접근 방식의 한 가지 설명은 Autofac ContainerBuilder
클래스를 기본 종속성 컨테이너로 사용하면 진입점을 다음과 같이 구성할 수 있다는 것입니다.
제 생각에는 이것이 이상적인 애플리케이션 시작 코드입니다. 왜? 왜냐하면 당신은 그것을 만지기 위해 여기로 다시 올 필요가 없기 때문입니다. 항상. 아무리 많은 것을 추가해도 상관없습니다! 어셈블리를 스캔하여 더 많은 모듈을 로드할 수 있기 때문입니다.
다시 말하지만, 이것은 내 개인적인 취향이며 이것이 모든 사람의 목표가 되어야 한다고 주장하려는 것은 아닙니다.
물론, 방탄이 아닌 또 다른 접근 방식입니다. 이제 이 Autofac 설정으로 얻을 수 없는 것이 무엇인지 논의해 보겠습니다.
WebApplication
Autofac을 종속성 주입 프레임워크로 사용하도록 구성되지 않았습니다!WebApplication
인스턴스를 가져올 수 없습니다. 따라서 이에 액세스하는 데 있어 특별히 개선된 사항은 없습니다.
하지만 그게 대부분이에요! 끔찍한 단점 목록은 아니지만 Autofac ContainerBuilder 접근 방식은 우리에게 만능 해결책은 아니었습니다. 그래서 우리는 그것으로부터 무엇을 얻었습니까? 다음 내용을 설명하는 데도 도움이 됩니다.
우리가 하는 모든 일의 장점과 단점! 이제 ASP.NET Core의 Autofac ContainerBuilder 관련 문제를 확인했으므로 이를 통해 어떤 발전을 얻었는지 살펴보겠습니다.
WebApplicationBuilder
및 IConfiguration
인스턴스에 계속 액세스할 수 있으므로 AutofacServiceProviderFactory
접근 방식을 사용하는 것과 비슷한 이점이 있습니다.Autofac 등록 방법 내에서 최소한의 API를 등록할 수 있다는 것을 확인했지만 불행하게도 최소한의 API 호출 자체에서는 컨테이너의 종속성을 직접 해결할 수 없습니다. 종속성이 자동으로 해결되는 경로 정의를 처리하는 다음과 같은 전용 클래스를 빌드할 수 있습니다.
internal sealed class WeatherForecastRoutes( DependencyA _dependencyA // FIXME: still can't depend on this because // we can't get the WebApplication //, DependencyB _dependencyB , DependencyC _dependencyC) { private static readonly string[] _summaries = new[] { "Freezing", "Bracing", // ... }; public WeatherForecast[] Forecast() { var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast ( DateOnly.FromDateTime(DateTime.Now.AddDays(index)), Random.Shared.Next(-20, 55), _summaries[Random.Shared.Next(_summaries.Length)] )) .ToArray(); return forecast; } }
이 클래스와 종속성이 모두 동일한 컨테이너에 있는 경우 자동 해결이 발생합니다. 그러면 다음 코드를 호출하기만 하면 됩니다.
var weatherForecastRoutes = ctx.Resolve<WeatherForecastRoutes>(); app.MapGet("/weatherforecast2", weatherForecastRoutes.Forecast);
등록 코드를 호출하기 위해 모든 경로 클래스를 수동으로 해결해야 하기 때문에 플러그인 관점에서는 여전히 약간 짜증납니다. 이 모든 것은 WebApplication
에 대한 자체 액세스를 해결할 수 없다는 사실에서 비롯됩니다. 사례.
하지만 잠깐... 상황을 뒤집어 보면 어떨까요? IRegisterRoutes
와 같은 일부 인터페이스를 해결하고 WebApplication
인스턴스를 전달할 수 있다면 어떨까요?!
// NOTE: make sure to register WeatherForecastRouteRegistrar on the // autofac container as IRegisterRoutes! internal interface IRegisterRoutes { void RegisterRoutes(WebApplication app); } internal sealed class WeatherForecastRouteRegistrar( WeatherForecastRoutes _weatherForecastRoutes) : IRegisterRoutes { public void RegisterRoutes(WebApplication app) { app.MapGet("/weatherforecast2", _weatherForecastRoutes.Forecast); } } // TODO: add this to the autofac code where the // WebApplication instance is built: foreach (var registrar in ctx.Resolve<IEnumerable<IRegisterRoutes>>()) { registrar.RegisterRoutes(app); }
이제 WebApplication
인스턴스가 플러그인에 액세스 가능한지 여부도 신경 쓸 필요가 없습니다! 아마도 첫 번째 버전은 결국 그다지 제한적이지 않았을 것입니다. 어쩌면 우리는 여기에 뭔가를 하고 있을지도 모릅니다… 그러나 다음 기사에서는 이에 대해 더 자세히 설명해야 합니다.
이 기사에서는 일반적으로 제안되는 AutofacServiceProviderFactory
사용하는 대신 명시적으로 Autofac ContainerBuilder
사용하는 방법을 살펴보았습니다. 비슷한 장점과 단점이 있었지만 고려해야 할 사항도 달랐습니다. 각 방법은 지원서에서 원하는 바에 따라 장단점을 제공할 수 있습니다.
흥미로운 점은 플러그인 작업을 시도하는 경우 플러그인에서 WebApplication
인스턴스에 전혀 액세스할 필요조차 없다는 것입니다! 최소한의 API에 관심이 있다면 여전히 제한적일 수 있습니다. 하지만 그렇지 않으면 흥미로운 사고방식을 갖게 됩니다!
이 내용이 유용하고 더 많은 학습 기회를 찾고 있다면 무료 주간 소프트웨어 엔지니어링 뉴스레터를 구독 하고 YouTube에서 내 무료 동영상을 확인해 보세요!