Novidades do .NET 8: melhorias na injeção de dependências
Neste novo artigo dou continuidade à série sobre novidades do .NET 8, abordando agora melhorias no mecanismo nativo de injeção de dependências da plataforma .NET. Trata-se de uma capacidade bastante aguardada, sobretudo por muitos Desenvolvedores que tiveram algum contato com frameworks para injeção de dependências como Autofac e Spring.NET: a possibilidade de registrar e resolver dependências utilizando chaves identificadoras, mecanismo este batizado como Keyed dependency injection.
Já utilizo há algum tempo (desde o .NET Core 1.x) um mesmo exemplo - clique neste link para acessar a versão em .NET 7 - a fim de explicar o funcionamento do mecanismo nativo de injeção de dependências que integra o .NET. Esta implementação sofreu poucas variações a cada lançamento de uma nova versão da plataforma, envolvendo o uso de 2 interfaces (ITesteA e ITesteB):
E 3 classes concretas (incluindo implementações das interfaces ITesteA e ITesteB):
Ao analisar esses tipos notamos que os mesmos possuem praticamente a mesma estrutura e, pensando melhor a respeito, logo surge o questionamento: não poderíamos resolver tudo isso com uma única interface e uma classe que deriva desta última? Se considerarmos a existência de 3 possíveis dependências (ITesteA, ITesteB e TesteC) diferentes e o suporte existente até o .NET 7, a resposta infelizmente é não. É o que demonstra a listagem a seguir, com o uso dos métodos AddSingleton, AddTransient e AddScoped:
Como já mencionei no início deste post, o .NET 8 traz como uma de suas melhorias a possibilidade de resolvermos dependências através do uso de diferentes chaves. Esse novo comportamento supera então a limitação presente até o .NET 7, em que a impossibilidade de se definirem chaves para as dependências poderia acarretar na criação de múltiplas interfaces e classes com uma mesma estrutura geral.
O exemplo que apresentarei aqui já foi disponibilizado no GitHub:
https://github.com/renatogroffe/ASPNETCore8-RESTAPI-DI
Caso achem útil esta solução, peço por favor um ⭐️ no repositório apoiando. Fica também o convite para que vocês me sigam lá no GitHub!
As próximas listagens detalhes demonstram a utilização da funcionalidade Keyed dependency injection.
A interface ITesteDI terá suas dependências resolvidas ao longo da aplicação, contando com a propriedade somente leitura IdReferencia que armazena um GUID:
Já a classe TesteDI implementa a interface ITesteDI, contando com um construtor responsável pelo preenchimento da propriedade IdReferencia:
Temos também nesta solução 2 estruturas importantes para demonstrar as novidades envolvendo injeção de dependências:
- O tipo ResultadoInjecao conta com as propriedades Horario, ValoresA e ValoresB;
- Essas 2 últimas propriedades (ValoresA e ValoresB) são instâncias da classe ValoresInjecaoUsandoKey, a qual conterá a chave empregada na injeção de dependências (propriedade Key), o valor de um GUID de ITesteDI injetado a partir do construtor de um Controller (propriedade Construtor) e este mesmo conteúdo resolvido em um método que represente um endpoint (propriedade Action);
- O método ToString de ValoresInjecaoUsandoKey retorna uma string com os valores de GUID no injetados no construtor e na Action de um Controller, tendo sido implementado para efeitos de logging.
No arquivo Program.cs estão configuradas as dependências a serem resolvidas por meio do uso de Keys/chaves:
- As dependências foram configuradas (linhas 6 a 13) utilizando os métodos AddKeyedSingleton, AddKeyedScoped e AddKeyedTransient, os quais contam com um comportamento similar a AddSingleton, AddScoped e AddTransient. A única diferença está na indicação de uma chave, que pode se basear em qualquer tipo de objeto (para este exemplo foram usadas strings);
- Para tipo de configuração (Singleton, Scoped e Transient) foram configuradas 2 dependências. Podemos inclusive utilizar uma sobrecarga que recebe um delegate voltado à inicialização do objeto empregado na resolução de uma dependência, como no caso de SingletonB (linha 7).
Para testar as dependências fiz uso no Visual Studio 2022 de um arquivo .http com as seguintes chamadas:
A classe SingletonController permitirá que se testem dependências envolvendo o uso de Singletons. As dependências SingletonA e SingletonB estão baseadas na interface ITesteDI, sendo resolvidas no construtor deste Controller e na Action Get por meio da utilização do atributo FromKeyedServices (tipo FromKeyedServicesAttribute, localizado no namespace Microsoft.Extensions.DependencyInjection):
Executando os testes através do Visual Studio 2022 podemos observar que os valores de SingletonA e SingletonB se mantêm constantes, o que caracteriza um comportamento em conformidade com o design pattern Singleton (uma única instância de uma dependência durante o tempo de vida da aplicação):
Os logs gerados por esta aplicação também atestam isso, conforme print a seguir:
Já o tipo ScopedController demonstrará a resolução de dependências baseadas no modo Scoped. Em relação ao Controller anterior, temos aqui uma diferença: as dependências ScopedA e ScopedB serão resolvidas na Action Get por meio de uma instância de IServiceProvider (esta mesma também injetada), invocando-se para isto o método genérico GetRequiredKeyedService e informando ao mesmo a chave esperada.
Na próxima animação observamos a execução dos testes, com os valores de ScopedA e ScopedB permanecendo constantes durante o processamento de uma requisição recebida pelo Controller (a mudança no conteúdo ocorrerá a cada nova requisição):
Por fim, a classe TransientController permitirá que se teste a utilização do modo Transient. Desta vez o uso de IServiceProvider com o método GetRequiredKeyedService foi transferido para o construtor, com a Action Get tendo as dependências resolvidas através do atributo FromKeyedServices:
A animação seguinte mostra que cada dependência encontrada para TransientA e TransientB (construtor e Action Get) resultará em um novo valor GUID:
Algo também demonstrado no próximo print: