asp.net-mvc
Внедрение зависимости
Поиск…
замечания
Вся цель Injection Dependency Injection (DI) заключается в уменьшении сцепления кода. Представьте себе любой вид, если взаимодействие, связанное с новинкой, похоже на «пример жесткой кодировки».
Большая часть написания кода - это возможность его протестировать. Каждый раз, когда мы обновляем новую зависимость, мы делаем наш код трудным для тестирования, потому что у нас нет контроля над этой зависимостью.
Как бы вы протестировали код, который зависит от DataTime.Now, например? Он всегда меняется, поэтому у вас нет ссылки. Это когда вы вводите стабильный параметр в качестве отправной точки. Вы можете управлять им, вы можете писать тесты на основе различных значений и всегда получать правильный результат.
Поэтому хорошим вариантом является передача интерфейса или абстрактного класса в качестве параметра в конструкторе DI.
Интерфейс представляет собой четко определенный контракт, вы всегда можете полагаться на методы, которые должны быть там, и вы всегда можете полагаться на сигнатуры метода.
Как только вы начнете использовать DI, откроются другие аспекты. Например, даже если вы передадите интерфейс в какой-то момент, вам понадобится реальная реализация для выполнения любой работы. Здесь появляются другие понятия. Мы можем использовать IOC (Inversion of Control) для разрешения наших зависимостей. Это означает, что мы инструктируем наш код всегда использовать конкретную реализацию для любого контракта. Конечно, есть и другие способы сделать это. Мы всегда можем создавать экземпляры каждого контракта с конкретной реализацией, и с этого момента наш код может использовать эту часть:
public ILogging Logging { get; set }
в какой-то момент мы его инициализируем.
Logging = new FileLogging();
это всегда будет работать, пока наш класс выполнит ожидаемый контракт:
public class FileLogging : ILogging
с момента инициализации мы всегда используем объект Logging. Это облегчает жизнь, потому что, если мы когда-либо решаем изменить и использовать DatabaseLogging, например, нам нужно только изменить код в одном месте, и именно это мы инициализируем класс Logging.
Является ли DI только хорошим для тестирования? Нет, DI важно при написании кода поддерживаемого кода. Это позволяет четко разобраться в проблемах.
Когда вы пишете какой-либо код, подумайте ... можно ли его проверить, могу ли я написать тест, то есть при введении значения DateTime вместо использования DateTime.Now имеет смысл.
Конфигурации Ninject
После установки контейнера IoC (Inversion of Control) необходимы некоторые настройки для его работы. В этом случае я буду использовать Ninject. В файле NinjectWebCommon, который находится в папке App_Start, замените метод CreateKernel на:
private static IKernel CreateKernel()
{
// Create the kernel with the interface to concrete bindings
var kernel = RegisterServices();
try
{
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
return kernel;
}
catch
{
kernel.Dispose();
throw;
}
}
И метод RegisterServices с:
private static StandardKernel RegisterServices()
{
Container container = new Container();
// encapsulate the interface to concrete bindings in another class or even in another layer
StandardKernel kernel = container.GetServices();
return kernel;
}
Создайте новый класс для привязки, который в этом случае называется Container:
public class Container
{
public StandardKernel GetServices()
{
// It is good practice to create a derived class of NinjectModule to organize the binding by concerns. In this case one for the repository, one for service and one for app service bindings
return new StandardKernel(new NinjectRepositoryModule(),
new NinjectServiceModule(),
new NinjectAppServiceModule());
}
}
Наконец, в каждом производном классе NinjectModule модифицируйте привязки, перегружая метод Load, например:
public class NinjectRepositoryModule: NinjectModule
{
public override void Load()
{
// When we need a generic IRepositoryBase<> to bind to a generic RepositoryBase<>
// The typeof keyword is used because the target method is generic
Bind(typeof (IRepositoryBase<>)).To(typeof (RepositoryBase<>));
// When we need a IUnitOfWorkbind to UnitOfWork concrete class that is a singleton
Bind<IUnitOfWork>().To<UnitOfWork>().InSingletonScope();
}
}
Другой пример производного NinjectModule:
public class NinjectServiceModule :NinjectModule
{
public override void Load()
{
// When we need a IBenefitService to BenefitService concrete class
Bind<IBenefitService>().To<BenefitService>();
// When we need a ICategoryService to CategoryService concrete class
Bind<ICategoryService>().To<CategoryService>();
// When we need a IConditionService to ConditionService concrete class
Bind<IConditionService>().To<ConditionService>();
}
}
Использование интерфейсов
В конкретном классе, которому нужна услуга, используйте интерфейс для доступа к службе вместо ее реализации, например:
public class BenefitAppService
{
private readonly IBenefitService _service;
public BenefitAppService(IBenefitService service)
{
_service = service;
}
public void Update(Benefit benefit)
{
if (benefit == null) return
_service.Update(benefit);
_service.Complete();
}
}
Теперь, если вам нужно что-то в конкретном классе, вы не будете вмешиваться в код выше. Вы можете изменить реализацию сервиса для другой полностью разницы, и до тех пор, пока она удовлетворяет интерфейсу, вам хорошо идти. Также это очень легко проверить.
Инъекционная зависимость конструктора
Инъекция зависимостей конструктора требует, чтобы параметры в конструкторе вводили зависимости. Таким образом, вы должны передавать значения при создании нового объекта.
public class Example
{
private readonly ILogging _logging;
public Example(ILogging logging)
{
this._logging = logging;
}
}
Жестко закодированная зависимость
public class Example
{
private FileLogging _logging;
public Example()
{
this._logging = new FileLogging();
}
}
параметр DI
public DateTime SomeCalculation()
{
return DateTime.Now.AddDays(3);
}
против
public DateTime SomeCalculation(DateTime inputDate)
{
return inputDate.AddDays(3);
}
Инъекция зависимых от инъекций
Преобразователь зависимостей используется для избежания тесно связанных классов, повышения гибкости и упрощения тестирования. Вы можете создать свой собственный инжектор зависимостей (не рекомендуется) или использовать один из хорошо написанных и проверенных инжекторов зависимостей. В этом примере я собираюсь использовать Ninject .
Шаг первый: создайте зависимый преобразователь.
Прежде всего, загрузите Ninject из NuGet. Создайте папку с именем Infrastructure и добавьте класс с именем NinjectDependencyResolver :
using Ninject;
using System;
using System.Collections.Generic;
using System.Web.Mvc;
public class NinjectDependencyResolver
: IDependencyResolver
{
private IKernel kernel;
public NinjectDependencyResolver()
{
// Initialize kernel and add bindings
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()
{
// Bindings added here
}
}
MVC Framework будет вызывать методы GetService и GetServices, когда ему требуется защита класса для обслуживания входящего запроса.
Шаг второй: зарегистрируйте зависимый преобразователь.
Теперь у нас есть наш пользовательский преобразователь зависимостей, и нам нужно зарегистрировать его, чтобы сообщить инфраструктуре MVC использовать наш зависимый преобразователь. Реестр зависимых имен в файле Global.asax.cs :
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
DependencyResolver.SetResolver(new NinjectDependencyResolver());
// .....
}
Шаг третий: добавьте привязки.
Представьте, что у нас есть интерфейс и имплантация:
public interface ICustomCache
{
string Info { get; }
}
public class CustomCache : ICustomCache
{
public string Info
{
get
{
return "Hello from CustomCache.";
}
}
}
Если мы хотим использовать CustomCache в нашем контроллере без жесткого связывания нашего контроллера с CustomCache, нам необходимо привязать ICustomCache к CustomCache и ввести его с помощью Ninject. Прежде всего, привяжите ICustomCache к CustomCache , добавив следующий код в метод AddBindings () для NinjectDependencyResolver :
private void AddBindings()
{
// Bindings added here
kernel.Bind<ICustomCache>().To<CustomCache>();
}
Затем подготовьте ваш контроллер для инъекций, как показано ниже:
public class HomeController : Controller
{
private ICustomCache CustomCache { get; set; }
public HomeController(ICustomCache customCacheParam)
{
if (customCacheParam == null)
throw new ArgumentNullException(nameof(customCacheParam));
CustomCache = customCacheParam;
}
public ActionResult Index()
{
// cacheInfo: "Hello from CustomCache."
string cacheInfo = CustomCache.Info;
return View();
}
}
Это пример инъекции бизнес-структур, и это одна из форм инъекции зависимости . Как видите, наш домашний контроллер не зависит от класса CustomCache. Если мы хотим использовать другое приложение ICustomCache в нашем приложении, единственное, что нам нужно изменить, - это привязать ICustomCache к другому внедрению, и это единственный шаг, который нам нужно предпринять. Что здесь произошло, MVC Framework попросила зарегистрированного зависимого преобразователя создать экземпляр класса HomeController с помощью метода GetService . Метод GetService попросит ядро Ninject создать запрошенный объект, а ядро Ninject проверяет тип в его терминах и выясняет, что конструктор HomeController требует ICustomCache, и привязка уже добавлена для ICustomCache . Ninject создает экземпляр связанного класса , использует его для создания HomeController и возвращает его MVC Framework.
Цепи зависимостей.
Когда Ninject пытается создать тип, он анализирует другие зависимости между типом и другими типами, и если есть какие-либо Ninject, они также пытаются их создать. Например, если для нашего класса CustomCache требуется ICacheKeyProvider, и если добавление бина для ICacheKeyProvider Ninject может предоставить его для нашего класса.
Интерфейс ICacheKeyProvider и внедрение SimpleCacheKeyProvider:
public interface ICacheKeyProvider
{
string GenerateKey(Type type);
}
public class SimpleCacheKeyProvider
: ICacheKeyProvider
{
public string GenerateKey(Type type)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
return string.Format("{0}CacheKey", type.Name);
}
}
Измененный класс CustomCache
public class CustomCache : ICustomCache
{
private ICacheKeyProvider CacheKeyProvider { get; set; }
public CustomCache(ICacheKeyProvider keyProviderParam)
{
if (keyProviderParam == null)
throw new ArgumentNullException(nameof(keyProviderParam));
CacheKeyProvider = keyProviderParam;
}
...........
}
Добавить привязку для ICacheKeyProvider :
private void AddBindings()
{
// Bindings added here
kernel.Bind<ICustomCache>().To<CustomCache>();
kernel.Bind<ICacheKeyProvider>().To<SimpleCacheKeyProvider>();
}
Теперь, когда мы переходим к HomeController, Ninject создает экземпляр SimpleCacheKeyProvider, который использует его для создания CustomCache и использует экземпляр CustomCache для создания HomeController .
Ninject имеет множество замечательных функций, таких как закодированная инъекция зависимостей, и вы должны изучить их, если хотите использовать Ninject.