+7 (495) 229-0436   shopadmin@itshop.ru 119334, г. Москва, ул. Бардина, д. 4, корп. 3
 
 
Вход
 
 
Каталог
 
 
Подписка на новости
Новости ITShop
Windows 7 и Office: Новости и советы
Обучение и сертификация Microsoft
Вопросы и ответы по MSSQLServer
Delphi - проблемы и решения
Adobe Photoshop: алхимия дизайна
 
Ваш отзыв
Оцените качество магазина ITShop.ru на Яндекс.Маркете. Если вам нравится наш магазин - скажите об этом Google!
 
 
Способы оплаты
 
Курс расчета
 
 1 у.е. = 78.62 руб.
 
 Цены показывать:
 
 
 
 
  
Новости, статьи, акции
 

ASP.NET MVC Урок 2. Dependency Injection

10.04.2013 11:18
habrahabr

Цель урока: Изучение DI (Dependency Injection). Пример на Ninject и Unity (Autofac, Winsor).

Во многих случаях, один и тот же экземпляр класса используется в вашем приложении в разных модулях. Простым способом реализации является применение шаблона Одиночка (Singleton).

Но рассмотрим эту ситуацию с другой стороны. Так как данный объект создается при первом обращении к нему, мы не можем контролировать его время жизни. При модульном тестировании (unit-test) нет необходимости использовать этот объект (или это может быть невозможно). Чтобы избежать этого, мы не напрямую вызываем объект, а через интерфейс. И реальный экземпляр класса, и экземпляр-заглушка для тестирования будут реализовывать этот интерфейс. А логику создания мы поручаем DI-контейнеру.


Например, до использования сервиса. Опишем пару классов, интерфейс IWeapon с методом Kill, два класса реализации Bazuka и Sword, и класс Warrior, который пользуется оружием:
public interface IWeapon { void Kill(); } public class Bazuka : IWeapon { public void Kill() { Console.WriteLine("BIG BADABUM!"); } } public class Sword : IWeapon { public void Kill() { Console.WriteLine("Chuk-chuck"); } } public class Warrior { readonly IWeapon Weapon; public Warrior(IWeapon weapon) { this.Weapon = weapon; } public void Kill() { Weapon.Kill(); } }
Используем это:
class Program { static void Main(string[] args) { Warrior warrior = new Warrior(new Bazuka()); warrior.Kill(); Console.ReadLine(); } }
Читаем между строк. Создаем воина и даем ему базуку, он идет и убивает. В консоли получаем:
BIG BADABUM!
Заметим, что у нас нет проверки на null в строке
Weapon.Kill();

Что здесь некоректно? Воин не знает, есть ли у него оружие, и выдачей оружия занимается не отдельный модуль, а главная программа.
Суть DI - поручить выдачу оружия другому модулю.

Подключаем Ninject:

Install-Package Ninject

Создаем модуль, который занимается выдачей оружия:

public class WeaponNinjectModule : NinjectModule { public override void Load() { this.Bind<IWeapon>().To<Sword>(); } }

Что буквально значит: "если попросят оружие - то выдайте мечи".
Создаем "сервис-локатор" и пользуемся оружием:
class Program { public static IKernel AppKernel; static void Main(string[] args) { AppKernel = new StandardKernel(new WeaponNinjectModule()); var warrior = AppKernel.Get<Warrior>(); warrior.Kill(); Console.ReadLine(); } }
Как видно, объект warrior мы создаем не с помощью конструкции new, а через AppKernel.Get<>(). При создании AppKernel, мы передаем в качестве конструктора модуль, отвечающий за выдачу оружия (в данном случае это меч). Любой объект, который мы пытаемся получить через AppKernel.Get, будет (по мере возможности) проинициализирован, если существуют модули, которые знают, как это делать.

Другой момент применения, когда объект Warrior не берет с собой оружие каждый раз, а при не обнаружении оного обращается к сервису локатору и получает его:

public class OtherWarrior { private IWeapon _weapon; public IWeapon Weapon { get { if (_weapon == null) { _weapon = Program.AppKernel.Get<IWeapon>(); } return _weapon; } } public void Kill() { Weapon.Kill(); } }
Исполняем:
var otherWarrior = new OtherWarrior(); otherWarrior.Kill();
Наш воин получает оружие по прямым поставкам - супер!

В Ninject есть еще одна очень хорошая деталь. Если свойство (public property) помечено [Inject], то при создании класса через AppKernel.Get<>() - поле инициализуется сервисом-локатором:
public class AnotherWarrior { [Inject] public IWeapon Weapon { get; set; } public void Kill() { Weapon.Kill(); } } var anotherWarrior = AppKernel.Get<AnotherWarrior>(); anotherWarrior.Kill();

Unity

Абсолютно всё то же:

  • Установка
    Install-Package Unity
  • Инициализация сервиса локатора (Container)
    Container = new UnityContainer();
  • Регистрация типа
    Container.RegisterType(typeof(IWeapon), typeof(Bazuka));
  • Получение объекта и использование:
    var warrior = Container.Resolve<Warrior>(); warrior.Kill();
  • Кроме того, у Unity есть класс-одиночка (Singleton) ServiceLocator, который регистрирует контейнер и позволяет получить доступ к сервисам из любого места.
    var serviceProvider = new UnityServiceLocator(Container); ServiceLocator.SetLocatorProvider(() => serviceProvider);
  • Хитрый OtherWarrior теперь так получает оружие:
    public class OtherWarrior { private IWeapon _weapon; public IWeapon Weapon { get { if (_weapon == null) { _weapon = ServiceLocator.Current.GetInstance<IWeapon>(); } return _weapon; } } public void Kill() { Weapon.Kill(); } }

Autofac

Так же, собственно, всё и происходит:

  • Установка
    Install-Package Autofac
  • Инициализация строителя сервиса-локатора (ContainerBuilder) - нет-нет, это еще не сам контейнер, это - как модули
    var builder = new ContainerBuilder();

  • Регистрация типов. Надо зарегистрировать все необходимые классы, потому что создание экземпляров незарегистрированных классов тут не реализован.
    builder.RegisterType<Bazuka>(); builder.RegisterType<Warrior>(); builder.Register<IWeapon>(x => x.Resolve<Bazuka>());
  • Создание сервиса локатора (Container)
    var container = builder.Build();
  • Получение объекта и использование:
    var warrior = container.Resolve<Warrior>(); warrior.Kill();

Castle Windsor

  • Установка
    Install-Package Castle.Windsor
  • Инициализация сервиса-локатора
    var container = new WindsorContainer();
  • Регистрация типов. Аналогична как и в Autofac.
    container.Register(Component.For<IWeapon>().ImplementedBy<Bazuka>(), Component.For<Warrior>().ImplementedBy<Warrior>());
  • Получение объекта и использование:
    var warrior = container.Resolve<Warrior>(); warrior.Kill();

Маленький подитог

На самом деле, реализации Dependency Injection не сильно, но всё же отличаются. Некоторые поддерживают инициализацию в Web.config (App.config) файлах. Некоторые, задают правила для инициализации, как мы сейчас посмотрим на расширении Ninject для asp.net mvc - это касается инициализации сервиса-локатора как генератора общих объектов, так и отдельно для каждого потока или web-запросе.

Объекты областей (Ninject)

В Ninject можно задать несколько способов инициализации получения объекта из класса. Если мы работаем в различных контекстах (например, в разных потоках (Thread)), то объекты должны быть использованы разные. Тем самым, поддерживается масштабируемость и гибкость приложения.

Область  Метод связывания  Объяснение 
Временный .InTransientScope()  Объект класса будет создаваться по каждому требованию (метод по умолчанию).
Одиночка .InSingletonScope()  Объект класса будет создан один раз и будет использоваться повторно.
Поток .InThreadScope()  Один объект на поток.
Запрос .InRequestScope()  Один объект будет на каждый web-запрос

Lifetime Manager в Unity

В Unity для задачи правил инициализации используется реализация абстрактного класса LifetimeManager.
Происходит это так:
_container.RegisterType<DbContext, SavecashTravelContext>(new PerRequestLifetimeManager());
Где PerRequestLifetimeManager - это реализация LifetimeManager:
public class PerRequestLifetimeManager : LifetimeManager { /// <summary> /// Key to store data /// </summary> private readonly string _key = String.Format("SingletonPerRequest{0}", Guid.NewGuid()); /// <summary> /// Retrieve a value from the backing store associated with this Lifetime policy. /// </summary> /// <returns> /// the object desired, or null if no such object is currently stored. /// </returns> public override object GetValue() { if (HttpContext.Current != null && HttpContext.Current.Items.Contains(_key)) return HttpContext.Current.Items[_key]; return null; } /// <summary> /// Stores the given value into backing store for retrieval later. /// </summary> /// <param name="newValue">The object being stored.</param> public override void SetValue(object newValue) { if (HttpContext.Current != null) HttpContext.Current.Items[_key] = newValue; } /// <summary> /// Remove the given object from backing store. /// </summary> public override void RemoveValue() { if (HttpContext.Current != null && HttpContext.Current.Items.Contains(_key)) HttpContext.Current.Items.Remove(_key); } }
Суть. Все объекты хранятся в HttpContext.Current.Items[_key] и выдаются только, если уже находятся в том же контексте (HttpContext.Current). В ином случае, создается новый объект. Если текущий контекст (HttpContext.Current) в области кода не существует (используем такой LifetimeManager в консольном приложении или в отдельном потоке) - то данный контейнер не будет работать.

Использование Ninject в asp.net mvc

Устанавливаем Ninject в среду asp.net mvc. Отдельно создаем свой проект LessonProject, создадим там HomeController с методом и view Index. (/Contollers/HomeController.cs):
public class HomeController : Controller { public ActionResult Index() { return View(); } }
И (/Views/Home/Index.cshtml):

@{ ViewBag.Title = "LessonProject"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>LessonProject</h2>

Запускаем - работает.

Примечание: В дальнейшем мы будем переносить этот проект в последующие уроки.

Теперь установим модуль Ninject и Ninject.MVC3 для этого проекта.
Install-Package Ninject.MVC3
Добавляем класс в папку App_Start (/App_Start/NinjectWebCommon.cs):
[assembly: WebActivator.PreApplicationStartMethod(typeof(LessonProject.App_Start.NinjectWebCommon), "Start")] [assembly: WebActivator.ApplicationShutdownMethodAttribute(typeof(LessonProject.App_Start.NinjectWebCommon), "Stop")] namespace LessonProject.App_Start { using System; using System.Web; using Microsoft.Web.Infrastructure.DynamicModuleHelper; using Ninject; using Ninject.Web.Common; public static class NinjectWebCommon { private static readonly Bootstrapper bootstrapper = new Bootstrapper(); /// <summary> /// Starts the application /// </summary> public static void Start() { DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule)); DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule)); bootstrapper.Initialize(CreateKernel); } /// <summary> /// Stops the application. /// </summary> public static void Stop() { bootstrapper.ShutDown(); } /// <summary> /// Creates the kernel that will manage your application. /// </summary> /// <returns>The created kernel.</returns> private static IKernel CreateKernel() { var kernel = new StandardKernel(); kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel); kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>(); RegisterServices(kernel); return kernel; } /// <summary> /// Load your modules or register your services here! /// </summary> /// <param name="kernel">The kernel.</param> private static void RegisterServices(IKernel kernel) { } } }

В RegisterServices мы добавляем инициализацию своих сервисов. Для начала добавим шутливый IWeapon, а в дальнейшем еще будем возвращаться к этому методу для регистрации других сервисов:
public interface IWeapon { string Kill(); } … public class Bazuka : IWeapon { public string Kill() { return "BIG BADABUM!"; } } … private static void RegisterServices(IKernel kernel) { kernel.Bind<IWeapon>().To<Bazuka>(); }

В контроллере используем атрибут [Inject]:
public class HomeController : Controller { [Inject] public IWeapon weapon { get; set; } public ActionResult Index() { return View(weapon); } }

Изменяем View:
@model LessonProject.Models.IWeapon @{ ViewBag.Title = "LessonProject"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>LessonProject</h2> <p> @Model.Kill() </p>
На выходе получаем:

Ninject использует WebActivator:

  • регистрирует свои модули OnePerRequestHttpModule и NinjectHttpModule
  • создает StandartKernel
  • инициализирует наши сервисы.

DependencyResolver

В asp.net mvc3 появился класс DependencyResolver. Этот класс обеспечивает получение экземпляра сервиса. Наши зарегистрированные сервисы (и даже используемый DI-контейнер) мы также можем получить посредством этого класса.
public class HomeController : Controller { private IWeapon weapon { get; set; } public HomeController() { weapon = DependencyResolver.Current.GetService<IWeapon>(); } public ActionResult Index() { return View(weapon); } }

Итог

Использование DI-контейнеров в современных приложениях необходимо, чтобы избавиться от сильной связности кода, и для легкого доступа из любой его части к сервисам. Также, это необходимо для написания Unit-тестов.

Ссылки по теме

  
Помощь
Задать вопрос
 программы
 обучение
 экзамены
 компьютеры
Бесплатный звонок
ICQ-консультанты
Skype-консультанты

Общая справка
Как оформить заказ
Тарифы доставки
Способы оплаты
Прайс-лист
Карта сайта
 
Бестселлеры
Курсы обучения "Atlassian JIRA - система управления проектами и задачами на предприятии"
Microsoft Windows 10 Профессиональная 32-bit/64-bit. Все языки. Электронный ключ
Microsoft Office для Дома и Учебы 2019. Все языки. Электронный ключ
Курс "Oracle. Программирование на SQL и PL/SQL"
Курс "Основы TOGAF® 9"
Microsoft Office 365 Персональный 32-bit/x64. 1 ПК/MAC + 1 Планшет + 1 Телефон. Все языки. Подписка на 1 год. Электронный ключ
Курс "Нотация BPMN 2.0. Ее использование для моделирования бизнес-процессов и их регламентации"
 

О нас
Интернет-магазин ITShop.ru предлагает широкий спектр услуг информационных технологий и ПО.

На протяжении многих лет интернет-магазин предлагает товары и услуги, ориентированные на бизнес-пользователей и специалистов по информационным технологиям.

Хорошие отзывы постоянных клиентов и высокий уровень специалистов позволяет получить наивысший результат при совместной работе.

В нашем магазине вы можете приобрести лицензионное ПО выбрав необходимое из широкого спектра и ассортимента по самым доступным ценам. Наши менеджеры любезно помогут определиться с выбором ПО, которое необходимо именно вам. Также мы проводим учебные курсы. Мы приглашаем к сотрудничеству учебные центры, организаторов семинаров и бизнес-тренингов, преподавателей. Сфера сотрудничества - продвижение бизнес-тренингов и курсов обучения по информационным технологиям.



 

О нас

 
Главная
Каталог
Новинки
Акции
Вакансии
 

Помощь

 
Общая справка
Как оформить заказ
Тарифы доставки
Способы оплаты
Прайс-лист
Карта сайта
 

Способы оплаты

 

Проекты Interface Ltd.

 
Interface.ru   ITShop.ru   Interface.ru/training   Olap.ru   ITnews.ru  
 

119334, г. Москва, ул. Бардина, д. 4, корп. 3
+7 (495) 229-0436   shopadmin@itshop.ru
Проверить аттестат
© ООО "Interface Ltd."
Продаем программное обеспечение с 1990 года