Поделиться через


Внедрение зависимостей в веб-API ASP.NET 2

Скачивание завершенного проекта

В этом руководстве показано, как внедрить зависимости в контроллер веб-API ASP.NET.

Версии программного обеспечения, используемые в руководстве

Что такое внедрение зависимостей?

Зависимость — это любой объект, который требуется другому объекту. Например, обычно определяет репозиторий, который обрабатывает доступ к данным. Рассмотрим пример. Сначала мы определим модель домена:

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

Ниже приведен простой класс репозитория, который хранит элементы в базе данных с помощью 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);
    }
}

Теперь определим контроллер веб-API, поддерживающий запросы GET для Product сущностей. (Я покидаю POST и другие методы для простоты.) Ниже приведена первая попытка:

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);
    }
}

Обратите внимание, что класс контроллера зависит от ProductRepositoryэтого, и мы разрешаем контроллеру создавать ProductRepository экземпляр. Тем не менее, это плохая идея жестко кодировать зависимость таким образом по нескольким причинам.

  • Если вы хотите заменить ProductRepository другой реализацией, необходимо также изменить класс контроллера.
  • ProductRepository Если у него есть зависимости, необходимо настроить их внутри контроллера. Для большого проекта с несколькими контроллерами код конфигурации становится разбросанным по проекту.
  • Модульное тестирование трудно выполнить, так как контроллер жестко закодирован для запроса к базе данных. Для модульного теста следует использовать репозиторий макета или заглушки, который невозможно использовать с текущим проектом.

Мы можем устранить эти проблемы, введя репозиторий в контроллер. Сначала рефакторинг ProductRepository класса в интерфейс:

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

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

Затем укажите параметр IProductRepository конструктора:

public class ProductsController : ApiController
{
    private IProductRepository _repository;

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

    // Other controller methods not shown.
}

В этом примере используется внедрение конструктора. Вы также можете использовать внедрение метода задания, где можно задать зависимость с помощью метода задания или свойства.

Но теперь возникает проблема, так как приложение не создает контроллер напрямую. Веб-API создает контроллер при маршрутизации запроса, а веб-API ничего не знает.IProductRepository Это место, где входит сопоставитель зависимостей веб-API.

Сопоставитель зависимостей веб-API

Веб-API определяет интерфейс IDependencyResolver для разрешения зависимостей. Ниже приведено определение интерфейса:

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

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

Интерфейс IDependencyScope имеет два метода:

  • GetService создает один экземпляр типа.
  • GetServices создает коллекцию объектов указанного типа.

Метод IDependencyResolver наследует IDependencyScope и добавляет метод BeginScope. Далее в этом руководстве я поговорим о областях.

При создании экземпляра контроллера веб-API сначала вызывает IDependencyResolver.GetService, передав тип контроллера. Этот перехватчик расширяемости можно использовать для создания контроллера, разрешающего все зависимости. Если GetService возвращает значение NULL, веб-API ищет конструктор без параметров в классе контроллера.

Разрешение зависимостей с контейнером Unity

Хотя вы можете написать полную реализацию IDependencyResolver с нуля, интерфейс действительно предназначен для работы в качестве моста между веб-API и существующими контейнерами IoC.

Контейнер IoC — это программный компонент, отвечающий за управление зависимостями. Вы регистрируете типы в контейнере, а затем используете контейнер для создания объектов. Контейнер автоматически определяет отношения зависимостей. Многие контейнеры IoC также позволяют управлять такими объектами, как время существования объекта и область.

Примечание.

"IoC" обозначает "инверсию элемента управления", который является общим шаблоном, в котором платформа вызывает код приложения. Контейнер IoC создает объекты для вас, что "инвертирует" обычный поток управления.

В этом руководстве мы будем использовать Unity из Microsoft Patterns &Practices. (Другие популярные библиотеки включают в себя Castle Windsor, Spring.Net, Autofac, Ninject, Simple Injector и StructureMap.) Для установки Unity можно использовать диспетчер пакетов NuGet. В меню "Сервис" в Visual Studio выберите NuGet диспетчер пакетов, а затем выберите диспетчер пакетов консоль. В окне консоли диспетчер пакетов введите следующую команду:

Install-Package Unity

Ниже приведена реализация IDependencyResolver , которая упаковывает контейнер 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();
    }
}

Настройка сопоставителя зависимостей

Задайте сопоставитель зависимостей для свойства DependencyResolver глобального объекта HttpConfiguration .

Следующий код регистрирует IProductRepository интерфейс в Unity, а затем создает 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.
}

Область зависимостей и время существования контроллера

Контроллеры создаются для каждого запроса. Для управления временем существования объектов IDependencyResolver использует концепцию области.

Сопоставитель зависимостей, подключенный к объекту HttpConfiguration , имеет глобальную область. При создании веб-API контроллера вызывает BeginScope. Этот метод возвращает IDependencyScope , представляющий дочернюю область.

Затем веб-API вызывает GetService в дочерней области, чтобы создать контроллер. По завершении запроса веб-API вызывает Dispose в дочерней области. Используйте метод Dispose для удаления зависимостей контроллера.

Реализация BeginScope зависит от контейнера IoC. Для Unity область соответствует дочернему контейнеру:

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

Большинство контейнеров IoC имеют аналогичные эквиваленты.