Я запускаю новое настольное приложение и хочу создать его с помощью MVVM и WPF.
Я также собираюсь использовать TDD.
Проблема в том, что я не знаю, как мне использовать контейнер IoC для внедрения моих зависимостей в мой производственный код.
Предположим, у меня есть следующий класс и интерфейс:
public interface IStorage
{
bool SaveFile(string content);
}
public class Storage : IStorage
{
public bool SaveFile(string content){
// Saves the file using StreamWriter
}
}
И затем у меня есть еще один класс, который имеет IStorage
зависимость, предположим также, что этот класс является ViewModel или бизнес-классом ...
public class SomeViewModel
{
private IStorage _storage;
public SomeViewModel(IStorage storage){
_storage = storage;
}
}
Благодаря этому я могу легко писать модульные тесты, чтобы убедиться, что они работают должным образом, используя макеты и т. Д.
Проблема в том, когда дело доходит до использования его в реальном приложении. Я знаю, что у меня должен быть контейнер IoC, который связывает реализацию IStorage
интерфейса по умолчанию , но как мне это сделать?
Например, как бы было, если бы у меня был следующий xaml:
<Window
... xmlns definitions ...
>
<Window.DataContext>
<local:SomeViewModel />
</Window.DataContext>
</Window>
Как я могу правильно «сказать» WPF внедрять зависимости в этом случае?
Кроме того, предположим, что мне нужен экземпляр SomeViewModel
из моего кода C #, как мне это сделать?
Я чувствую, что полностью потерялся в этом, я был бы признателен за любой пример или руководство о том, как лучше всего с этим справиться.
Я знаком со StructureMap, но не эксперт. Кроме того, дайте мне знать, если есть более эффективный / простой / нестандартный фреймворк.
источник
Ответы:
Я использовал Ninject и обнаружил, что с ним приятно работать. Все настроено в коде, синтаксис довольно прост и имеет хорошую документацию (и множество ответов по SO).
В основном это выглядит так:
Создайте модель представления и возьмите
IStorage
интерфейс в качестве параметра конструктора:Создайте
ViewModelLocator
со свойством get для модели представления, которое загружает модель представления из Ninject:Сделайте
ViewModelLocator
ресурс приложения в App.xaml:Свяжите
DataContext
изUserControl
к соответствующему свойству в ViewModelLocator.Создайте класс, наследующий NinjectModule, который установит необходимые привязки (
IStorage
и модель просмотра):Инициализируйте ядро IoC при запуске приложения с необходимыми модулями Ninject (пока что выше):
Я использовал статический
IocKernel
класс для хранения экземпляра ядра IoC для всего приложения, поэтому при необходимости могу легко получить к нему доступ:В этом решении используется статический
ServiceLocator
(IocKernel
), который обычно считается анти-шаблоном, поскольку он скрывает зависимости класса. Однако очень сложно избежать какого-либо ручного поиска служб для классов пользовательского интерфейса, поскольку они должны иметь конструктор без параметров, и вы все равно не можете управлять созданием экземпляра, поэтому вы не можете внедрить виртуальную машину. По крайней мере, этот способ позволяет тестировать виртуальную машину изолированно, в которой и находится вся бизнес-логика.Если у кого-то есть способ получше, поделитесь, пожалуйста.
РЕДАКТИРОВАТЬ: Lucky Likey предоставил ответ, чтобы избавиться от статического локатора сервисов, позволив Ninject создавать экземпляры классов пользовательского интерфейса. Подробности ответа можно увидеть здесь
источник
DataContext="{Binding [...]}"
. Это заставляет VS-Designer выполнять весь программный код в конструкторе ViewModel. В моем случае Window выполняется и модально блокирует любое взаимодействие с VS. Возможно, следует изменить ViewModelLocator, чтобы не находить «настоящие» модели представления во время разработки. - Еще одно решение - «Отключить код проекта», что также предотвратит отображение всего остального. Возможно, вы уже нашли изящное решение этой проблемы. В таком случае я прошу вас показать это.В своем вопросе вы устанавливаете значение
DataContext
свойства представления в XAML. Для этого требуется, чтобы ваша модель представления имела конструктор по умолчанию. Однако, как вы заметили, это не работает с внедрением зависимостей, когда вы хотите внедрить зависимости в конструктор.Таким образом, вы не можете установить
DataContext
свойство в XAML . Вместо этого у вас есть другие альтернативы.Если ваше приложение основано на простой иерархической модели представления, вы можете построить всю иерархию модели представления при запуске приложения (вам нужно будет удалить
StartupUri
свойство изApp.xaml
файла):Это основано на графе объектов моделей представлений с корнем,
RootViewModel
но вы можете внедрить некоторые фабрики моделей представления в родительские модели представления, позволяя им создавать новые дочерние модели представления, поэтому граф объекта не нужно фиксировать. Это также, надеюсь, ответит на ваш вопрос. Предположим, мне нужен экземплярSomeViewModel
из моегоcs
кода, как мне это сделать?Если ваше приложение более динамично по своей природе и, возможно, основано на навигации, вам придется подключиться к коду, который выполняет навигацию. Каждый раз, когда вы переходите к новому представлению, вам необходимо создать модель представления (из контейнера DI), само представление и установить
DataContext
для представления модель представления. Сначала вы можете сделать это представление, где вы выбираете модель представления на основе представления, или вы можете сначала сделать это модель представлениягде модель просмотра определяет, какой вид использовать. Платформа MVVM предоставляет эту ключевую функциональность с помощью некоторого способа подключить контейнер DI к созданию моделей представления, но вы также можете реализовать его самостоятельно. Я здесь немного расплывчат, потому что в зависимости от ваших потребностей эта функция может стать довольно сложной. Это одна из основных функций, которые вы получаете от фреймворка MVVM, но использование собственной в простом приложении даст вам хорошее представление о том, что фреймворки MVVM предоставляют под капотом.Не имея возможности объявить
DataContext
в XAML, вы теряете некоторую поддержку во время разработки. Если ваша модель представления содержит некоторые данные, они появятся во время разработки, что может быть очень полезно. К счастью, вы можете использовать атрибуты времени разработки и в WPF. Один из способов сделать это - добавить следующие атрибуты к<Window>
элементу или<UserControl>
в XAML:Тип модели представления должен иметь два конструктора: по умолчанию для данных времени разработки и еще один для внедрения зависимости:
Таким образом вы можете использовать внедрение зависимостей и сохранить хорошую поддержку во время разработки.
источник
То, что я публикую здесь, является улучшением ответа sondergard, потому что то, что я собираюсь рассказать, не вписывается в комментарий :)
Фактически, я представляю изящное решение, которое позволяет избежать использования ServiceLocator и оболочки для
StandardKernel
экземпляра -Instance, который вызывается в решении sondergardIocContainer
. Зачем? Как уже упоминалось, это антипаттерны.Сделать
StandardKernel
доступным вездеКлюч к магии Ninject - это
StandardKernel
-Instance, который необходим для использования.Get<T>()
-Method.В качестве альтернативы sondergard
IocContainer
вы можете создатьStandardKernel
внутри класса -ClassApp
.Просто удалите StartUpUri из вашего App.xaml
Это CodeBehind приложения внутри App.xaml.cs
Отныне Ninject жив и готов к бою :)
Введение вашего
DataContext
Пока Ninject жив, вы можете выполнять все виды инъекций, например, Property Setter Injection или наиболее распространенный Constructor Injection .
Это, как Вы вводите свой ViewModel в ваши
Window
-хDataContext
Конечно, вы также можете Inject,
IViewModel
если выполняете правильные привязки, но это не часть этого ответа.Доступ к ядру напрямую
Если вам нужно вызвать методы ядра напрямую (например,
.Get<T>()
-Method), вы можете позволить ядру внедрить себя.Если вам понадобится локальный экземпляр ядра, вы можете ввести его как свойство.
Хотя это может быть очень полезно, я бы не рекомендовал вам это делать. Просто обратите внимание, что объекты, введенные таким образом, не будут доступны внутри Конструктора, потому что он будет введен позже.
Согласно этой ссылке вы должны использовать factory-Extension вместо того, чтобы вводить
IKernel
(DI Container).Как Ninject.Extensions.Factory будет использоваться также может быть красным здесь .
источник
Ninject.Extensions.Factory
, укажите это здесь, в комментариях, и я добавлю дополнительную информацию.DependencyProperty
поле поддержки, так и его методы Get и Set.Я использую подход "сначала просмотр", когда я передаю модель представления конструктору представления (в его коде программной части), который назначается контексту данных, например
Это заменяет ваш подход на основе XAML.
Я использую платформу Prism для обработки навигации - когда некоторый код запрашивает отображение определенного представления (путем «перехода» к нему), Prism разрешает это представление (внутренне, используя структуру DI приложения); каркас DI, в свою очередь, разрешит любые зависимости, которые имеет представление (модель представления в моем примере), затем разрешит его зависимости и так далее.
Выбор структуры DI в значительной степени не имеет значения, поскольку все они, по сути, делают одно и то же, то есть вы регистрируете интерфейс (или тип) вместе с конкретным типом, который вы хотите, чтобы структура создавала экземпляр, когда обнаруживает зависимость от этого интерфейса. Для записи я использую Castle Windsor.
Навигация с помощью Prism требует некоторого привыкания, но она довольно хороша, когда вы начинаете разбираться в ней, позволяя вам составлять свое приложение с использованием различных представлений. Например, вы можете создать «область» Prism в своем главном окне, а затем, используя навигацию Prism, переключаться с одного представления на другое в этой области, например, когда пользователь выбирает пункты меню или что-то еще.
В качестве альтернативы взгляните на одну из фреймворков MVVM, например MVVM Light. У меня нет опыта в этом, поэтому не могу комментировать, как они используются.
источник
Установите MVVM Light.
Частью установки является создание локатора модели вида. Это класс, который представляет ваши модели просмотра как свойства. Затем получатель этих свойств может возвращать экземпляры из вашего механизма IOC. К счастью, MVVM light также включает фреймворк SimpleIOC, но вы можете подключиться к другим, если хотите.
С помощью простого IOC вы регистрируете реализацию для типа ...
В этом примере ваша модель представления создается и передается объект поставщика услуг в соответствии с его конструктором.
Затем вы создаете свойство, которое возвращает экземпляр из IOC.
Умная часть состоит в том, что локатор модели представления затем создается в app.xaml или его эквиваленте в качестве источника данных.
Теперь вы можете привязать его к свойству MyViewModel, чтобы получить модель просмотра с внедренной службой.
Надеюсь, это поможет. Приносим извинения за любые неточности кода, закодированные из памяти на iPad.
источник
GetInstance
илиresolve
за ее пределами. В этом суть DI!Кейс Canonic DryIoc
Отвечаю на старый пост, но делать это
DryIoc
и делать то, что я считаю хорошим использованием DI и интерфейсов (минимальное использование конкретных классов).App.xaml
, и там мы говорим, какое исходное представление использовать; мы делаем это с помощью кода вместо стандартного xaml:StartupUri="MainWindow.xaml"
в App.xamlв выделенном коде (App.xaml.cs) добавьте следующее
override OnStartup
:это точка запуска; это также единственное место, куда
resolve
следует звонить.корень конфигурации (согласно книге Марка Симана «Внедрение зависимостей в .NET; единственное место, где следует упомянуть конкретные классы») будет находиться в том же самом коде, в конструкторе:
Замечания и еще несколько деталей
MainWindow
;Конструктор ViewModel с DI:
Конструктор ViewModel по умолчанию для дизайна:
Программный код представления:
и что нужно в представлении (MainWindow.xaml), чтобы получить экземпляр проекта с ViewModel:
Вывод
Таким образом, мы получили очень чистую и минимальную реализацию приложения WPF с контейнером DryIoc и DI, сохраняя при этом возможность создания экземпляров представлений и моделей представления.
источник
Используйте платформу управляемой расширяемости .
В общем, вам нужно иметь статический класс и использовать Factory Pattern, чтобы предоставить вам глобальный контейнер (кэшированный, natch).
Что касается того, как вводить модели представления, вы вводите их так же, как и все остальное. Создайте конструктор импорта (или поместите оператор импорта в свойство / поле) в коде программной части XAML-файла и скажите ему импортировать модель представления. Затем связать ваш
Window
«SDataContext
к этому свойству. Ваши корневые объекты, которые вы фактически сами извлекаете из контейнера, обычно являются составнымиWindow
объектами. Просто добавьте интерфейсы в классы окон и экспортируйте их, затем возьмите из каталога, как указано выше (в App.xaml.cs ... это файл начальной загрузки WPF).источник
new
.Я бы предложил использовать ViewModel - первый подход https://github.com/Caliburn-Micro/Caliburn.Micro
см. https://caliburnmicro.codeplex.com/wikipage?title=All%20About%20Conventions
использовать
Castle Windsor
как контейнер IOC.Все о соглашениях
Одна из основных особенностей Caliburn.Micro проявляется в его способности устранять необходимость в стандартном коде, действуя в соответствии с рядом соглашений. Некоторые люди любят условности, а некоторые их ненавидят. Вот почему соглашения CM полностью настраиваются и даже могут быть полностью отключены при нежелании. Если вы собираетесь использовать соглашения и поскольку они включены по умолчанию, полезно знать, что это за соглашения и как они работают. Это тема данной статьи. Разрешение просмотра (ViewModel-First)
Основы
Первое соглашение, с которым вы, вероятно, столкнетесь при использовании CM, связано с разрешением просмотра. Это соглашение влияет на все области приложения ViewModel-First. В ViewModel-First у нас есть существующая ViewModel, которую нам нужно отобразить на экране. Для этого CM использует простой шаблон именования, чтобы найти UserControl1, который он должен привязать к ViewModel и отобразить. Итак, что это за образец? Давайте просто взглянем на ViewLocator.LocateForModelType, чтобы узнать:
Давайте сначала проигнорируем переменную «context». Чтобы получить представление, мы предполагаем, что вы используете текст «ViewModel» в именах своих виртуальных машин, поэтому мы просто меняем его на «View» везде, где мы его находим, удаляя слово «Model». В результате меняются как имена типов, так и пространства имен. Таким образом, ViewModels.CustomerViewModel станет Views.CustomerView. Или, если вы организуете свое приложение по функциям: CustomerManagement.CustomerViewModel становится CustomerManagement.CustomerView. Надеюсь, это довольно просто. Получив имя, мы ищем типы с этим именем. Мы ищем любую сборку, которую вы предоставили CM как доступную для поиска через AssemblySource.Instance.2 Если мы находим тип, мы создаем экземпляр (или получаем его из контейнера IoC, если он зарегистрирован) и возвращаем его вызывающей стороне. Если мы не найдем тип,
Теперь вернемся к этому «контекстному» значению. Вот как CM поддерживает несколько представлений по одной и той же ViewModel. Если предоставляется контекст (обычно строка или перечисление), мы выполняем дальнейшее преобразование имени на основе этого значения. Это преобразование фактически предполагает, что у вас есть папка (пространство имен) для различных представлений, путем удаления слова «View» в конце и добавления вместо этого контекста. Итак, с учетом контекста «Master» наша ViewModels.CustomerViewModel станет Views.Customer.Master.
источник
Удалите URI запуска из вашего app.xaml.
App.xaml.cs
Теперь вы можете использовать свой класс IoC для создания экземпляров.
MainWindowView.xaml.cs
источник
GetInstance
дляresolve
внешнего файла app.xaml.cs, вы теряете смысл DI. Кроме того, упоминание представления xaml в выделенном коде представления выглядит запутанным. Просто вызовите представление на чистом C # и сделайте это с контейнером.