Внедрение зависимостей в веб-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);
    }
}

Обратите внимание, что класс Controller зависит от 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);
}

Интерфейс идепенденцископе имеет два метода:

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

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

Когда веб-API создает экземпляр контроллера, он сначала вызывает IDependencyResolver.-Service, передавая ему тип контроллера. С помощью этого обработчика расширяемости можно создать контроллер, разрешающий все зависимости. Если функция " услуга " возвращает значение null, веб-API ищет конструктор без параметров в классе Controller.

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

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

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

Note

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

В рамках этого руководства мы будем использовать Unity из & практических рекомендаций Майкрософт. (Другие популярные библиотеки включают Castle Windsor, Spring.NET, Autofac, нинжекти 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();
    }
}

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

Установите сопоставитель зависимостей в свойстве депенденциресолвер глобального объекта 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 создает контроллер, он вызывает бегинскопе. Этот метод возвращает объект идепенденцископе , представляющий дочернюю область.

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

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

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

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