Há muitas maneiras de gerenciar a injeção de dependência dentro de nossos aplicativos e acho importante compreender os benefícios e as limitações de diferentes abordagens. Usar um Autofac ContainerBuilder
no ASP.NET Core como a principal forma de estruturar suas dependências em vez de usar o AutofacServiceProviderFactory
sugerido é um caminho que podemos explorar!
Neste artigo, destaco como usar o ContainerBuilder do Autofac em seu aplicativo ASP.NET Core em vez do AutofacServiceProviderFactory. Veremos o que você pode ou não fazer com essa abordagem em comparação com outras abordagens às quais temos acesso com injeção de dependência.
Isso fará parte de uma série em que exploro a resolução de dependências com Autofac dentro do ASP.NET Core. Com certeza incluirei a série abaixo à medida que as edições forem publicadas:
Ao final desta série, você poderá explorar com mais confiança as arquiteturas de plug-ins dentro do ASP.NET Core e do Blazor, o que fornecerá ainda mais conteúdo para você explorar.
Para ser justo, o título da seção é *quase* uma isca de clique. Eu realmente acho que AutofacServiceProviderFactory
usado como a maneira sugerida de configurar o Autofac em seus aplicativos ASP.NET Core é ótimo para a maioria dos aplicativos. A grande maioria dos desenvolvedores que desejam usar o Autofac como estrutura de injeção de dependência preferida não enfrentaria muitos problemas dessa maneira.
Isso nos permite a capacidade de:
WebApplicationBuilder
(e qualquer coisa disponível neste momento)IConfiguration
(também disponível na instância WebApplicationBuilder
)
Mas o grande problema para mim: não podemos acessar a instância WebApplication
. Quando construo arquiteturas de plug-ins em C#, principalmente ao criar aplicativos ASP.NET Core, gosto de ter acesso à instância WebApplication
para registrar rotas. Isso me permite registrar APIs mínimas de meus plug-ins com facilidade, que tecnicamente só precisam de acesso a uma implementação de IEndpointRouteBuilder
para obter a sintaxe útil.
Posso registrar APIs não mínimas sem isso? Absolutamente. Existe outra maneira de fornecer sintaxe semelhante e não exigir uma instância WebApplication
? Muito provavelmente. Mas, em vez de tentar contornar ESSE problema, eu queria ver se conseguia acessar a dependência na qual estou interessado.
Era hora de mudar o plano de configuração do meu contêiner de dependência!
Vejamos um exemplo de aplicativo para que tenhamos alguns pontos em comum a explorar. Se você leu o artigo anterior , isso será semelhante — uma variação do aplicativo de amostra de clima:
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);
Um destaque dessa abordagem é que usar a classe Autofac ContainerBuilder
como nosso contêiner de dependência principal nos dá a oportunidade de estruturar nosso ponto de entrada apenas para:
Este é, na minha opinião, o código de inicialização de aplicativo ideal. Por que? Porque você nunca precisa voltar aqui para tocá-lo. Sempre. Não importa quantas coisas você adicione! E isso tudo porque você pode digitalizar montagens para carregar mais módulos.
Mais uma vez, esta é uma preferência pessoal minha e não estou tentando afirmar que este deva ser o objetivo de todos.
Claro, outra abordagem que não é totalmente à prova de balas. Então, vamos discutir o que NÃO obtemos com esta configuração do Autofac:
WebApplication
que foi construído não está configurado para usar Autofac como estrutura de injeção de dependência!WebApplication
no contêiner de dependência… Portanto, não fizemos nenhum avanço específico no acesso a ela.
Mas é principalmente isso! Não é uma lista terrível de desvantagens, mas a abordagem Autofac ContainerBuilder não foi uma solução mágica para nós. Então, o que ganhamos com isso? também ajudará a explicar o seguinte:
Prós e contras de tudo o que fazemos! Agora que vimos os problemas com o Autofac ContainerBuilder no ASP.NET Core, é hora de ver quais avanços obtivemos com isso:
WebApplicationBuilder
e IConfiguration
, portanto esse é um benefício comparável ao uso da abordagem AutofacServiceProviderFactory
.Vimos que poderíamos registrar APIs mínimas dentro de um método de registro Autofac, mas infelizmente não podemos resolver dependências do contêiner diretamente na própria chamada de API mínima. Poderíamos construir uma classe dedicada como a seguinte, que lida com definições de rotas com dependências sendo resolvidas automaticamente:
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; } }
A resolução automática acontece se tivermos esta classe E todas as dependências no mesmo container. Então é só chamar esse código:
var weatherForecastRoutes = ctx.Resolve<WeatherForecastRoutes>(); app.MapGet("/weatherforecast2", weatherForecastRoutes.Forecast);
Isso ainda é um pouco chato do ponto de vista do plug-in, porque precisaríamos resolver todas as classes de rota manualmente apenas para chamar o código de registro dessa forma - tudo decorrente do fato de que essas coisas não podem resolver seu próprio acesso ao WebApplication
instância.
Mas espere… E se invertermos as coisas? E se pudéssemos resolver alguma interface como IRegisterRoutes
e passar a instância 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); }
Agora, nem precisamos nos preocupar se a instância WebApplication
está acessível aos nossos plugins! Talvez a primeira versão não fosse tão limitante, afinal. Talvez estejamos no caminho certo… Porém, o próximo artigo deverá explicar isso com mais detalhes.
Neste artigo, explorei o uso explícito de um Autofac ContainerBuilder
em vez de usar AutofacServiceProviderFactory
como normalmente é sugerido. Vimos alguns benefícios e desvantagens semelhantes, mas também um conjunto diferente de coisas a considerar. Cada forma pode oferecer prós e contras, dependendo do que você procura em seu aplicativo.
O interessante é que se estivermos tentando trabalhar com plug-ins, talvez nem precisemos acessar a instância WebApplication
de nossos plug-ins! Se nos preocupamos com APIs mínimas, isso ainda pode ser limitante... mas por outro lado, estamos em uma linha de pensamento interessante!
Se você achou isso útil e está procurando mais oportunidades de aprendizado, considere assinar meu boletim informativo semanal gratuito sobre engenharia de software e conferir meus vídeos gratuitos no YouTube !