Injeção de dependência no ASP.NET Web API 2

por Mike Wasson

Baixar projeto concluído

Este tutorial mostra como injetar dependências em seu controlador de ASP.NET Web API.

Versões de software usadas no tutorial

O que é injeção de dependência?

Uma dependência é qualquer objeto exigido por outro objeto. Por exemplo, é comum definir um repositório que lide com o acesso a dados. Vamos ilustrar com um exemplo. Primeiro, vamos definir um modelo de domínio:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Aqui está uma classe de repositório simples que armazena itens em um banco de dados, usando Entity Framework.

public class ProductsContext : DbContext
{
    public ProductsContext()
        : base("name=ProductsContext")
    {
    }
    public DbSet<Product> Products { get; set; }
}

public class ProductRepository : IDisposable
{
    private ProductsContext db = new ProductsContext();

    public IEnumerable<Product> GetAll()
    {
        return db.Products;
    }
    public Product GetByID(int id)
    {
        return db.Products.FirstOrDefault(p => p.Id == id);
    }
    public void Add(Product product)
    {
        db.Products.Add(product);
        db.SaveChanges();
    }

    protected void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (db != null)
            {
                db.Dispose();
                db = null;
            }
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Agora, vamos definir um controlador de API da Web que dá suporte a solicitações GET para Product entidades. (Estou deixando o POST e outros métodos para simplificar.) Esta é uma primeira tentativa:

public class ProductsController : ApiController
{
    // This line of code is a problem!
    ProductRepository _repository = new ProductRepository();

    public IEnumerable<Product> Get()
    {
        return _repository.GetAll();
    }

    public IHttpActionResult Get(int id)
    {
        var product = _repository.GetByID(id);
        if (product == null)
        {
            return NotFound();
        }
        return Ok(product);
    }
}

Observe que a classe Controller depende ProductRepository e estamos permitindo que o controlador crie a ProductRepository instância. No entanto, é uma boa ideia codificar a dependência dessa forma, por vários motivos.

  • Se você quiser substituir ProductRepository por uma implementação diferente, também precisará modificar a classe do controlador.
  • Se o ProductRepository tiver dependências, você deverá configurá-las dentro do controlador. Para um projeto grande com vários controladores, seu código de configuração se torna disperso em seu projeto.
  • É difícil fazer o teste de unidade, pois o controlador é embutido em código para consultar o banco de dados. Para um teste de unidade, você deve usar um repositório de simulação ou de stub, o que não é possível com o design atual.

Podemos resolver esses problemas injetando o repositório no controlador. Primeiro, refatore a ProductRepository classe em uma interface:

public interface IProductRepository
{
    IEnumerable<Product> GetAll();
    Product GetById(int id);
    void Add(Product product);
}

public class ProductRepository : IProductRepository
{
    // Implementation not shown.
}

Em seguida, forneça o IProductRepository como um parâmetro de construtor:

public class ProductsController : ApiController
{
    private IProductRepository _repository;

    public ProductsController(IProductRepository repository)  
    {
        _repository = repository;
    }

    // Other controller methods not shown.
}

Este exemplo usa injeção de Construtor. Você também pode usar a injeção de setter, em que você define a dependência por meio de um método ou propriedade setter.

Mas agora há um problema, porque seu aplicativo não cria o controlador diretamente. A API da Web cria o controlador quando ele roteia a solicitação e a API da Web não sabe nada sobre o IProductRepository . É aí que entra o resolvedor de dependências da API Web.

O resolvedor de dependência da API Web

A API Web define a interface IDependencyResolver para resolver dependências. Aqui está a definição da interface:

public interface IDependencyResolver : IDependencyScope, IDisposable
{
    IDependencyScope BeginScope();
}

public interface IDependencyScope : IDisposable
{
    object GetService(Type serviceType);
    IEnumerable<object> GetServices(Type serviceType);
}

A interface IDependencyScope tem dois métodos:

  • GetService cria uma instância de um tipo.
  • GetServices cria uma coleção de objetos de um tipo especificado.

O método IDependencyResolver herda IDependencyScope e adiciona o método BeginScope . Falarei sobre escopos mais adiante neste tutorial.

Quando a API da Web cria uma instância de controlador, ela primeiro chama IDependencyResolver. GetService, passando o tipo de controlador. Você pode usar esse gancho de extensibilidade para criar o controlador, resolvendo quaisquer dependências. Se GetService retornar NULL, a API da Web procurará um construtor sem parâmetros na classe do controlador.

Resolução de dependência com o contêiner do Unity

Embora você possa escrever uma implementação de IDependencyResolver completa do zero, a interface é realmente projetada para atuar como ponte entre a API da Web e os contêineres IOC existentes.

Um contêiner IoC é um componente de software que é responsável por gerenciar dependências. Você registra os tipos com o contêiner e, em seguida, usa o contêiner para criar objetos. O contêiner ilustra automaticamente as relações de dependência. Muitos contêineres IoC também permitem que você controle coisas como o tempo de vida e o escopo do objeto.

Note

"IoC" significa "inversão de controle", que é um padrão geral em que uma estrutura chama o código do aplicativo. Um contêiner IoC constrói seus objetos para você, que "inverte" o fluxo de controle usual.

Para este tutorial, usaremos o Unity de práticas de padrões da Microsoft & . (Outras bibliotecas populares incluem Castle Windsor, Spring.net, Autofac, Ninjecte StructureMap.) Você pode usar o Gerenciador de pacotes NuGet para instalar o Unity. No menu ferramentas no Visual Studio, selecione Gerenciador de pacotes NuGete, em seguida, selecione console do Gerenciador de pacotes. Na janela do console do Gerenciador de pacotes, digite o seguinte comando:

Install-Package Unity

Aqui está uma implementação de IDependencyResolver que encapsula um contêiner do Unity.

using Microsoft.Practices.Unity;
using System;
using System.Collections.Generic;
using System.Web.Http.Dependencies;

public class UnityResolver : IDependencyResolver
{
    protected IUnityContainer container;

    public UnityResolver(IUnityContainer container)
    {
        if (container == null)
        {
            throw new ArgumentNullException(nameof(container));
        }
        this.container = container;
    }

    public object GetService(Type serviceType)
    {
        try
        {
            return container.Resolve(serviceType);
        }
        catch (ResolutionFailedException exception)
        {
            throw new InvalidOperationException(
                $"Unable to resolve service for type {serviceType}.",
                exception)
        }
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        try
        {
            return container.ResolveAll(serviceType);
        }
        catch (ResolutionFailedException exception)
        {
            throw new InvalidOperationException(
                $"Unable to resolve service for type {serviceType}.",
                exception)
        }
    }

    public IDependencyScope BeginScope()
    {
        var child = container.CreateChildContainer();
        return new UnityResolver(child);
    }

    public void Dispose()
    {
        Dispose(true);
    }

    protected virtual void Dispose(bool disposing)
    {
        container.Dispose();
    }
}

Configurando o resolvedor de dependência

Defina o resolvedor de dependência na propriedade DependencyResolver do objeto HttpConfiguration global.

O código a seguir registra a IProductRepository interface com o Unity e, em seguida, cria um UnityResolver .

public static void Register(HttpConfiguration config)
{
    var container = new UnityContainer();
    container.RegisterType<IProductRepository, ProductRepository>(new HierarchicalLifetimeManager());
    config.DependencyResolver = new UnityResolver(container);

    // Other Web API configuration not shown.
}

Tempo de vida do controlador e do escopo de dependência

Os controladores são criados por solicitação. Para gerenciar tempos de vida de objeto, IDependencyResolver usa o conceito de um escopo.

O resolvedor de dependência anexado ao objeto HttpConfiguration tem escopo global. Quando a API da Web cria um controlador, ele chama BeginScope. Esse método retorna um IDependencyScope que representa um escopo filho.

A API Web então chama GetService no escopo filho para criar o controlador. Quando a solicitação for concluída, as chamadas da API Web serão descartadas no escopo filho. Use o método Dispose para descartar as dependências do controlador.

A maneira como você implementa o BeginScope depende do contêiner IoC. Para Unity, o escopo corresponde a um contêiner filho:

public IDependencyScope BeginScope()
{
    var child = container.CreateChildContainer();
    return new UnityResolver(child);
}

A maioria dos contêineres IoC tem equivalentes semelhantes.