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

Dependency Injection to Page ViewModels in WPF

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

我的结构是这样的

MainWindow
|---BalanceIntegrationPage
   |---BalanceIntegrationViewModel

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

DataContext = new ScaleIntegrationViewModel();  

无法在 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();

        }

我的 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";
        }

我也尝试使用描述的模式 here

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

有什么建议吗??

编辑:

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

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

依赖注入意味着你注入依赖,但不要自己构造它们。

在您的示例中,您在页面内手动构建视图模型。这不是 DI。您必须将视图模型的实例注入页面。

您没有向我们展示如何将页面注入主页面 window。我不确定使用 DI 将页面注入 window 是一个好的决定。这是 UI,你可以在没有 DI 的情况下描述所有内容(通过使用数据模板和纯 XAML)。

无论如何,要将您的视图模型注入到页面中,只需在您的页面中引入一个构造函数参数即可:

public ScaleInterfacePage(ScaleIntegrationViewModel vm)
{
    InitializeComponent();
    DataContext = vm;
}

就是这样,你已经准备好了。

您缺少某些依赖项的配置。根据您发布的代码,您错过了配置 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();

}

此外,如前所述,您还必须将视图模型注入 MainWindow。这是依赖图开始的地方,应用程序的根:

partial class MainWindow : Window
{
  public MainWindow(ScaleIntegrationViewModel viewModel)
  {
    this.DataContext = viewModel;
  }
}

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

partial class MainWindow : Window
{
  public MainWindow(IScaleIntegrationViewModel viewModel)
  {
    this.DataContext = viewModel;
  }
}

像页面这样的控件应该通过DataTemplate生成,而不是直接在XAML中实例化。您需要做的就是注入页面视图模型,例如进入另一个视图模型。将它们绑定到 ContentPresenter 并定义一个针对页面视图模型类型的隐式 DataTemplate。该模板包含实际页面。看到这个 .

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