Особенности использования 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-контейнер.