Особенности использования Unity Application Block для внедрения зависимостей в приложении ASP.NET MVC 5
Как известно в ASP.NET MVC 3 и выше есть два основных варианта реализации механизма внедрения зависимостей. Первый – использовать собственную фабрику контроллеров, второй – использовать так называемый Dependency resolver. Последний есть не что иное, как обычный Service Locator (Марк Симан в своей книге "Dependency Injection in .NET" называет его антипаттерном) для предоставления нужного DI-контейнера. Что же, поробуем использовать Uity в приложении MVC 5. Для этого создаём простое приложение
без использования лишних библиотек, не нужных нам в данный момент.
Устанавливаем Unity Application Block последней версии через менеджер NuGet.
Напишем самую простую реализацию Dependency resolver, как обычно пишется в книгах.
namespace UnityDIExample
{
public class UnityDependencyResolver : IDependencyResolver
{
private readonly IUnityContainer unityContainer;
public UnityDependencyResolver()
{
unityContainer = new UnityContainer();
unityContainer.RegisterType<ITestService, TestService>();
}
public object GetService(Type serviceType)
{
return unityContainer.IsRegistered(serviceType) ?
unityContainer.Resolve(serviceType) : null;
}
public IEnumerable<object> GetServices(Type serviceType)
{
throw new NotImplementedException();
}
}
}
Добавим зависимость ITestService
namespace UnityDIExample.Controllers
{
public class HomeController : Controller
{
private readonly ITestService testService;
public HomeController(ITestService testService)
{
this.testService = testService;
}
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
}
в конроллер и запустим приложение, предварительно задав Dependency resolver в Global.asax.
namespace UnityDIExample
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
DependencyResolver.SetResolver(new UnityDependencyResolver());
}
}
}
Видим, что приложение выбрасывает исключение: "No parameterless constructor defined for this object".
Дело в том, что стандартный активатор контроллера не может создать экземпляр контроллера без параметров. Чтобы полностью настроить и установить Unity нужен ещё один пакет NuGet, так называемый загрузчик (bootstraper). Установим его используя менеджер пакетов NuGet.
Как видно из нижнего рисунка в проект добавилось два файла: UnityConfig.cs и UnityMvcActivator.cs.
Первый содержит класс реализующий шаблон – "Одиночка (Singleton)", для конфигурации контейнера. Именно сюда нужно добавить метод регистрации зависимости ITestService, а именно – RegisterType<ITestService, TestService>() . Код показан ниже (оригинальные комментарии удалены в целях экономии места в листинге, их можно будет увидеть в проекте, когда создаются данные файлы).
namespace UnityDIExample.App_Start
{
public class UnityConfig
{
#region Unity Container
private static Lazy<IUnityContainer> container =
new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
public static IUnityContainer GetConfiguredContainer()
{
return container.Value;
}
#endregion
public static void RegisterTypes(IUnityContainer container)
{
// TODO: Register your types here
container.RegisterType<ITestService, TestService>();
}
}
}
Втрой файл – UnityMvcActivator.cs содержит методы Start и Shutdown, которые запускаются на старте приложения уже не через Global.asax, а при помощи атрибутов из сборки WebActivatorEx. Некоторые оригинальные коментарии в коде ниже, также были удалены.
using System.Linq;
using System.Web.Mvc;
using Microsoft.Practices.Unity.Mvc;
[assembly: WebActivatorEx.PreApplicationStartMethod
(typeof(UnityDIExample.App_Start.UnityWebActivator), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethod
(typeof(UnityDIExample.App_Start.UnityWebActivator), "Shutdown")]
namespace UnityDIExample.App_Start
{
public static class UnityWebActivator
{
public static void Start()
{
var container = UnityConfig.GetConfiguredContainer();
FilterProviders.Providers
.Remove(FilterProviders.
Providers.
OfType<FilterAttributeFilterProvider>().First());
FilterProviders.Providers
.Add(new UnityFilterAttributeFilterProvider(container));
DependencyResolver.
SetResolver(new UnityDependencyResolver(container));
// TODO: Uncomment if you want to use PerRequestLifetimeManager
// Microsoft.Web.Infrastructure.DynamicModuleHelper.
// DynamicModuleUtility.RegisterModule
// (typeof(UnityPerRequestHttpModule));
}
public static void Shutdown()
{
var container = UnityConfig.GetConfiguredContainer();
container.Dispose();
}
}
}
Можно увидеть, что и тут добавляется UnityDependencyResolver, но он уже не наш, а поставляется вместе с сборкой загрузчика (Unity bootstrapper for ASP.NET MVC).
Код UnityDependencyResolver можно увидеть ниже. Если покапаться в коде сборки, то можно найти очень много интересного, например HTTP-модуль UnityPerRequestHttpModule. Это уже оставляю вам, дорогой читатель.
public class UnityDependencyResolver : IDependencyResolver
{
private readonly IUnityContainer container;
public UnityDependencyResolver(IUnityContainer container)
{
this.container = container;
}
public object GetService(Type serviceType)
{
if (typeof(IController).IsAssignableFrom(serviceType))
{
return UnityContainerExtensions
.Resolve(this.container,
serviceType, new ResolverOverride[0]);
}
try
{
return UnityContainerExtensions
.Resolve(this.container,
serviceType, new ResolverOverride[0]);
}
catch (ResolutionFailedException)
{
return null;
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
return this.container.
ResolveAll(serviceType, new ResolverOverride[0]);
}
}
Теперь если запустить приложение, то всё будет работать.
И если вы решите использовать Unity в своём приложении (рекомендую почитать данный раздел в MSDN, доступна и PDF версия руководства), надеюсь, что данная статья поможет вам быстро сконфигурировать DI-контейнер.
Comments
Anonymous
January 03, 2015
Отличная статья, всё кратко и понятно! Правда, в предложении, цитата: "Теперь если запустить приложение, то всё будет работь.", слово "работь" лучше исправить на "работать".Anonymous
January 04, 2015
Жук спасибо, исправил!