Partilhar via


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

Download do projeto concluído

Este tutorial mostra como injetar dependências no controlador de API Web do ASP.NET.

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 lida com o acesso a dados. Vamos ilustrar com um exemplo. Primeiro, definiremos 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 o 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 Web que dê suporte a solicitações GET para Product entidades. (Estou deixando de fora o POST e outros métodos para simplificar.) Aqui está 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 do controlador depende de ProductRepository, e estamos permitindo que o controlador crie a ProductRepository instância. No entanto, é uma má ideia codificar a dependência dessa maneira, 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 fica espalhado pelo projeto.
  • É difícil testar a unidade, porque o controlador é codificado para consultar o banco de dados. Para um teste de unidade, você deve usar um repositório fictício ou 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 Web cria o controlador quando roteia a solicitação, e a API Web não sabe nada sobre IProductRepositoryo . É aqui que entra o resolvedor de dependência 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 Web cria uma instância do controlador, ela primeiro chama IDependencyResolver.GetService, passando o tipo de controlador. Você pode usar esse gancho de extensibilidade para criar o controlador, resolvendo todas as dependências. Se GetService retornar nulo, a API 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 IDependencyResolver completa do zero, a interface foi realmente projetada para atuar como ponte entre a API Web e os contêineres de IoC existentes.

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

Observação

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

Para este tutorial, usaremos o Unity de Padrões e Práticas da Microsoft. (Outras bibliotecas populares incluem Castelo Windsor, Spring.Net, Autofac, Ninject, Injetor Simples e StructureMap.) Você pode usar o Gerenciador de Pacotes NuGet para instalar o Unity. No menu Ferramentas no Visual Studio, selecione Gerenciador de Pacotes NuGet e, em seguida, selecione Console do Gerenciador de Pacotes. Na janela 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 interface com o IProductRepository Unity e cria um UnityResolverarquivo .

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.
}

Escopo de dependência e tempo de vida do controlador

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

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

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

A forma como você implementa o BeginScope depende do contêiner de IoC. Para o 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 de IoC tem equivalentes semelhantes.