我想在必须将资源注入 GUI 控件的场景中使用依赖注入。因为这可能是错误的地方,所以我有一些理由在这里而不是在视图模型中执行此操作(例如,我需要窗口句柄等)。
构造函数参数注入似乎是首选方式。大多数人都知道 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 和 OCP。
SRP,因为您的用户控件需要的所有依赖项都必须注入到使用窗口/视图中,而该视图可能不需要(全部)这些依赖项。
还有 OCP,因为每次从用户控件中添加或删除依赖项时,您还需要从消费窗口/视图中添加或删除它。
使用用户控件,您可以像编写其他类(例如服务、命令和查询处理程序等)一样编写视图。当涉及到依赖项注入时,编写应用程序的位置是 composition root
WPF 中的ContentControls 都是关于从应用程序中的其他“内容”“组合”视图。
像 Caliburn Micro 这样的 MVVM 工具通常使用内容控件来注入用户控件视图(阅读:没有代码隐藏的 xaml)及其自己的视图模型。事实上,作为最佳实践,在使用 MVVM 时,您可以从 usercontrols 类构建应用程序中的所有视图。
这可能看起来像这样:
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
显示在窗口中,其中该窗口的 DataContext
设置为 MainViewModel
。内容控件将填充 UserControlView
,并将其 DataContext
设置为 UserControlViewModel
类。这种情况会自动发生,因为 MVVM 工具将使用“约定优于配置”将视图模型绑定到相应的视图。
如果您不使用 MVVM 工具,而是直接将依赖项注入到窗口类后面的代码中,您只需遵循相同的模式即可。在您的视图中使用 ContentControl,就像上面的示例一样,并在窗口的构造函数中注入 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...
}
/// <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>();
}
App.xaml.cs
文件中,我完成了所有依赖项构建工作并公开了
IServiceProvider
的单个静态属性。public partial class App : Application
{
private IServiceProvider? _serviceProvider;
private readonly CancellationTokenSource _cancellationTokenSource = new();
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var services = new ServiceCollection();
_serviceProvider = ConfigureServices(services);
var window = _serviceProvider.GetRequiredService<MainWindow>();
window?.Show();
}
public static IServiceProvider ServiceProvider => ((App)Current)._serviceProvider!;
private ServiceProvider ConfigureServices(IServiceCollection services) => ...
}
然后在我的用户控件或其他视图中我可以根据需要直接访问所需的依赖项,而无需直接注入它们。
/// <summary>
/// Interaction logic for ProxySettingsControl.xaml
/// </summary>
public partial class ProxySettingsControl : UserControl
{
public ProxySettingsControl()
{
InitializeComponent();
var sp = App.ServiceProvider;
var proxySettings = sp.GetService<ProxySettingsViewModel>();
DataContext = proxySettings;
}
}