如何使用具有简单注入器依赖项的 WPF 控件

How to use WPF controls with Simple Injector dependencies

我想在必须将资源注入 GUI 控件的情况下使用依赖注入。因为那可能是错误的地方,我有一些理由在这里而不是在视图模型中这样做(例如,我需要 Window 句柄等)。

构造函数参数注入似乎是首选方式。大多数人都知道 WPF 控件必须具有无参数构造函数,否则 XAML 将不起作用,对于当前情况,我喜欢保留我的 XAML,因为它包含一些名称注册和绑定。

所以:我如何在 WPF+XAML 场景中使用构造函数 DI 以及(如果可能的话,在简单注入器的情况下)?

是否存在标记扩展,或者 XAML 解析器是否可以实现容器感知并接受具有参数的构造函数作为控件?

方案示例:

<Grid>
 <gg:WhateverResourceNeedingViewer ItemSource={Binding Items}/>
</Grid>

并且:

public class WhateverResourceNeedingViewer : ItemsControl
{
   public WhateverResourceNeedingViewer(Dep1 d, DepResource d2)
   {
   ...
   }
...
}

最好的做法是不仅使用 SOLID 设计原则构建视图模型,而且在视图中也这样做。用户控件的使用可以帮助您。

如果技术上可行,您建议的方法的缺点是该设计将违反 SRP and OCP

SRP,因为您的用户控件需要的所有依赖项都必须注入到使用 window/view 中,而此视图可能不需要(所有)这些依赖项。

还有 OCP,因为每次从用户控件中添加或删除依赖项时,您还需要从消费 window/view.

中添加或删除它

使用用户控件,您可以像编写其他 class 一样编写视图,例如服务、命令和查询处理程序等。在依赖注入方面,编写应用程序的地方是composition root

WPF 中的

ContentControls 都是关于 'composing' 您对应用程序中其他 'content' 的看法。

Caliburn Micro 这样的 MVVM 工具通常使用内容控件来注入一个用户控件视图(读作:xaml 没有代码隐藏)和它自己的视图模型。事实上,当使用 MVVM 时,您将从用户控件 class 构建应用程序中的所有视图,作为最佳实践。

这可能看起来像这样:

public interface IViewModel<T> { }

public class MainViewModel : IViewModel<Someclass>
{
    public MainViewModel(IViewModel<SomeOtherClass> userControlViewModel)
    {
        this.UserControlViewModel = userControlViewModel;
    }

    public IViewModel<SomeOtherClass> UserControlViewModel { get; private set; }
}

public class UserControlViewModel : IViewModel<SomeOtherClass>
{
    private readonly ISomeService someDependency;

    public UserControlViewModel(ISomeService someDependency)
    {
        this.someDependency = someDependency;
    }
}

MainView 的 XAML:

// MainView
<UserControl x:Class="WpfUserControlTest.MainView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
        <ContentControl Name="UserControlViewModel" />
    </Grid>
</UserControl>

// UserControl View
<UserControl x:Class="WpfUserControlTest.UserControlView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
        <TextBlock Text="SomeInformation"/>
    </Grid>
</UserControl>

结果将是 MainView 显示在 window 中,其中 window 的 DataContext 设置为 MainViewModel。内容控件将填充 UserControlView,其 DataContext 设置为 UserControlViewModel class。这是自动发生的,因为 MVVM 工具将使用 Convention over configuration.

将视图模型绑定到相应的视图

如果您不使用 MVVM 工具,而是直接将依赖项注入 window class 的代码中,您只需遵循相同的模式即可。在您的视图中使用 ContentControl,就像上面的示例一样,并在 window 的构造函数中注入 UserControl(根据需要使用包含参数的构造函数)。然后只需将 ContentControl 的 Content 属性 设置为您的 UserControl 的注入实例。

看起来像:

public partial class MainWindow : Window
{
    public MainWindow(YourUserControl userControl)
    {
        InitializeComponent();
        // assuming you have a contentcontrol named 'UserControlViewModel' 
        this.UserControlViewModel.Content = userControl;
    }

    // other code...
}

这可能被认为是一种反模式 - 在许多层面上 -(有关详细信息,请参阅 Ric 的回答)但如果你只是想让它工作,希望务实并有一个简单的用例,我会建议静态 DI 解析器。这对于遗留组件或像这样受底层架构限制的情况非常方便。

/// <summary>
///     Provides static resolution of Simple Injector instances.
/// </summary>
public class ServiceResolver
{
    private Container Container { get; }
    private static ServiceResolver Resolver { get; set; }

    public ServiceResolver(Container container)
    {
        Container = container;
        Resolver = this;
    }

    public static T GetInstance<T>()
    {
        if (Resolver == null) throw new InvalidOperationException($"{nameof(ServiceResolver)} must be constructed prior to use.");
        return (T) Resolver.Container.GetInstance(typeof(T));
    }
}

用法,根据您的示例:

public WhateverResourceNeedingViewer()
{
    InitializeComponent();

    // Resolve view model statically to fulfill no-arg constructor for controls
    DataContext = ServiceResolver.GetInstance<DepResource>();
}