如何使用具有简单注入器依赖项的 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>();
}
我想在必须将资源注入 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>();
}