A injeção de dependência em C# é um salva-vidas ao organizar dependências — especialmente em aplicativos ASP.NET Core mais complexos. E se você já estiver familiarizado com IServiceCollection ou apenas quiser ficar o mais próximo possível das ofertas de DI já fornecidas, o Scrutor em C# é um grande aprimoramento.
Neste artigo, fornecerei uma visão geral de alto nível de Injeção de Dependência e Scrutor em C#. Mas a partir daí, vamos direto para três dicas que você pode usar com o Scrutor e que podem ser muito úteis em sua aplicação!
Scrutor é um pacote NuGet poderoso em C# que aprimora a injeção de dependência. Simplifica o registro e a resolução de dependências, facilitando o gerenciamento e a organização do seu código.
A injeção de dependência é uma abordagem popular para configurar aplicativos que promove acoplamento fraco e melhora a testabilidade e a capacidade de manutenção. Envolve injetar dependências em uma classe, em vez de criá-las diretamente dentro da classe. Isso significa que a responsabilidade de criar a dependência não pertence à classe que a exige, mas sim a alguém na pilha de chamadas. Eventualmente, isso empurra quase toda a criação de dependências para o ponto de entrada de um aplicativo, tornando-o complicado. No entanto, os frameworks de injeção de dependência ajudam a limpar e organizar toda essa lógica .
O Scrutor leva esse conceito um passo adiante, fornecendo uma maneira simples e intuitiva de registrar e resolver dependências. Com o Scrutor, você não precisa mais registrar manualmente cada dependência, uma por uma. Em vez disso, você pode usar convenções e atributos para automatizar o processo.
Para ilustrar como usar o Scrutor em C# para registro e resolução, vamos considerar um cenário simples. Imagine que temos uma aplicação que requer o uso de diferentes repositórios de dados, como UserRepository e ProductRepostiory.
Primeiro, precisamos instalar o pacote Scrutor NuGet em nosso projeto. Abra o Console do Gerenciador de Pacotes e execute o seguinte comando:
Install-Package Scrutor
A seguir, precisamos definir nossos repositórios e suas interfaces correspondentes. Este exemplo está essencialmente vazio, mas iremos nos referir a eles em um momento:
public interface IUserRepository { // Interface methods } public class UserRepository : IUserRepository { // Implementation } public interface IProductRepository { // Interface methods } public class ProductRepository : IProductRepository { // Implementation }
Agora, vamos conectar nossas dependências usando o Scrutor. No código de inicialização (como no método ConfigureServices no ASP.NET Core), adicione o seguinte código:
services.Scan(scan => scan .FromAssemblyOf<Startup>() .AddClasses(classes => classes.AssignableToAny( typeof(IUserRepository), typeof(IProductRepository))) .AsImplementedInterfaces() .WithScopedLifetime());
Este código usa o método Scan do Scrutor para verificar o assembly que contém a classe Startup. Em seguida, ele filtra as classes que podem ser atribuídas às interfaces IUserRepository ou IProductRepository. Por fim, mapeia as implementações dessas interfaces e as registra em suas respectivas interfaces.
Agora podemos injetar essas dependências em nossas classes. Por exemplo, digamos que temos uma classe UserService que requer IUserRepository:
public class UserService { private readonly IUserRepository _userRepository; public UserService(IUserRepository userRepository) { _userRepository = userRepository; } // Rest of the class implementation }
Ao declarar IUserRepository como uma dependência no construtor, o Scrutor junto com IServiceCollection resolverá e injetará automaticamente a instância UserRepository para nós.
Imagine que você tem uma interface de serviço IService
e uma implementação MyService
. Você deseja registrar a entrada e a saída de cada chamada de método em MyService
sem poluir sua lógica de negócios com preocupações de registro.
Primeiro, defina a interface IService
e sua implementação:
public interface IService { void DoWork(); } public class MyService : IService { public void DoWork() { // Business logic here } }
Em seguida, crie uma classe decoradora LoggingDecorator
que implemente IService
. Esta classe envolverá o serviço real e adicionará registro em torno das chamadas de método delegado:
public class LoggingDecorator : IService { private readonly IService _decorated; private readonly ILogger<LoggingDecorator> _logger; public LoggingDecorator(IService decorated, ILogger<LoggingDecorator> logger) { _decorated = decorated; _logger = logger; } public void DoWork() { _logger.LogInformation("Starting work."); _decorated.DoWork(); _logger.LogInformation("Finished work."); } }
Agora, use o Scrutor para registrar seu serviço e seu decorador no Startup.cs
ou onde quer que você configure seus serviços:
public void ConfigureServices(IServiceCollection services) { // Register the base service services.AddScoped<IService, MyService>(); // Use Scrutor to apply the decorator services.Decorate<IService, LoggingDecorator>(); // Make sure to register the ILogger or ILogger<T> dependencies if not already done }
Esta configuração usa o Scrutor para agrupar o registro IService
com o LoggingDecorator
. Ao resolver IService
, o contêiner DI fornecerá uma instância de LoggingDecorator
, que por sua vez agrupa uma instância de MyService
. Essa abordagem consegue a separação de interesses, mantendo a lógica de registro fora da lógica de negócios.
Quando estamos construindo sistemas complexos, muitas vezes há casos em que queremos introduzir algo como sinalizadores de recursos. Isso nos permite seguir diferentes caminhos de código com base na configuração e não precisar recompilar e implantar um aplicativo inteiro. Em algumas situações, não se trata apenas de escolher um caminho de código diferente, mas de trocar uma implementação inteira de algo!
Vamos experimentar um exemplo juntos! Suponha que você tenha uma interface IFeatureService
com múltiplas implementações: NewFeatureService
(um novo recurso experimental) e StandardFeatureService
(o recurso atual e estável). Você deseja alternar entre essas implementações em tempo de execução com base em um sinalizador de configuração.
Primeiro, defina a interface IFeatureService
e suas implementações:
public interface IFeatureService { void ExecuteFeature(); } public class NewFeatureService : IFeatureService { public void ExecuteFeature() { // New feature logic } } public class StandardFeatureService : IFeatureService { public void ExecuteFeature() { // Standard feature logic } }
Em seguida, você precisa determinar qual implementação usar com base em uma configuração de alternância de recursos. Pode ser um valor em appsettings.json
, uma configuração de banco de dados ou qualquer outra fonte de configuração.
Agora, use o Scrutor para registrar dinamicamente a implementação de serviço apropriada com base na alternância de recursos:
public void ConfigureServices(IServiceCollection services) { // Assume GetFeatureToggleValue() retrieves the current feature toggle setting var useNewFeature = GetFeatureToggleValue("UseNewFeature"); // Dynamically register the appropriate implementation based on the feature toggle if (useNewFeature) { services.AddScoped<IFeatureService, NewFeatureService>(); } else { services.AddScoped<IFeatureService, StandardFeatureService>(); } // Optional: You could combine this with the previous example // to use the decorator pattern too! // services.Decorate<IFeatureService, FeatureServiceProxy>(); } private bool GetFeatureToggleValue(string featureName) { // This method would typically check your // configuration source to determine if the feature is enabled // For simplicity, this is just a placeholder return false; // or true based on actual configuration }
Adotar uma abordagem como essa pode nos ajudar com:
Neste artigo, forneci três dicas simples para injeção de dependência com Scrutor em C#. Após uma breve visão geral do que é injeção de dependência e como o Scrutor se encaixa nela, passamos direto aos nossos exemplos. Vimos a verificação de assembly, como decorar dependências e até mesmo como considerar a sinalização de recursos de implementações inteiras!
A injeção de dependência é algo que pode simplificar bastante seus aplicativos à medida que crescem em complexidade, e o Scrutor pode ser uma grande ajuda se você quiser manter a oferta integrada IServiceCollection da Microsoft . 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 ! Junte-se a mim e aos outros membros da comunidade no Discord !
Também publicado aqui.