2013-04-20 3 views
1

Допустим, у меня есть метод, который требует статического свойства, и я хочу создать модульные тесты, поэтому я переношу его в класс-оболочку. Назовем интерфейс IFoo & конкретным классом Foo.Как получить объект в глубоко вложенный метод, не передавая его через каждый объект?

Теперь, если мой метод вызывается из представления MVC, как вы получаете экземпляр обертки в этот метод?

Очевидно, что я могу добавить параметр IFoo в свой конструктор контроллера, свойство IFoo для моей модели представления и параметр IFoo для моего метода, а затем передать его по цепочке; контроллер, модель просмотра, вид, метод расширения. Мне это не кажется приемлемым.

Так есть ли более чистый способ сделать это?

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

var dt = kernel.Get<IFoo>(); 

Я предположил, что это поможет мне избежать всего описанного выше параметра/свойства тротуара. Теперь я знал, что мне все равно нужно получить переменную ядра откуда-то, но я подумал, что я вспомнил, что видел что-то о вызове с помощью области потока/сеанса/запроса. Я думал, что могу создать экземпляр одного и того же экземпляра ядра независимо от того, где он был вызван, но когда я просмотрел его, я узнал, что это только для экземпляров объектов, которые запускает ядро ​​... не ядро.

Итак, есть ли способ получить экземпляр Foo в метод, не передавая его через кучу объектов, которые ничего не делают, кроме как передать его?

+0

Можете ли вы сделать его более конкретным, пожалуйста? Вы утверждаете, что это произошло несколько раз - наверняка вы можете привести (даже надуманный) пример кода? Также мне больно понять, что вы подразумеваете под «Я узнал, что это только для экземпляров объектов, вызываемых ядром ... не самого ядра. ' –

+0

@RubenBartelink - первая редакция имеет довольно конкретный пример. См. Http://stackoverflow.com/revisions/16124853/1 –

+0

@RubenBartelink - Что касается предложения ядра, я имел в виду, что я знал, что могу сделать kernel.Bind () .ToSelf(). InRequestScope(); чтобы получить тот же Foo на протяжении всего запроса, но не было никакого способа получить одно и то же ядро ​​в течение всего запроса. Поэтому мне пришлось бы пройти через ядро ​​через след, о котором я упоминал в вопросе. Поэтому я ничего не получаю. Имеет ли это смысл? –

ответ

1

Поскольку я не знаю вашего точного варианта использования, то, что я могу предложить, может быть или не быть уместным. Есть несколько способов, с которыми я справился, и это зависит от того, где вы тестируете. С точки зрения метода расширения, я думаю, вы можете просто взять его DateTime и вручную ввести соответствующие значения в свои модульные тесты для расширения. Если ему нужно проверить значение противDateTime.UtcNow, это немного отличается, но вы можете обрабатывать его с нулевым DateTime, который по умолчанию равен DateTime.UtcNow, если вы не укажете второй параметр.

public static IHtmlString RegularExtension(this HtmlHelper helper, DateTime when) 
    { 
     ... 
    } 

    public static IHtmlString ComparisonExtension(this HtmlHelper helper, DateTime when, DateTime? now = null) 
    { 
     var nowDate = now ?? DateTime.UtcNow; // verified by inspection, tests use specified values 
     ... 
    } 

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

С точки зрения контроллера, если вам нужно использовать согласованное значение для текущего времени, вы можете ввести оболочку в контроллер и иметь модель, заполненную «развернутым» значением из обертки. Таким образом, вы можете проверить, что контроллер правильно устанавливает значение в модели, не связывая все нисходящее по отношению к обертке. Ключ в том, что весь ваш код использует введенное (и развернутое) значение вместо прямого вызова DateTime.UtcNow. С точки зрения тестов на этих классах они не знают, откуда исходит эта ценность, только что она поставляется с восходящего потока.

public FooController(IDateTimeWrapper timeWrapper) 
    { 
     var model = new FooModel { Now = timeWrapper.Unwrap(), ... }; 

     ... 

     return View(model); 
    } 
+0

Спасибо, Тим, но пока пример DateTime несколько реален, я использовал его в основном, чтобы проиллюстрировать проблематичный образец, который я вижу повсюду. Это больше о том, как мне избежать передачи моего объекта через кучу классов, которые не используют его, кроме как передать его. Имеет ли это смысл? –

+0

@JohnMacIntyre Я понимаю, но здесь есть ключевой принцип: везде, где это возможно, ваши зависимости предоставляют значения компонентам более низкого уровня, а не распространяют зависимость. В частности, когда дело доходит до представления, вы действительно должны иметь все необходимые данные в качестве значений. Другой альтернативой является использование DependencyResolver.Current.GetService() ', http://bradwilson.typepad.com/blog/2010/10/service-location-pt5-idependencyresolver.html и' DependencyResolver.SetResolver' в вашем тесты. – tvanfosson

+0

Спасибо. Я получаю идею о том, что функциональность более высокого уровня должна проходить IFoo, но идея создания поля в контроллере ничего не делать, кроме переноса объекта с контроллера на * one * метод просмотра методов действий, кажется, кажется, плохой дизайн. Я имею в виду, что не свойства и поля, которые должны иметь высокую сплоченность в классе? Я думал, что DI Framework должен был позволить вам справиться с этим лучше. –

1

Если ViewModel или View создается Ninject, а также, вы должны быть в состоянии добавить IFoo как конструктор зависимости и получить его вводят. Если вы хотите получить прямой доступ к Kernel и решить проблему (обычно это не самая лучшая идея), вы можете ввести IResolutionRoot и позвонить Get on it.

+0

Undid my +1. Это плохой совет - не создавайте ViewModels через DI, также не вводите 'IResolutionRoot' во что-либо, что на самом деле не является Фабрикой. Причина моего первоначального +1 заключалась в том, что вы обратились к тому, как можно передать материал вниз по цепочке (пункт «Абстрактная фабрика» в моем ответе) –

+0

Что плохого в том, чтобы создавать ViewModels, созданные DI? Я согласен с тем, что не использую IResolutionRoot, я упомянул, что это обычно не очень хорошая идея. – treze

+0

В целом, модель должна быть обладающей логикой Responsibility/business, ViewModel должен адаптировать вещи к представлению. Хотя это не нужно переводить в жесткое и быстрое правило, что все нужно сгладить в объекты ценности, я хочу сказать, что вы можете (и должны) использовать намного больше «новых» и намного меньше абстрактной фабрики для создания ViewModels в целом. Я знаю, что есть контрпримеры, и я не склоняюсь ни к чему. См. Http://stackoverflow.com/questions/4835046/why-not-use-an-ioc-container-to-resolve-dependencies-for-entities-business-objec –

1

Прежде всего, купите http://manning.com/seemann - вы не пожалеете.

В общем, проблема «Я здесь, в моем дереве объектов, дайте мне X» адресуется в одном из следующих способов:

  • Constructor Injection - любой объект, который нуждается в X, спрашивает об этом во время строительства. См. Инъекционные шаблоны в Вики-шаблоне Ninject
  • Место службы - каждый может всегда кричать на центральную фигуру матери, чтобы получить материал - то есть держать ref в контейнере в статике и называть его волей-неволей - см. Статью с одним и тем же именем
  • Abstract Factory - вы получите инъекцию Func<T> или interface IXFactory { X CreateX(); } - см вики Ninject.Extensions.Factory
  • Метод Injection - он получает создан где-то и проходите его вокруг по мере необходимости - см инъекции зависимостей в .NET книге

Вы ожидаете немного большей части контейнера, чтобы сделать его волшебным возможно, для кого угодно, чтобы кричать: «Я хочу X», особенно в статическом классе, вызываемом из DTO. Помните, что контейнер не входит и переписывает ваши вызовы перехвата IL на новый. In может по существу применять несколько Decorators, Factoryories и Proxies в качестве клея при работе с неглазурованными электромонтажными материалами.

Суть заключается в том, что соответствующий способ обработки вещи, как это

  • Все, что нужно DI должны (и могут) не быть статичным, период
  • Краткосрочные экземпляры объектов, таких как DTOS не должен участвовать в DI (это не значит, что нужно обрабатывать графические объекты с уверенностью даже при обработке запросов - именно то, что вы подключаете, должно быть Services, а не объектами). См Why not use an IoC container to resolve dependencies for entities/business objects?
  • Большую часть времени вы лучше от создания зависимостей явных с помощью метода инъекций и Constructor Injection - таким образом он проталкивает свои требования к поверхности, а не создавая внеполосного крыс связи гнезда через Service Location

Последнее значение имеет решающее значение - путем выявления реальных зависимостей и их прослушивания вы в конечном итоге выясняете, где Time Service, Scheduler или Formatter (или что бы то ни было, что абстракция более высокого уровня, которая отсутствует в вашем случае) должна жить со временем ,

Я настоятельно рекомендую прочитать здесь Mark Seemann's top answers, так как большинство из этих важных вопросов и тем подробно рассматриваются в них, не полагаясь на упрощенные объяснения, как мой ответ. Конечно, сама книга - лучшее общее использование вашего времени и денег!

EDIT: (Вдохновленный вашими комментариями с помощью @tvonfosson) Одна из ключевых вещей, которые Контейнер может принести на вечеринку (кроме поддержки одного X в пределах данной области действия (см. Ninject.Extensions.NamedScope wiki), должен иметь возможность пропускать косвенные зависимости вниз по иерархии сервисов без причины - то есть, если ваш Контроллер зависит от Службы, которая зависит от Планировщика, а планировщику требуется Часы, Служба не должна говорить о Часах, только Планировщиках.

Главное в контейнерах - убедиться, что вы не заходите слишком далеко, а затем попытайтесь оскорбить их для кода, который должен быть просто кодом.

+0

Спасибо за ответ и ваше терпение. Что касается «Одной из ключевых вещей, которые Контейнер может принести партии, - это возможность пропускать косвенные зависимости вниз по иерархии сервисов без причины» ... это то, что я получал в своих комментариях к Тиму и этому вопросу. Я понимаю, что контейнер DI не делает ничего волшебного, но я предположил, что могу просто настроить его и использовать его где угодно, не передавая его. На данный момент я отбросил рамки DI в моем проекте и просто передаю свои объекты через конструктор контроллера, пока не получу рекомендованную вами книгу. Еще раз спасибо. –

+0

@JohnMacIntyre Неплохая идея сделать инъекцию зависимости вручную - это не много печатать, и вы узнаете много. Просто не беспокойтесь слишком много с половинчатыми домами слишком большого количества Bastard Injection. –

Смежные вопросы