37

我想开始在我的 WPF 应用程序中使用依赖注入,主要是为了更好的单元可测试性。我的应用程序主要是按照 MV-VM 模式构建的。我正在为我的 IoC 容器寻找Autofac,但我认为这对这个讨论没有太大影响。

将服务注入启动窗口似乎很简单,因为我可以在 App.xaml.cs 中创建容器并从中解析。

我正在苦苦挣扎的是如何将 DI ViewModels 和服务转换为用户控件?用户控件是通过 XAML 标记实例化的,因此它们没有机会Resolve()

我能想到的最好的办法是将容器放在单例中,并让用户控件从全局容器中解析他们的 ViewModel。充其量,这感觉像是一个半途而废的解决方案,因为它仍然需要我的组件依赖于 ServiceLocator。

使用 WPF 可以实现完整的 IoC 吗?

[编辑] - 有人建议使用 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>();
4

8 回答 8

17

这实际上很容易做到。正如 jedidja 所提到的,我们在 Prism 中有这样的例子。您可以让 ViewModel 与 View 一起注入,也可以让 View 与 ViewModel 一起注入。在 Prism StockTraderRI 中,您将看到我们将 View 注入 ViewModel。本质上,发生的事情是 View(和 View 接口)具有 Model 属性。该属性在代码隐藏中实现,以将 DataContext 设置为该值,例如:this.DataContext = value;. 在 ViewModel 的构造函数中,视图被注入。然后它设置View.Model = this;哪个将自己作为 DataContext 传递。

您也可以轻松地执行相反的操作,并将 ViewModel 注入到 View 中。我实际上更喜欢这个,因为这意味着 ViewModel 不再有任何对视图的反向引用。这意味着在对 ViewModel 进行单元测试时,您甚至没有 Mock 的视图。此外,它使代码更清晰,因为在 View 的构造函数中,它只是将 DataContext 设置为注入的 ViewModel。

我在 Jeremy Miller 和我在 Kaizenconf 上发表的分离演示模式演讲的视频录制中更多地谈到了这一点。第一部分可以在这里找到https://vimeo.com/2189854

于 2008-11-15T07:46:06.637 回答
10

我想你已经解决了这个问题。控件需要注入到其父级中,而不是通过 XAML 以声明方式创建。

为了使 DI 工作,DI 容器应该创建接受依赖项的类。这意味着父级在设计时不会有子控件的任何实例,并且在设计器中看起来像一个外壳。这可能是推荐的方法。

另一个“替代方案”是从控件的构造函数或类似的东西中调用一个全局静态容器。有一种常见的模式,其中声明了两个构造函数,一个带有用于构造函数注入的参数列表,另一个没有委托的参数:

// For WPF
public Foo() : this(Global.Container.Resolve&lt;IBar&gt;()) {}

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

我几乎将其称为反模式,但事实上某些框架别无选择。

我什至不是 WPF 专家的一半,所以我期待这里有一个健康的 downmod 服务:) 但希望这会有所帮助。Autofac 组(从主页链接)可能是另一个提出这个问题的地方。Prism 或 MEF 示例应用程序(其中包括一些 WPF 示例)应该让您了解什么是可能的。

于 2008-11-22T17:43:30.830 回答
4

我们遇到了类似的问题。我们期待在 Expression Blend 2.0(强类型)下提供设计时支持的解决方案。此外,我们期待在 Expression Blend 下提供一些 Mock+Auto-Generated 数据样本的解决方案。

当然,我们也希望使用 IOC 模式让所有这些工作正常工作。

Paul Stovell 作为一篇有趣的文章开始: http: //www.paulstovell.com/blog/wpf-dependency-injection-in-xaml

所以我尝试了一些事情来在设计时为绑定和模拟对象添加更有价值的设计时支持,现在我的大部分问题都与在视图(代码)和 ModelView 之间建立强类型连接有关( Xaml),我尝试了几个场景:

解决方案 1:使用 Generic 创建视图

public class MyDotNetcomponent<T> : 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 is design surface,但 Xaml 确实有一些,在运行时工作得很好,但在设计时没有;

解决方案 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:继承 ObjectDataProvider

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

解决方案 4:从头开始创建一个模拟 ObjectDataProvider 到作业

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

解决方案 5:创建标记扩展 (Paul Stovell)

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

只是为了清除一点。当我说“不在 blend 中工作”时,我的意思是 Binding 对话框不可用,设计人员需要自己手写 XAML。

我们的下一步可能是花时间评估为 Expression Blend 创建插件的能力。

于 2008-11-13T01:28:29.637 回答
3

你应该看看Caliburn——它是一个简单的 WPF/Silverlight MVC 框架,支持完整的 DI。它看起来很酷,它可以让你使用任何你想要的 IoC 容器。文档 wiki上有几个示例

于 2008-12-03T12:06:06.513 回答
3

是的,我们一直这样做。您可以“注入”您的 ViewModel 到控件的 DataContext 中。

实际上,我发现 WPF 更容易与 DI 一起使用。甚至依赖对象和属性也可以无缝地使用它。

于 2008-11-12T19:03:44.937 回答
1

Glen Block(见上文)提到一种常见的方法是设计您的MVVM解决方案以使用DataContext作为您可以在视图中“解析”您的视图模型的地方。然后,您可以使用表达式混合 2008 中的设计扩展(请注意,您不需要使用表达式混合设计工具来利用这一点)。例如:

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

不要忘记使用接口,以便您的视图更易于测试,或帮助您注入不同的运行时实现。

通常,您不应该在解决方案中到处从容器中解决问题。在每个构造函数中传递容器或使其全局可访问实际上被认为是不好的做法。(您应该查看有关“服务定位器”策略为何构成“反模式”的讨论)。

创建具有容器(例如 Prism Unity 或 MEF)可以解析的显式依赖关系的公共视图构造函数。

如有必要,您还可以创建一个内部默认构造函数来创建您的视图模型的模拟(或就此而言的真实模型)。这可以防止在外部(在您的“Shell”或任何地方)无意中使用此“设计构造函数”。您的测试项目也可以使用“ AssemblyInfo ”中的“ InternalsVisibleToAttribute ”来使用此类构造函数。但是当然,这通常不是必需的,因为无论如何您都可以使用完整的依赖构造函数来注入模拟,并且因为您的大多数测试首先应该关注ViewModel理想情况下,视图中的任何代码都应该非常简单。(如果您的视图需要大量测试,那么您可能想问自己为什么!)

Glen 还提到您可以将Views 注入到 View Models中,或者将View Models 注入到 Views中。我更喜欢后者,因为有非常好的技术可以解耦一切(使用声明性绑定、命令、事件聚合、中介模式等)。视图模型是协调核心业务逻辑的所有繁重工作。如果View Model提供了所有必要的“绑定”点,那么它真的不需要知道关于View的任何信息(大部分可以在 XAML 中以声明方式连接到它)。

如果我们让 View Model 与用户交互的来源无关,那么测试会更容易(最好是先测试)。这也意味着您可以轻松插入任何视图(WPF、Silverlight、ASP.NET、控制台等)。事实上,为了确保实现适当的解耦,我们可以问自己,“MVM”(模型-视图模型)架构是否可以在工作流服务的上下文中工作。当你停下来想一想时,你的大部分单元测试可能都是在这个前提下设计的。

于 2013-10-29T16:37:29.113 回答
0

我认为你必须先决定 View First 或 Viewmodel First 然后给出另一个答案,它可以决定.. 有几个开源框架也是如此。我在首先采用 ViewModel 的地方使用 Caliburn,它的方法非常好

于 2011-03-14T08:43:26.807 回答
0

我编写了一个非常轻量级的框架,其中 ViewModel 在运行时通过使用 IoC (Unity) 作为标记扩展来解析。

该框架允许在没有代码的情况下编写 XAML,但仍允许您拥有路由命令、数据绑定和事件处理程序。

无论如何,我认为您不需要松散的 XAML,但是如果您查看代码 ( http://xtrememvvm.codeplex.com ),可能会发现您可以使用一些代码来通过注入视图模型和服务来解决您自己的问题。

于 2013-03-07T17:11:28.077 回答