Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Скачивание завершенного проекта
В этом руководстве показано, как внедрить зависимости в контроллер веб-API ASP.NET.
Версии программного обеспечения, используемые в руководстве
- Веб-API 2
- Блок приложения Unity
- Entity Framework 6 (версия 5 также работает)
Что такое внедрение зависимостей?
Зависимость — это любой объект, который требуется другому объекту. Например, обычно определяет репозиторий, который обрабатывает доступ к данным. Рассмотрим пример. Сначала мы определим модель домена:
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. Здесь в дело вступает резольвер зависимостей Web 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. В меню "Сервис / Tools" в 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 имеют аналогичные эквиваленты.