Neste artigo mostrarei a usabilidade, facilidade e performance do gerenciador de container Simple Injector para turbinar sua Injeção de Dependência nas plataformas MVC e Web API usando C#.
Não me arrependo de ter usado Ninject, onde praticamente não era necessário fazer nada, simplesmente criar uma classe que herdasse de NinjectModule, configurar a Injeção de Dependência e correr para o abraço.
Quando me tornei público, ou seja, comecei a contribuir com a comunidade de desenvolvedores, Ninject era como se fosse um palavrão e quase fui apedrejado ao mencioná-lo em grupos de redes sociais. Ninguém é perfeito, por isso não esqueça de ler 50 Erros Comprovados Cometidos por um Programador.
Quando Odair Junior expôs uma dúvida sobre Simple Injector no grupo do facebook ASP.NET Brasil, administrado por Eduardo Pires, diversos colegas começaram a discutir sobre Injeção de Dependência, então achei interessante escrever um artigo para tentar dar uma clareada sobre o assunto.
Antes de mostrar o código do Simple Injector, uma das classes mais usadas para uso de Injeção de Dependência, queria falar um pouco sobre esse Design Pattern “Dependency Injection” e o que ele irá melhorar sua aplicação e sua vida. Podemos então?
O que direi aqui poderia ser uma redundância do que escrevi no meu eBook Programação no Mundo Real Design Patterns Volume 1 que você pode baixar gratuitamente. A diferença para o eBook é que aqui, irei fazer algo do zero até chegar ao Simple Injector.
Agora me responda sinceramente, quantos sistemas você conhece que usam Injeção de Dependência? Se a resposta se aproximou de zero, calma, isso é uma estatística normal e compreensível.
Se você não tem um arquiteto de software na equipe ou algum desenvolvedor que já trabalhou em projeto com um, pouco vai saber o que significa isso e muito menos usar. Mas calma, acho que você vai entender ao final do artigo.
Mas para que usar essa pitomba de Injeção de Dependência?
Injeção de Dependência serve para resolver automaticamente todas as dependências que uma classe possui em seu construtor sem a necessidade de informa-las toda vez que for instanciá-la.
O que você precisa instalar e configurar aí?
Primeiro de tudo, crie uma aplicação MVC e instale o plugin/lib Simple Injector através do NuGet Package Manager. Procure pelos pacotes SimpleInjector.Integration.Web.Mvc.
Sugiro sempre instalação de pacotes estáveis “stable” e nunca versões beta. É claro, a não ser que você curta se aventurar nessas versões beta :D.
Com todos os pacotes instalados, dá uma atualizada na versão do Newtonsoft.JSON através do próprio NuGet, guia de Updates.
O que é NuGet?
NuGet é um gerenciador e instalador de pacotes/bibliotecas da plataforma de desenvolvimento da Microsoft, incluindo Visual Studio.
Antigamente a gente precisava baixar uma DLL por exemplo, colocá-la em uma pasta local e referenciá-la na nossa aplicação.
Compile e teste sua aplicação MVC para ver se pelo menos abre a tela homepage básica que foi criada automaticamente quando você criou o projeto web pelo Visual Studio.
Imagine a classe abaixo que retorna uma lista de produtos de um banco de dados SQL.
public class SqlProductRepository { public IList<string> GetAllProducts(string criteria) { return new List<string>() { "product1", "product2" }; } }
Imagine outra classe, agora uma classe de negócios que retorna uma lista de produtos filtrada por um determinado status. Essa classe de negócios usa o repositório de produtos mencionado acima.
public class ProductService { private readonly SqlProductRepository _productRepository; public ProductService() { _productRepository = new SqlProductRepository(); } public IList<string> GetAllProductsByStatus() { var criteria = "Status=1"; return _productRepository.GetAllProducts(criteria); } }
Bem simples certo? Há um problema nessa classe de negócios de produtos. Não é possível testá-la, pois internamente, ela faz uma dependência explícita ao repositório de produtos do banco de dados SQL , como pode ver em seu construtor.
O uso da classe acima fica assim no controller do MVC:
public class Old1Controller : Controller { private readonly ProductService _productService; public Old1Controller() { _productService = new ProductService(); } // GET: Old1 public ActionResult Index() { var allProducts = _productService.GetAllProductsByStatus(); return View(); } }
Vamos melhorar um pouco essa classe de negócios, veja o código abaixo.
public class ProductService { private readonly SqlProductRepository _productRepository; public ProductService(SqlProductRepository productRepository) { _productRepository = productRepository; } public IList<string> GetAllProductsByStatus() { var criteria = "Status=1"; return _productRepository.GetAllProducts(criteria); } }
Deu uma melhorada certo? Agora nós temos uma dependência explícita no construtor da classe de negócios para o repositório de produtos do banco de dados SQL. Nós já conseguiríamos tentar efetuar testes unitários nessa classe…, só que não!
O problema é, nós ainda temos que instanciar uma classe de repositório de produtos do banco de dados SQL. E, se nós não tivermos banco de dados SQL? E, se nós quisermos apenas testar o funcionamento da regra de negócio? E, se amanhã, decidirmos trocar de banco de dados SQL para banco de dados MongoDB? Com certeza vai dar rosca!
Enfim, esse código poderia ser usado assim no controller do MVC:
public class Old2Controller : Controller { private readonly ProductService _productService; public Old2Controller() { var repository = new SqlProductRepository(); _productService = new ProductService(repository); } // GET: Old2 public ActionResult Index() { var allProducts = _productService.GetAllProductsByStatus(); return View(); } }
Repare que ainda precisamos instanciar duas classes para fazer a coisa toda funcionar. Aí não dá não é??!
Agora vamos mudar isso e deixar essas classes seguindo os princípios SOLID, já preparando o terreno para a Injeção de Dependência. Veja o código abaixo:
public class ProductService : IProductService { private readonly IProductRepository _productRepository; public ProductService(IProductRepository productRepository) { _productRepository = productRepository; } public IList<string> GetAllProductsByStatus() { var criteria = "Status=1"; return _productRepository.GetllAllProducts(criteria); } }
Agora a classe de negócios de produtos tem uma dependência em seu construtor a uma interface de repositório de produtos. Mas qual será a classe de repositório? SQL? MongoDB? A resposta é: não importa! “Eu, regra de negócios, não tenho que saber disso”.
Agora está ficando bom! Então, para gente testar basta criarmos qualquer classe que implemente a interface do repositório de produtos, passar no construtor da classe de negócios de produtos e correr para o abraço? Sim!!
O uso dessa classe no controller do MVC seria assim:
public class HomeController : Controller { private readonly IProductService _productService; public HomeController() { var repository = new SqlProductRepository(); _productService = new ProductService(repository); } public ActionResult Index() { var allProducts = _productService.GetAllProductsByStatus(); return View(); } }
Blarh! Pow! Arghhh! Ainda assim é preciso instanciar duas classes! E pior, ainda é necessário explicitar essas classes no construtor, por exemplo o repositório de produtos do banco de dados SQL.
Agora pare e analise com cuidado o código que vem a seguir. É código do controller do MVC alterado e preparado com todos os princípios SOLID e Injeção de Dependência:
public class HomeController : Controller { private readonly IProductService _productService; public HomeController(IProductService productService) { _productService = productService; } public ActionResult Index() { var allProducts = _productService.GetAllProductsByStatus(); return View(); } }
Basicamente o construtor do controller tem uma interface como parâmetro (dependência explícita) e guarda esse parâmetro da classe de negócios de produtos em uma variável local. O método action Index usa essa variável para acessar o método da classe de negócios que irá listar os produtos filtrados por status.
Qual é a classe de negócios que implementa IProductService? De onde vem os dados? Qual é o banco de dados? Nenhuma classe foi instanciada! Mágica? Quase… chamam isso vulgarmente de Inversion of Control.
Inversion of Control
Inversão de Controle quer dizer que a forma ou sequência de utilização dos métodos na programação não é totalmente explícita pelo programador, onde a responsabilidade da implementação fica a cargo de outros componentes.
Coloque um breakpoint aí no construtor do controller execute a aplicação em modo debug e repare que o construtor recebe uma instância de ProductService quentinha e prontinha para ser utilizada. É aí que entra a Injeção de Dependência.
Como configuro no MVC?
Para isso basta configurar no “startup” da sua aplicação algo assim: “Para a interface X, use a classe Y”. No exemplo acima, foi configurado: “Para interface IProductService, use a classe ProductService”. Da mesma forma que: “Para a interface IProductRepository, use a classe de SqlProductRepository”.
Com essa configuração, vulgarmente chamada classe de container, o MVC (nesse caso) irá fornecer uma instância de classe correspondente a interface sempre que encontrar uma dependência em algum construtor na sua aplicação. Vide configuração:
var container = new Container(); container.Register<IProductRepository, SqlProductRepository>(Lifestyle.Singleton); container.Register<IProductService, ProductService>(Lifestyle.Singleton); container.Verify(); DependencyResolver.SetResolver( new SimpleInjectorDependencyResolver(container));
Normalmente em uma aplicação MVC, essa configuração de container é feita no Global.asax, mais especificamente no método Application_Start.
Falando de Simple Injector, basta instanciar a sua classe de container e registrar as dependências interface/classe e finalmente escolher o tipo de instância para a classe (LifeStyle) que será fornecida.
Escolha Singleton para aquelas classes que não guardam estado e não possuem propriedades.
Singleton
É um design pattern que garante a existência de apenas uma instância de uma classe em todo ciclo de vida da aplicação.
Para Transient uma nova instância da classe será retornada toda vez que solicitada. Normalmente usamos Transient para Models e ViewModels.
Depois de registradas as dependências, forneça essa classe de container ao Dependency Resolver do MVC. É essa classe que o MVC irá pesquisar as dependências configuradas quando ele encontrar uma interface (dependência) ou classe em construtores na aplicação web.
Observação de Rafael Santos sobre o Simple Injector e Windows Service:
“Quando você constroi um serviço (Windows Service) de longa execução (seja disparado por timer ou while infinito) e usa o Simple Injector, é preciso definir um lifecycle por escopo de execução. Supondo, por exemplo, que dentro de um mesmo loop se deseje realizar mais de uma tarefa (ainda que sejam assincronas) é preciso q cada tarefa possua um escopo envelopando a execução dentro de um Using. Dessa forma vc chama a função BeginLifetimeScope() do container e no término da execução o Dispose elimina os objetos criados. Isso trás como benefício, por exemplo, utilizar instâncias do Unit Of Work em métodos assíncronos sem que haja conflito.”
E na Web API?
Falei muito de MVC, mas o Simple Injector também funciona em outras plataformas como Web Forms, Windows Forms, WCF e inclusive Web API.
Para funcionar com Web API você precisa fazer o seguinte:
1 – Instalar o pacote do NuGet SimpleInjector.Integration.WebApi;
2 – No Global.asax, o código com MVC + Web API ficaria como abaixo. Repare as linhas comentadas como “//web api”.
var container = new Container(); container.Register<IProductRepository, SqlProductRepository>(Lifestyle.Singleton); container.Register<IProductService, ProductService>(Lifestyle.Singleton); container.RegisterWebApiControllers(GlobalConfiguration.Configuration); //web api container.Verify(); DependencyResolver.SetResolver( new SimpleInjectorDependencyResolver(container)); GlobalConfiguration.Configuration.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container); //web api
Pronto! Injeção de Dependência com Simple Injector funcionando com Web API.
Essa é a redenção do Simple Injector, simples, gratuito, rápido e bem documentado.
Não esqueça de baixar meu eBook Programação no Mundo Real Design Patterns Volume 1 e conferir alguns outros padrões de desenvolvimento, inclusive o Lazy Loading e Service Locator, que tem tudo a ver com o Injeção de Dependência.
Bom, se depois de ler esse artigo você não usar Injeção de Dependência na sua aplicação ou até mesmo não usar o Simple Injector, por favor leia de novo, tire suas dúvidas e não desista.
Percebeu que o código está bem claro e pouca complexidade?
Dá uma olhada e veja aqui 3 Dicas Infalíveis para Reduzir Cyclomatic Complexity no C#.
Recomendações de outros Blogs:
– André Alves de Lima
– Elemar Jr
É isso. Espero que tenha ajudado.
Faça download completo do código fonte no github. |