WPF 应用程序是不是可以进行依赖注入?

Posted

技术标签:

【中文标题】WPF 应用程序是不是可以进行依赖注入?【英文标题】:Is Dependency Injection possible with a WPF application?WPF 应用程序是否可以进行依赖注入? 【发布时间】:2010-09-22 01:05:05 【问题描述】:

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

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

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

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

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

[编辑] - 有人建议使用 Prism,但即使评估 Prism 似乎也是一项巨大的投资。我希望有更小的东西。

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

【问题讨论】:

Glenn Block 通过播客和博客文章对 Prism 做了一些介绍 - 我认为评估它的投资并不多。 Scott,你看到的“大”投资是什么?你是在猜这个,还是你真的看过它? Prism 的设计方式是您可以只使用您需要的部件,没有大的承诺。我很乐意与您离线讨论此事。 【参考方案1】:

这实际上很容易做到。正如 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。

【讨论】:

这很有帮助 - 我也更喜欢第二种方法,在哪里可以找到执行此操作的代码示例?【参考方案2】:

我认为您已经解决了这个问题。控件需要注入到其父级中,而不是通过 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 示例)应该让您了解什么是可能的。

【讨论】:

【参考方案3】:

我们遇到了类似的问题。我们期待在 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 内部是设计表面,但 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 创建插件的能力。

【讨论】:

此视频提供了一种使用 M-V-VM 并获得 deginer/blend 集成的好方法 - 跳到最后 10-20 分钟了解这些详细信息lab49.com/files/videos/Jason%20Dolinger%20MVVM.wmv【参考方案4】:

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

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

【讨论】:

【参考方案5】:

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

【讨论】:

Caliburn is a legacy framework. All development is focused on Caliburn.Micro. | Caliburn.Micro - No longer under active maitenance | Announcement | GitHub discussion. 没问题?(PS:我在12年前写了上面的答案.....)【参考方案6】:

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"

在您看来,您可以拥有一个属性 getter,将您的 DataContext 转换为您期望的类型(只是为了更容易在代码隐藏中使用)。

private IMyViewModel ViewModel  get  return (IMyViewModel) DataContext;  

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

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

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

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

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

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

【讨论】:

【参考方案7】:

我认为你必须先决定 View First 或 Viewmodel First 然后给出其他答案,它可以决定.. 有几个开源框架做同样的事情。我在首先采用 ViewModel 的地方使用 Caliburn,这是一种非常好的方法

【讨论】:

同意。当我写这个问题时,首先查看是我的隐含假设。【参考方案8】:

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

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

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

【讨论】:

请不要发布只是公开您的代码的答案。在此处回答问题(使用代码 sn-p)并链接到您的代码(明确标记为此类)作为参考。

以上是关于WPF 应用程序是不是可以进行依赖注入?的主要内容,如果未能解决你的问题,请参考以下文章

WPF PRISM开发入门二(Unity依赖注入容器使用)

如何在 WPF/MVVM 应用程序中处理依赖注入

如何为 .NET Core 3.0 中 WPF 配置依赖注入 ?

将 DbContext 与依赖注入一起使用

WPF 高级篇 MVVM (MVVMlight) 依赖注入使用Messagebox

PHP实现依赖注入