Возможно ли внедрение зависимости с приложением WPF? - PullRequest
35 голосов
/ 12 ноября 2008

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

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

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

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

Возможен ли полный IoC с WPF?

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

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

    //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>();

Ответы [ 9 ]

16 голосов
/ 15 ноября 2008

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

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

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

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

8 голосов
/ 22 ноября 2008

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

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

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

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

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

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

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

5 голосов
/ 12 ноября 2008

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

3 голосов
/ 03 декабря 2008

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

3 голосов
/ 13 ноября 2008

Хамм, У нас возникла похожая проблема, и мы ожидаем решения, которое обеспечит поддержку времени разработки в Expression Blend 2.0 (Strong Type). Кроме того, мы с нетерпением ждем решения, которое предоставило бы некоторые образцы автоматически созданных данных Mock + в 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 внутри, это поверхность проекта, но 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 -->

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

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

2 голосов
/ 12 ноября 2008

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

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

1 голос
/ 29 октября 2013

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

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}"

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

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

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

В общем, вы не должны разрешать вещи из контейнера повсюду в вашем решении. На самом деле считается плохой практикой передавать свой контейнер в каждом конструкторе или делать его глобально доступным. (Вам следует поискать обсуждения того, почему стратегии «Service Locator» составляют «Anti-Pattern»).

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

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

Глен также упоминает, что вы можете добавить видов в модели видов или видов моделей в виды . Я предпочитаю последнее, потому что есть совершенно хорошие методы для разделения всего (использование декларативного связывания, командования, агрегации событий, шаблонов посредника и т. Д.). Модель View - это то место, где будут сделаны все тяжелые работы для организации основной бизнес-логики. Если View Model предоставляет все необходимые «связующие» точки, ему действительно не нужно знать ANYTHING о View (которое может в основном быть декларативно подключенным к нему в XAML).

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

0 голосов
/ 07 марта 2013

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

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

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

0 голосов
/ 14 марта 2011

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

...