WPF 中页面视图模型的依赖注入

Joe*_*e K 6 c# wpf dependency-injection mvvm .net-core-3.1

我正在使用 .NET core 3.1 创建一个 WPF 应用程序,我过去开发过 ASP.Net 应用程序,我很高兴在 WPF 中使用它。我做了一些搜索,发现 WPF 中的 DI 并不像 ASP.Net 中那样简单,这意味着您必须注册视图和视图模型。

我的结构是这样的

MainWindow
|---BalanceIntegrationPage
   |---BalanceIntegrationViewModel
Run Code Online (Sandbox Code Playgroud)

一切都在 XAML 中处理,MainWindow.xaml.cs 仅生成代码,而 BalanceIntegrationPage.xaml.cs 在构造函数中添加了一行

DataContext = new ScaleIntegrationViewModel();  
Run Code Online (Sandbox Code Playgroud)

这无法在 xaml 中处理,因为 DI 需要构造函数中的参数。

这是我的 app.xaml.cs:

protected override async void OnStartup(StartupEventArgs startupEventArgs)
        {
            base.OnStartup(startupEventArgs);
            ServiceCollection services = new ServiceCollection();
            services.AddScoped<MainWindow>();
            services.AddScoped<ScaleInterfacePage>();
            services.AddScoped<ScaleIntegrationViewModel>();
            services.AddScoped<IScale>(provider => new Scale("1234"));

            ServiceProvider serviceProvider = services.BuildServiceProvider();
            MainWindow mainWindow = serviceProvider.GetService<MainWindow>();
            mainWindow.Show();

        }
Run Code Online (Sandbox Code Playgroud)

我的 ScaleIntegrationViewModel 看起来像:

public ScaleIntegrationViewModel(IJMDataIntegration jmContext = null, IBalanceIntegrationContext localContext = null, IScale scale = null)
        {
            _jmContext = jmContext ?? new JMDataIntegration();
            _localContext = localContext ?? new BalanceIntegrationContext();
            _scale = scale ?? new Scale("1234");
            //JK read from config
            _commPort = "1234";
        }
Run Code Online (Sandbox Code Playgroud)

我还尝试使用此处描述的模式

当我单步执行代码时,ViewModel 构造函数中的 IScale 对象始终为 null。

有什么建议么??

编辑:

根据评论,我删除了页面构造函数中的 ViewModel 调用,而是将其分配到 .xaml 上,这迫使我创建一个默认的无参数构造函数,该构造函数随后会破坏 DI。

几乎开始看起来我需要将服务注入到 MainWindow ctor 中,然后将它们传递给我从那里调用的所有内容。这对我来说毫无意义,因为到那时,我可能会扔掉 DI,并在需要时重新安装它们。

Bio*_*ode 6

您缺少某些依赖项的配置。从您发布的代码中,您错过了配置IJMDataIntegrationIBalanceIntegrationContext

protected override async void OnStartup(StartupEventArgs startupEventArgs)
{
  base.OnStartup(startupEventArgs);
  ServiceCollection services = new ServiceCollection();
  services.AddScoped<MainWindow>();
  services.AddScoped<ScaleInterfacePage>();
  services.AddScoped<IJMDataIntegration, JMDataIntegration>();
  services.AddScoped<IBalanceIntegrationContext, BalanceIntegrationContext>();
  services.AddScoped<IScale>(provider => new Scale("1234"));
  services.AddScoped<ScaleIntegrationViewModel>();

  ServiceProvider serviceProvider = services.BuildServiceProvider();
  MainWindow mainWindow = serviceProvider.GetService<MainWindow>();
  mainWindow.Show();

}
Run Code Online (Sandbox Code Playgroud)

另外,正如已经提到的,您还必须将视图模型注入到中MainWindow。这是依赖关系图开始的地方,即应用程序的根:

partial class MainWindow : Window
{
  public MainWindow(ScaleIntegrationViewModel viewModel)
  {
    this.DataContext = viewModel;
  }
}
Run Code Online (Sandbox Code Playgroud)

为了启用依赖注入的全部功能(并使模拟更容易),您应该在整个应用程序中使用依赖反转。这意味着您应该只依赖于接口,因此构造函数中只具有接口类型:

partial class MainWindow : Window
{
  public MainWindow(IScaleIntegrationViewModel viewModel)
  {
    this.DataContext = viewModel;
  }
}
Run Code Online (Sandbox Code Playgroud)

像页面这样的控件应该通过 XAML 生成DataTemplate,而不是直接在 XAML 中实例化。您需要做的就是将页面视图模型注入到另一个视图模型中。将它们绑定到 aContentPresenter并定义一个DataTemplate以页面视图模型类型为目标的隐式。该模板包含实际页面。请参阅此示例

如果您需要更多详细信息,请搜索视图模型优先模式。基本上,视图可以定义为数据模板并与视图模型类型相关联。数据模板可以定义为资源,也可以在将显示视图模型的控件内内嵌定义。控件的内容是视图模型实例,数据模板用于直观地表示它。该技术是首先实例化视图模型,然后创建视图的情况的示例。
这是首选方式,尤其是与依赖注入结合使用时。