В этой статье я опишу, как использовать библиотеку
Microsoft Unity в приложениях
ASP.NET MVC. А также, какие преимущества от этого можно получить, в частности для написания unit-тестов.
Как известно, Unity представляет собой реализацию паттерна
Dependency Injection (DI). Кратко опишу, что собой представляет DI(“ внедрение зависимостей”). Представим, что некий объект использует в своей работе какой-то сервис. Ссылка на сервис хранится в приватном поле объекта. Для того, что бы объект не был жестко связан с сервисом (coupling), мы можем применить «внедрение зависимостей». DI представляет механизм, позволяющий в момент создания объекта извне инициализировать ссылку на сервис. Как правило, это делается через публичное свойство или параметризованный конструктор.
И так, для начала рассмотрим, как выглядит простое MVC приложение, без использования Unity. Предположим, что у нас есть контроллер, передающий во view список пользователей. Контроллер обращается к статическому методу класса DataHelper, для получения списка пользователей. DataHelper, в свою очередь, обращается к базе данных.
<code>public class DataHelper
{
public static List<User> GetAllUsers()
{
//connect to DB and fetch list of users
}
}
public class UserController
{
public ActionResult ShowAll()
{
return View(DataHelper.GetAllUsers().ToList());
}
}
* This source code was highlighted with Source Code Highlighter.
В данной реализации, мы видим сильную “связанность” классов UserController и DataHelper.
И как следствие этого, есть проблема с тестированием метода ShowAll. Мы не можем протестировать этот метод, без вызова метода GetAllUsers и соответственно обращения к базе данных.
Теперь, рассмотрим вариант с использованием Unity. Первое, что мы сделаем – избавимся от статического метода в DataHelper и выделим интерфейс. А заодно переименуем класс DataHelper в DataService.
public interface IDataService
{
List<User> GetAllUsers();
}
public class DataService : IDataService
{
public List<User> GetAllUsers()
{
//connect to DB and fetch list of users
}
}
* This source code was highlighted with Source Code Highlighter.
Внесем изменения в реализацию контроллера: добавим публичное свойство DataService, в котором будет храниться ссылка на экземпляр IDataService, и пометим это свойство атрибутом Dependency.
[Dependency]
public IDataService DataService { get; set; }
public class UserController
{
public ActionResult ShowAll()
{
return View(DataService.GetAllUsers().ToList());
}
}
* This source code was highlighted with Source Code Highlighter.
Теперь осталось настроить собственно сам Unity. Мы сделаем это в модуле Global.ascx.
protected void Application_Start()
{
…
InitContainer();
}
private static UnityContainer _container;
private static void InitContainer()
{
if (_container == null)
_container = new UnityContainer();
IControllerFactory controllerFactory = new UnityControllerFactory(_container);
ControllerBuilder.Current.SetControllerFactory(controllerFactory);
_container.RegisterType<IDataService, DataService>(new ContainerControlledLifetimeManager());
}
* This source code was highlighted with Source Code Highlighter.
Обратите внимание, что в методе InitContainer мы регистрируем тип и его реализацию, который Unity будет внедрять, основываясь на атрибуте Dependency. При этом в качестве параметра метода RegisterType передается экземпляр ContainerControlledLifetimeManager. Это значит, что каждый раз, при вызове метода контейнера Resolve, будет возвращаться один и тот же экземпляр DataService.
И последнее, что нам осталось сделать, это реализовать свою собственную фабрику контроллеров UnityControllerFactory. Эта фабрика будет возвращать экземпляр котроллера UserController уже проинициализированный экземпляром DataService.
public class UnityControllerFactory : DefaultControllerFactory
{
IUnityContainer _container;
public UnityControllerFactory(IUnityContainer container)
{
_container = container;
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
throw new ArgumentNullException("controllerType");
if (!typeof(IController).IsAssignableFrom(controllerType))
throw new ArgumentException(string.Format(
"Type requested is not a controller: {0}", controllerType.Name),
"controllerType");
return _container.Resolve(controllerType) as IController;
}
}
* This source code was highlighted with Source Code Highlighter.
На этом этапе мы реализовали использование Unity в приложении MVC. Теперь можем написать unit test метода ShowAll класса UserController. Сначала создаем mock класс:
public class TestDataService : IDataService
{
public List<User> GetAllUsers()
{
return new List<User>
{
new User
{
FirstName = "firstname",
LastName = "lastname",
Age = "34"
},
new User
{
FirstName = "firstname1",
LastName = "lastname1",
Age = "31"
}
}
}
}
* This source code was highlighted with Source Code Highlighter.
И собственно сам тест:
[TestMethod]
public void ShowAllTest
{
var container = new UnityContainer();
container.RegistryType<IDataService, TestDataService>();
var userController = container.Resolve<UserController>();
ViewResult result = controller.ShowAll() as ViewResult;
var list = result.ViewData.Model as IEnumerable<User>
Assert.AreEqual(2, list.Count());
}
* This source code was highlighted with Source Code Highlighter.
Теперь проверяется код только метода ShowAll, и он не зависит от класса DataService и тем более от базы данных.
В заключении хочу отметить два момента. В production мы можем настраивать контейнер Unity декларативно, с использованием конфигурационного файла. Т.о., для перехода на тестовый environment, нам не надо изменять код, достаточно поменять конфигурационный файл. И второе, «внедрение зависимостей» мы можем производить не только через публичное свойство, но и через параметризованный конструктор. И в том, и в другом случае такие члены должны помечаться атрибутом Dependency.