Главная страница   /   6.2. Использование Ninject (ASP.NET MVC 4

ASP.NET MVC 4

ASP.NET MVC 4

Адам Фриман

6.2. Использование Ninject

Мы ввели понятие DI в главе 3. Напомним, что идея состоит в том, чтобы разделять компоненты в наших MVC приложениях, и мы делаем это, сочетая интерфейсы и DI. В следующих разделах мы разъясним проблему, которую мы сознательно создали в приложении из примера, и покажем, как использовать Ninject, наш любимый DI пакет, который может быть использован для ее решения.

В чем же проблема?

В приложении мы создали одну проблему, которую может решить DI. Наш простой проект опирается на сильно связанные классы: класс ShoppingCart тесно связан с классом LinqValueCalculator, а класс HomeController тесно связан с обоими классами ShoppingCart и LinqValueCalculator. Это обозначает, что если мы захотим заменить класс LinqValueCalculator, нам нужно будет найти и заменить все ссылки в классах, которые тесно с ним связаны. В принципе, это не проблема для такого простого проекта, но это может принести много ошибок в реальный проект, особенно если мы хотим реализовать разные виды расчетов, а не просто заменить один класс другим.

Применение интерфейса

Мы можем частично решить эту проблему, используя C# интерфейс для того, чтобы отделить определение функционала калькулятора от его реализации. Чтобы показать это, мы добавили файл IValueCalculator.cs в папку Models и создали интерфейс, показанный в листинге 6-6.

Листинг 6-6: Интерфейс IValueCalculator
using System.Collections.Generic;
namespace EssentialTools.Models
{
	public interface IValueCalculator
	{
		decimal ValueProducts(IEnumerable<Product> products);
	}
}

Затем мы можем реализовать этот интерфейс в классе LinqValueCalculator, как показано в листинге 6-7.

Листинг 6-7: Применение интерфейса к классу LinqValueCalculator
using System.Collections.Generic;
using System.Linq;
namespace EssentialTools.Models
{
	public class LinqValueCalculator : IValueCalculator
	{
		public decimal ValueProducts(IEnumerable<Product> products)
		{
			return products.Sum(p => p.Price);
		}
	}
}

Интерфейс позволяет нам сломать сильную связь между классами ShoppingCart и LinqValueCalculator, как показано в листинге 6-8.

Листинг 6-8: Применение интерфейса к классу ShoppingCart
using System.Collections.Generic;
namespace EssentialTools.Models
{
	public class ShoppingCart
	{
		private IValueCalculator calc;
		public ShoppingCart(IValueCalculator calcParam)
		{
			calc = calcParam;
		}
		public IEnumerable<Product> Products { get; set; }
		public decimal CalculateProductTotal()
		{
			return calc.ValueProducts(Products);
		}
	}
}

Мы добились определенного прогресса, но C# требует от нас указать класс реализации для интерфейса при создании экземпляра, что достаточно справедливо, ведь ему необходимо знать, какой именно класс реализации мы хотим использовать. Это обозначает, что у нас до сих пор есть проблема в контроллере Home, когда мы создаем объект LinqValueCalculator:

...
public ActionResult Index() {
	IValueCalculator calc = new LinqValueCalculator();
	ShoppingCart cart = new ShoppingCart(calc) { Products = products };
	decimal totalValue = cart.CalculateProductTotal();
	return View(totalValue);
}
...

Мы хотим использовать Ninject, чтобы достичь того этапа, когда мы указываем, что мы хотим создать экземпляр реализации интерфейса IValueCalculator, но детали о том, какая требуется реализация, не являлись бы частью кода в контроллере Home.

Добавление Ninject в проект Visual Studio

Самый простой способ добавить Ninject в MVC проект – это использовать интегрированную поддержку Visual Studio для NuGet, что облегчает установку большого набора пакетов и поддерживает их в актуальном состоянии. Выберите Tools Library Package Manager Manage NuGet Packages for Solution, чтобы открыть диалоговое NuGet пакетов. Нажмите Online на левой панели и введите Ninject в поле поиска в правом верхнем углу диалогового окна. Вы увидите ряд пакетов Ninject, похожих на те, что показаны на рисунке 6-2. (Вы можете увидеть другие результаты поиска, которые отображают обновления, выпущенные после того, как мы написали эту главу).

Рисунок 6-2: Выбор Ninject из пакетов NuGet

Нажмите кнопку Install для библиотеки Ninject, и Visual Studio загрузит библиотеку и установит ее в вашем проекте: вы увидите, как Ninject появится в разделе References проекта.

NuGet является отличным инструментом для получения и поддержания пакетов, используемых в проектах Visual Studio, и мы рекомендуем вам использовать его. В этой книге, однако, нам нужно применять другой подход, потому что использование NuGet добавляет в проект много логов и дополнительной информации, чтобы была возможность синхронизировать и обновлять пакеты. Эти дополнительные данные удвоили бы размер нашего простого проекта. И поэтому исходной код, доступный для скачивания на Apress.com, стал бы слишком большим для многих читателей.

Вместо этого мы загрузили последнюю версию библиотеки с веб сайта Ninject (www.ninject.org) и установили его вручную, выбрав Add Reference из меню Project в Visual Studio, нажали на кнопку Browse, перешли к файлу и распаковали zip файл Ninject. Мы выбрали файл Ninject.dll и добавили его в проект вручную. Мы получили такой же результат, как если бы использовали NuGet, но, конечно, мы не сможем получить выгоду от простого обновления и управления пакетами.

Примечание

Внесем ясность: единственная причина, почему мы добавили библиотеку Ninject вручную, заключается в том, чтобы сохранить довольно небольшой размер исходного кода, прилагаемого к этой книге. В наших собственных проектах, где несколько дополнительных мегабайт данных не имеют никакого значения, мы используем NuGet. Мы рекомендуем вам также использовать NuGet.

Приступим к работе с Ninject

Есть три стадии начала работы с основным функционалом Ninject, и все они представлены в листинге 6-9. Тут выделены изменения, которые мы внесли в контроллер Home.

Листинг 6-9: Добавление базового функционала Ninject в метод действия Index
using EssentialTools.Models;
using System.Web.Mvc;
using System.Linq;
using Ninject;
namespace EssentialTools.Controllers
{
	public class HomeController : Controller
	{
		private Product[] products = {
			new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
			new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
			new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
			new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
		};
		public ActionResult Index()
		{
			IKernel ninjectKernel = new StandardKernel();
			ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
			IValueCalculator calc = ninjectKernel.Get<IValueCalculator>();
			ShoppingCart cart = new ShoppingCart(calc) { Products = products };
			decimal totalValue = cart.CalculateProductTotal();
			return View(totalValue);
		}
	}
}

Первый этап заключается в подготовке Ninject для использования. Нам нужно создать экземпляр Ninject ядра – это объект, который мы будем использовать для связи с Ninject и запросом реализаций интерфейса. Вот выражение из листинга, которое создает ядро:

...
IKernel ninjectKernel = new StandardKernel();
...

Нам нужно создать реализацию интерфейса Ninject.IKernel, что мы делаем, создав новый экземпляр класса StandardKernel. Это позволит нам выполнить второй этап, который заключается в создании связи между интерфейсами в нашем приложении и реализациями классов, с которыми мы хотим работать. Вот выражение из листинга, которое это делает:

...
ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
...

Ninject использует параметры C# типов, чтобы создать связь: мы устанавливаем интерфейс, с которым мы хотим работать, в качестве параметра типа для метода Bind и вызываем метод To для результата, который он возвращает. Мы устанавливаем класс реализации, для которого мы хотим создать экземпляр, в качестве параметра типа для метода To. Это выражение говорит Ninject, что когда его попросят реализовать интерфейс IValueCalculator, он должен выполнить запрос, создав новый экземпляр класса LinqValueCalculator.

Последний этап заключается в реальном использовании Ninject, что мы делаем при помощи метода Get:

...
IValueCalculator calc = ninjectKernel.Get<IValueCalculator>();
...

Параметр типа, используемый методом Get, говорит Ninject, в каком интерфейсе мы заинтересованы, а результаты этого метода является экземпляром типа реализации, который мы только что определили для метода To.

Установка MVC DI

Результатом этих трех этапов, которые мы показали вам в предыдущем листинге, является то, что мы знаем, какой класс реализации должен быть использован для выполнения запросов для интерфейса IValueCalculator, который был установлен в Ninject. Но, конечно, мы еще не улучшили наше приложение, потому что все это по-прежнему определяется в контроллере Home: это означает, что контроллер Home по-прежнему тесно связан с классом LinqValueCalculator.

В следующих разделах мы покажем вам, как вставить Ninject в сердце MVC приложения, что позволит нам упростить контроллер, расширить влияние Ninject, чтобы он работал по всему приложению и использовал свои дополнительные возможности.

Создание DR (Dependency Resolver)

Первое изменение, которое мы собираемся сделать, заключается в создании DR (dependency resolver: функциональная возможность, которая отвечает за создание зависимостей). MVC использует DR для создания экземпляров классов, для которых он должен обрабатывать запросы. Создавая DR, мы гарантируем, что Ninject будет использоваться всякий раз, когда будет создан объект.

Добавьте в наш проект новую папку Infrastructure и добавьте новый файл класса NinjectDependencyResolver.cs. Убедитесь, что содержимое этого файла соответствует листингу 6-10.

Листинг 6-10: Содержимое файла NinjectDependencyResolver.cs
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using Ninject;
using Ninject.Parameters;
using Ninject.Syntax;
using System.Configuration;
using EssentialTools.Models;
namespace EssentialTools.Infrastructure
{
	public class NinjectDependencyResolver : IDependencyResolver
	{
		private IKernel kernel;
		public NinjectDependencyResolver()
		{
			kernel = new StandardKernel();
			AddBindings();
		}
		public object GetService(Type serviceType)
		{
			return kernel.TryGet(serviceType);
		}
		public IEnumerable<object> GetServices(Type serviceType)
		{
			return kernel.GetAll(serviceType);
		}
		private void AddBindings()
		{
			kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
		}
	}
}

MVC фреймворк вызовет методы GetService или GetServices, когда ему будет нужен экземпляр класса для обработки входящих запросов. Работа DR заключается в создании этого экземпляра: задача, которую мы выполняем при помощи методов Ninject TryGet и GetAll. Метод TryGet работает как метод Get, который мы использовали ранее, но он возвращает null, если нет подходящей связки, а не выбрасывает исключение. Метод GetAll поддерживает несколько связок для одного типа.

Наш класс DR также находится там, где мы установили наши Ninject связки. В методе AddBindings мы используем методы Bind и To, чтобы установить связь между интерфейсом IValueCalculator и классом LinqValueCalculator.

Регистрация DR

Нам нужно сказать MVC, что мы хотим использовать наш собственный DR, мы это можем сделать, изменив файл Global.asax.cs. Вы можете увидеть дополнения, которые мы внесли в класс MVCApplication, содержащийся в этом файле, в листинге 6-11.

Листинг 6-11: Регистрация DR
using EssentialTools.Infrastructure;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
namespace EssentialTools
{
	public class MvcApplication : System.Web.HttpApplication
	{
		protected void Application_Start()
		{
			AreaRegistration.RegisterAllAreas();
			DependencyResolver.SetResolver(new NinjectDependencyResolver());
			WebApiConfig.Register(GlobalConfiguration.Configuration);
			FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
			RouteConfig.RegisterRoutes(RouteTable.Routes);
		}
	}
}

С этим дополнением Ninject будет предложена возможность создать любой экземпляр объекта, который требует MVC фреймворк, разместив DI прямо в ядро нашего MVC приложения.

Рефакторинг контроллера Home

Последним шагом является рефакторинг контроллера Home, чтобы он использовал возможности, которые мы создали в предыдущих разделах. Изменения, которые мы сделали, представлены в листинге 6-12.

Листинг 6-12: Рефакторинг контроллера Home
using EssentialTools.Models;
using System.Web.Mvc;
using System.Linq;
namespace EssentialTools.Controllers
{
	public class HomeController : Controller
	{
		private Product[] products = {
			new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
			new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
			new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
			new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
		};
		private IValueCalculator calc;
		public HomeController(IValueCalculator calcParam)
		{
			calc = calcParam;
		}
		public ActionResult Index()
		{
			ShoppingCart cart = new ShoppingCart(calc) { Products = products };
			decimal totalValue = cart.CalculateProductTotal();
			return View(totalValue);
		}
	}
}

Основное изменение, которое мы здесь сделали, заключается в том, что мы добавили конструктор, который принимает реализацию интерфейса IValueCalculator. Мы не указали, с какой реализацией мы хотим работать, и мы добавили переменную экземпляра calc, которую мы можем использовать для обращения к IValueCalculator, который мы получаем в конструктор через класс контроллера.

Другое изменение, которые мы сделали, заключается в том, чтобы удалить любые упоминания о Ninject коде или классе LinqValueCalculator: наконец, мы разбили сильную связь между HomeController и классом LinqValueCalculator. Если вы запустите пример, вы увидите результат, показанный на рисунке 6-3. Естественно, мы получили тот же результат, что и когда мы создавали экземпляр класса LinqValueCalculator непосредственно в контроллере.

Рисунок 6-3: Результат запуска приложения

То, что мы создали, является примером внедрения через конструктор, это одна из форм внедрения зависимостей. Вот то, что произошло, когда вы запустили пример приложения и Internet Explorer сделал запрос на корневой URL приложения:

  1. MVC фреймворк получил запрос и выяснил, что запрос предназначен для контроллера Home (мы объясним, как MVC фрейморк выясняет это, в главе 15).
  2. MVC фреймворк попросил наш пользовательский DR класс создать новый экземпляр класса HomeController, указав класс, используя параметр Type метода GetService.
  3. Наш DR попросил Ninject создать новый класс HomeController при помощи передачи объекта Type методу TryGet.
  4. Ninject изучил конструктор HomeController и обнаружил, что он требует реализацию IValueCalculator.
  5. Ninject создает экземпляр класса LinqValueCalculator и использует его для создания нового экземпляра класса HomeController.
  6. Ninject передает вновь созданный экземпляр HomeController пользовательскому DR, который возвращает его MVC фреймоворку. MVC фреймворк использует экземпляр контроллера для обработки запроса.

Мы все это объяснили, потому что DI может показаться немного запутанным, если вы впервые с ним сталкиваетесь. Одно из преимуществ такого подхода заключается в том, что любой контроллер может объявить, что он требует в своем конструкторе IValueCalculator и что будет использоваться Ninject. Наш пользовательский DR создает экземпляр реализации, которую мы указали в методе AddBindings.

Самое приятное то, что нам нужно изменить только наш класс DR, если мы хотим заменить LinqValueCalculator другой реализацией, потому что это единственное место, где требуется определить реализацию, которая используется для выполнения запросов для интерфейса IValueCalculator.

Создание цепочек зависимостей

Когда вы просите Ninject создать тип, он проверяет связи между этим типом и другими типами. Если есть дополнительные зависимости, Ninject автоматически схватывает их и создает экземпляры всех требуемых классов. Чтобы показать эту функцию, мы добавили файл Discount.cs в папку Models проекта и определили новый интерфейс и класс, реализующий его, как представлено в листинге 6-13.

Листинг 6-13: Определение нового интерфейса и реализации
namespace EssentialTools.Models
{
	public interface IDiscountHelper
	{
		decimal ApplyDiscount(decimal totalParam);
	}
	public class DefaultDiscountHelper : IDiscountHelper
	{
		public decimal ApplyDiscount(decimal totalParam)
		{
			return (totalParam - (10m / 100m * totalParam));
		}
	}
}

IDiscountHelper определяет метод ApplyDiscount, который будет применять скидку к значению decimal. Класс DefaultDiscounterHelper реализует интерфейс и применяет фиксированную 10-процентную скидку. Мы изменили класс LinqValueCalculator, так что он использует интерфейс IDiscountHelper при выполнении расчетов, как показано в листинге 6-14.

Листинг 6-14: Добавление зависимости в класс LinqValueCalculator
using System.Collections.Generic;
using System.Linq;
namespace EssentialTools.Models
{
	public class LinqValueCalculator : IValueCalculator
	{
		private IDiscountHelper discounter;
		public LinqValueCalculator(IDiscountHelper discountParam)
		{
			discounter = discountParam;
		}
		public decimal ValueProducts(IEnumerable<Product> products)
		{
			return discounter.ApplyDiscount(products.Sum(p => p.Price));
		}
	}
}

Вновь добавленный конструктор класса принимает реализацию интерфейса IDiscountHelper, которая затем используется в методе ValueProducts, чтобы применить скидку к совокупной стоимости обрабатываемых объектов Product.

Мы связываем интерфейс IDiscountHelper c классом реализации, используя ядро Ninject в классе NinjectDependencyResolver, так же как мы сделали для IValueCalculator, как показано в листинге 6-15.

Листинг 6-15: Связывание другого интерфейса с его реализацией
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using Ninject;
using Ninject.Parameters;
using Ninject.Syntax;
using System.Configuration;
using EssentialTools.Models;
namespace EssentialTools.Infrastructure
{
	public class NinjectDependencyResolver : IDependencyResolver
	{
		private IKernel kernel;
		public NinjectDependencyResolver()
		{
			kernel = new StandardKernel();
			AddBindings();
		}
		public object GetService(Type serviceType)
		{
			return kernel.TryGet(serviceType);
		}
		public IEnumerable<object> GetServices(Type serviceType)
		{
			return kernel.GetAll(serviceType);
		}
		private void AddBindings()
		{
			kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
			kernel.Bind<IDiscountHelper>().To<DefaultDiscountHelper>();
		}
	}
}

Мы создали цепочку зависимостей, которую легко обрабатывает Ninject, используя связи, которые мы определили в пользовательском DR. Для того чтобы удовлетворить запрос для класса HomeController, Ninject считает, что необходимо создать реализацию для класса IValueCalculator, и он делает это, рассматривая свои связи и понимая, что наша политика для этого интерфейса заключается в использовании класса LinqValueCalculator. Но для того, чтобы создать объект LinqValueCalculator, Ninject понимает, что он должен использовать реализацию IDiscountHelper, и поэтому он рассматривает связи и создает объект DefaultDiscountHelper. Он создает DefaultDiscountHelper и передает его конструктору объекта LinqValueCalculator, который в свою очередь передает его конструктору класса HomeController. Затем он используется для обслуживания запроса пользователя. Ninject проверяет каждый класс, для которого он создал экземпляр для внедрения зависимостей, таким образом неважно, насколько длинными и сложными являются цепочки зависимостей.

Определение значений свойств и параметров конструктора

Мы можем конфигурировать классы, которые создает Ninject, путем предоставления информации о нужных значениях, применяемых к свойствам, когда мы связываем интерфейс с его реализацией. Чтобы продемонстрировать эту функцию, мы изменили класс DefaultDiscountHelper так, что он определяет свойство DiscountSize, которое используется для расчета суммы скидки, как показано в листинге 6-16.

Листинг 6-16: Добавление свойства в класс реализации
namespace EssentialTools.Models
{
	public interface IDiscountHelper
	{
		decimal ApplyDiscount(decimal totalParam);
	}
	public class DefaultDiscountHelper : IDiscountHelper
	{
		public decimal DiscountSize { get; set; }
		public decimal ApplyDiscount(decimal totalParam)
		{
			return (totalParam - (DiscountSize / 100m * totalParam));
		}
	}
}

Когда мы привязываем конкретный класс к типу при помощи Ninject, мы можем использовать метод WithPropertyValue для установки значения свойства DiscountSize в классе DefaultDiscountHelper. Вы можете увидеть изменение, которое мы внесли в метод AddBindings класса NinjectDependencyResolver, в листинге 6-17.

Листинг 6-17: Использование Ninject метода WithPropertyValue
private void AddBindings() {
	kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
	kernel.Bind<IDiscountHelper>()
		.To<DefaultDiscountHelper>().WithPropertyValue("DiscountSize", 50M);
}

Обратите внимание, что мы должны указать имя свойства в виде строки. Нам не нужно менять любую другую связь, а также менять то, как мы используем метод Get для получения экземпляра класса ShoppingCart.

Значение свойства установлено в связке с классом DefaultDiscountHelper, и его результат заключается в вычислении общей стоимости предметов со скидкой. Это изменение показано на рисунке 6-4.

Рисунок 6-4: Результат применения скидки через свойство при работе с цепочкой зависимостей

Если вам нужно установить более одного значения свойства, можно составить цепочку вызовов метода WithPropertyValue и охватить их всех. Мы можем сделать то же самое с параметрами конструктора. В листинге 6-18 показан класс DefaultDiscounterHelper, переделанный таким образом, чтобы размер скидки передавался в качестве параметра конструктора.

Листинг 6-18: Использование свойства конструктора в классе реализации
namespace EssentialTools.Models
{
	public interface IDiscountHelper
	{
		decimal ApplyDiscount(decimal totalParam);
	}
	public class DefaultDiscountHelper : IDiscountHelper
	{
		public decimal discountSize;
		public DefaultDiscountHelper(decimal discountParam)
		{
			discountSize = discountParam;
		}
		public decimal ApplyDiscount(decimal totalParam)
		{
			return (totalParam - (discountSize / 100m * totalParam));
		}
	}
}

Чтобы связать этот класс, используя Ninject, мы указываем значение параметра конструктора при помощи метода WithConstructorArgument в методе AddBindings, как показано в листинге 6-19.

Листинг 6-19: Связывание с классом, который требует параметр конструктора
private void AddBindings() {
	kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
	kernel.Bind<IDiscountHelper>()
		.To<DefaultDiscountHelper>().WithConstructorArgument("discountParam", 50M);
}

Эта техника позволяет внедрять значение в конструктор. Еще раз, можно связать вызовы этих методов, чтобы указать несколько значений, а затем смешивать и сочетать их с зависимостями. Ninject выяснит, что нам нужно, и создаст это соответствующим образом.

Совет

Обратите внимание, что мы не просто изменили вызов WithPropertyValue к WithConstructorArgument. Мы также изменили имя нужного члена, чтобы он соответствовал соглашению C# по именам параметров.

Использование условной связки

Ninject поддерживает ряд условных связующих методов, которые позволяют нам указать, какие классы должны использоваться для ответов на запросы для конкретных запросов. Чтобы продемонстрировать эту функцию, мы добавили новый файл FlexibleDiscountHelper.cs в папку Models нашего проекта, содержимое которого вы можете увидеть в листинге 6-20.

Листинг 6-20: Содержание файла FlexibleDiscountHelper.cs
namespace EssentialTools.Models
{
	public class FlexibleDiscountHelper : IDiscountHelper
	{
		public decimal ApplyDiscount(decimal totalParam)
		{
			decimal discount = totalParam > 100 ? 70 : 25;
			return (totalParam - (discount / 100m * totalParam));
		}
	}
}

Класс FlexibleDiscountHelper применяет различные скидки, исходя из величины общей суммы, на которую распространяется скидка. Затем мы можем изменить метод AddBindings в NinjectDependencyResolver, чтобы сказать Ninject, когда мы хотим использовать FlexibleDiscountHelper и когда мы хотим использовать DefaultDiscountHelper, как показано в листинге 6-21.

Листинг 6-21: Обновление метода AddBindings для использования условной связки
private void AddBindings() {
	kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
	kernel.Bind<IDiscountHelper>()
		.To<DefaultDiscountHelper>().WithConstructorArgument("discountParam", 50M);
	kernel.Bind<IDiscountHelper>().To<FlexibleDiscountHelper>()
		.WhenInjectedInto<LinqValueCalculator>();
}

Новая связка указывает, что если класс FlexibleDiscountHelper должен использоваться в качестве реализации интерфейса IDiscountHelper, тогда Ninject будет внедрять реализацию в объект LinqValueCalculator.

Мы оставили на месте исходную связку для IDiscountHelper. Ninject пытается найти наиболее подходящее совпадение, и поэтому стоит оставлять связку по умолчанию для того же класса или интерфейса, так чтобы у Ninject был резервный вариант, если критерии для условной связки не могут быть удовлетворены. Ninject поддерживает несколько различных условных связующих методов, и наиболее полезные из них мы привели в таблице 6-1.

Таблица 6-1: Условные связующие методы Ninject
Метод Результат
When(predicate) Связка используется, если утверждение (predicate) – лямбда-выражение – считается верным.
WhenClassHas<T>() Связка используется, если внедряемый класс, аннотируется атрибутом, чей тип определяется Т.
WhenInjectedInto<T>() Связка используется, если внедряемый класс принадлежит к типу T.