Возможна ли инъекция зависимостей с помощью приложения WPF?

Я хочу начать использовать инъекцию зависимостей в моем приложении WPF, в основном для лучшей проверки работоспособности. Мое приложение в основном построено по шаблону M-V-VM. Я смотрю autofac для моего контейнера IoC, но я не думаю, что это слишком важно для обсуждения.

Включение службы в начальное окно кажется простым, поскольку я могу создать контейнер и разрешить его в App.xaml.cs.

Чего я боюсь, так это то, как я могу использовать ViewModels и сервисы DI в пользовательских элементах управления? Пользовательские элементы управления создаются с помощью разметки XAML, поэтому нет возможности Resolve() их.

Лучшее, что я могу придумать, - разместить контейнер в Синглтоне, а пользовательские элементы управления разрешат их ViewModels из глобального контейнера. В лучшем случае это похоже на решение на полпути, поскольку все еще требуется, чтобы мои компоненты зависели от ServiceLocator.

Возможна ли полная IoC с помощью WPF?

[править] - Призма была предложена, но даже оценка Призма кажется большой инвестицией, я надеюсь на что-то меньшее

[edit] здесь фрагмент кода, где я остановлен

    //setup IoC container (in app.xaml.cs)
    var builder = new ContainerBuilder();
    builder.Register<NewsSource>().As<INewsSource>();
    builder.Register<AViewModel>().FactoryScoped();
    var container = builder.Build();

    // in user control ctor -
    // this doesn't work, where do I get the container from
    VM = container.Resolve<AViewModel>();

    // in app.xaml.cs
    // this compiles, but I can't use this uc, 
    //as the one I want in created via xaml in the primary window
    SomeUserControl uc = new SomeUserControl();
    uc.VM = container.Resolve<AViewModel>();

Ответ 1

На самом деле это очень легко сделать. У нас есть примеры этого в Призме, как упоминал джедиджа. Вы можете либо вставить ViewModel с помощью View или View, который будет введен с помощью ViewModel. В Prism StockTraderRI вы увидите, что мы вводим View в ViewModel. По сути, происходит то, что интерфейс View (и View) имеет свойство Model. Это свойство реализовано в коде, чтобы установить DataContext в значение, например: this.DataContext = value;. В конструкторе ViewModel представление вводится. Затем он устанавливает View.Model = this;, который передаст себя как DataContext.

Вы также можете легко сделать обратное и включить ViewModel в представление. Я действительно предпочитаю это, потому что это означает, что ViewModel больше не имеет обратной ссылки на представление вообще. Это означает, что при модульном тестировании ViewModel у вас нет представления даже для Mock. Кроме того, он делает код чище, в том, что в конструкторе View он просто устанавливает DataContext в ViewModel, который был введен.

Я немного об этом расскажу в видеозаписи раздельных презентаций, которые Джереми Миллер и я дали в Kaizenconf. Первая часть которой находится здесь http://www.vimeo.com/2189854.

Надеюсь, это поможет, Гленн

Ответ 2

Я думаю, вы попали в проблему. Элементы управления нужно вводить в родительский, а не декларативно, через XAML.

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

Другой "альтернативный" - иметь глобальный статический контейнер, вызываемый из конструктора управления, или нечто подобное. Существует общая схема, в которой объявляются два конструктора: один со списком параметров для вставки конструктора, а другой без параметров, которые делегируют:

// For WPF
public Foo() : this(Global.Container.Resolve<IBar>()) {}

// For the rest of the world
public Foo(IBar bar) { .. }

Я бы назвал это антипаттерном, но тот факт, что некоторые рамки не оставляют другого выбора.

Я даже не являюсь экспертом в WPF, поэтому я ожидаю, что здесь будет здоровое служение downmod:), но надеюсь, что это поможет. Группа Autofac (связанная с главной страницы) может быть другим местом, чтобы задать этот вопрос. Примеры приложений Prism или MEF (включая некоторые примеры WPF) должны дать вам представление о том, что возможно.

Ответ 3

Вы должны взглянуть на Prism из команды p & p. Вот сайт на Codeplex

Ответ 4

Хамм, Мы сталкиваемся с аналогичной проблемой, которую мы ожидаем от решения, которое обеспечит поддержку времени разработки в Expression Blend 2.0 (Strong Type). Плюс мы с нетерпением ждем решения, чтобы иметь образец Mock + Auto-Generated data, доступный в Expression Blend.

Конечно, мы также хотим, чтобы все эти вещи работали с использованием шаблона IOC.

Пол Стовелл как интересная статья, чтобы начать с: http://www.paulstovell.com/blog/wpf-dependency-injection-in-xaml

Итак, я попробую пару вещей, чтобы добавить более ценную поддержку времени разработки для привязки и издевательства объекта во время разработки, прямо сейчас. У меня большая часть проблемы связана с получением строго типизированного соединения между представлением (кодом) и ModelView (Xaml), я попробовал пару сценариев:

1.) Решение 1. Использование Generic для создания представления

public class MyDotNetcomponent&lt;T&gt; : SomeDotNetcomponent
{
    // Inversion of Control Loader…
    // Next step add the Inversion of control manager plus
    // some MockObject feature to work under design time
    public T View {Get;}
}

Это решение не работает, поскольку Blend не поддерживает Generic inside - это поверхность проектирования, но Xaml действительно имеет некоторые, хорошо работает во время выполнения, но не в дизайне;

2.) Решение 2: ObjectDataProvider

<ObjectDataProvider ObjectType="{x:Type CP:IFooView}" />
<!-- Work in Blend -->
<!—- IOC Issue: we need to use a concrete type and/or static Method there no way to achive a load on demande feature in a easy way -->

3.) Решение 3: Наследовать ObjectDataProvider

<CWD:ServiceObjectDataProvider ObjectType="{x:Type CP:IFooView}" />
<!-- Cannot inherit from ObjectDataProvider to achive the right behavior everything is private-->

4.) Решение 4. Создайте макет ObjectDataProvider с нуля для задания

<CWD:ServiceObjectDataProvider ObjectType="{x:Type CP:IFooView }" />
<!-- Not working in Blend, quite obvious-->

5.) Решение 5: Создайте расширение разметки (Paul Stovell)

<CWM:ServiceMarkup MetaView="{x:Type CP:IFooView}"/>
<!-- Not working in Blend -->

Просто, чтобы очистить одну точку, когда я сказал, что "не работает в blend", я имею в виду, что диалоговое окно Binding нельзя использовать, и дизайнеру нужно вручную переписать XAML.

Наш следующий шаг, вероятно, займет время, чтобы оценить возможность создания плагина для Expression Blend.

Ответ 5

Вы должны взглянуть на Caliburn - это простая инфраструктура MVC WPF/Silverlight MVC с поддержкой полного DI. Он выглядит действительно круто, и он позволяет вам использовать любой контейнер IoC, который вы хотите. Есть несколько примеров в документации wiki

Ответ 6

Да, мы делаем это все время. Вы можете "ввести" ваш ViewModel в DataContext элемента управления.

Я на самом деле считаю WPF еще более удобным для использования с DI. Даже объекты и свойства зависимостей работают с ним без проблем.

Ответ 7

В блоке Glen (см. выше) упоминается, что общий подход заключается в разработке решения MVVM для использования DataContext как места, где вы можете "разрешить" вашу модель просмотра в представлении. Затем вы можете использовать расширения дизайна из выражения blend 2008 (обратите внимание, что вам не нужно использовать средства разработки blend design, чтобы воспользоваться этим). Например:

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" 
d:DataContext="{d:DesignInstance Type=local:MyViewModelMock, IsDesignTimeCreatable=True}"

По вашему мнению, у вас может быть свойство getter, которое отличает ваш DataContext к тому типу, который вы ожидаете (просто чтобы его было проще использовать в коде).

private IMyViewModel ViewModel { get { return (IMyViewModel) DataContext; } }

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

В общем, вы не должны разрешать вещи из контейнера повсюду в своем решении. На самом деле считается плохой практикой передавать ваш контейнер в каждом конструкторе или делать его доступным по всему миру. (Вы должны найти обсуждения о том, почему стратегии "Локатор услуг" представляют собой "Анти-шаблон" ).

Создайте конструктор открытого представления с явными зависимостями, которые может разрешить контейнер (например, Prism Unity или MEF).

При необходимости вы также можете создать конструктор по умолчанию внутри, чтобы создать макет вашей модели представления (или, в действительности, такой). Это защищает от непреднамеренного использования этого конструктора конструктора извне (в вашей "оболочке" или где угодно). Ваши тестовые проекты также могут использовать такие конструкторы, используя " InternalsVisibleToAttribute" в " AssemblyInfo". Но, конечно, это обычно не требуется, так как вы можете в любом случае вводить свои издевательства с использованием конструкторов полной зависимости, и потому, что большинство ваших тестов должно быть сосредоточено на ViewModel. Любой код в View в идеале должен быть довольно тривиальным. (Если ваш просмотр требует много тестирования, то вы можете спросить себя, почему!)

Глен также упоминает, что вы можете вводить Просмотры в Модели просмотра или Просмотр моделей в представлениях. Я предпочитаю последнее, потому что есть отличные методы для развязывания всего (использование декларативного привязки, командования, агрегирования событий, шаблонов посредника и т.д.). Модель просмотра - это то, где весь тяжелый подъем будет сделан для организации основной бизнес-логики. Если все необходимые "привязывающие" точки предоставлены View Model, ему не нужно знать НИЧЕГО о Просмотр ( который в основном может быть подключен к нему декларативно в XAML).

Если мы сделаем агностик View Model источником взаимодействия с пользователем, это значительно упростит тест (желательно сначала). А это также означает, что вы можете легко подключить ЛЮБОЙ просмотр (WPF, Silverlight, ASP.NET, консоль и т.д.). Фактически, чтобы обеспечить надлежащую развязку, мы можем спросить себя, может ли архитектура MVM (Model-ViewModel) работать в контексте, скажем, службы Workflow. Когда вы перестанете думать об этом, большинство ваших модульных тестов, вероятно, будут разработаны на этой основе.

Ответ 8

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

Ответ 9

Я написал очень легкую структуру, где ViewModel разрешен во время выполнения с использованием IoC (Unity) в качестве расширения разметки.

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

В любом случае, я не думаю, что вам нужен свободный XAML в вашем случае, но если вы посмотрите на код (http://xtrememvvm.codeplex.com), может оказаться, что вы можете использовать часть кода для решения своих собственных проблем с помощью инъекции View Models and Services.