【问题标题】:Dependency Injection to Page ViewModels in WPFWPF中页面视图模型的依赖注入
【发布时间】:2020-03-21 21:58:02
【问题描述】:

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

我的结构是这样的

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 并在需要时重新使用它们。

【问题讨论】:

  • 您手动实例化您的视图模型并且不为构造函数参数提供任何值,因此使用默认的null 值。你到底有什么不清楚的地方?
  • @dymanoid 那么我将如何设置它以使用 DI 呢?我根据我从您的评论中尝试的内容编辑了这个问题。有什么建议吗?
  • 你真的应该使用 MVVM 框架,大多数都内置了 DI。
  • @mxmissile 依赖注入是 .NET Core 3.1 的一个特性,所以这应该可以在本地完成。如果我想使用框架或其他东西,我会回到使用 UnityContainers 并以这种方式进行 DI,就像我在完整的框架应用程序中所做的那样。目标是在这个项目中使用本机 DI。不过谢谢!
  • “意味着你必须注册 Views 和 ViewModels” - 这取决于你使用的框架,以及你是否按照约定依赖依赖映射而不是明确定义它.依赖约定方法只是意味着魔术在后台发生。

标签: c# wpf mvvm dependency-injection .net-core-3.1


【解决方案1】:

您缺少某些依赖项的配置。从您发布的代码中,您错过了配置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,它以页面视图模型的类型为目标。此模板包含实际页面。看到这个example

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

【讨论】:

  • 这有助于更有意义,至少比其他人更有意义,谢谢!我没有错过数据上下文的配置,它们的设置方式与 IScale 界面完全相同,我只是在处理缩放部分时将它们注释掉。我想现在我只是将带有可选参数的视图模型保留下来,这样我就可以在测试时模拟我需要的接口。我使用 DI 的主要原因是让测试更容易一些。这样我就不必改变我的整个视图范式。再次感谢!
【解决方案2】:

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

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

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

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

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

就是这样,一切准备就绪。

【讨论】:

  • 我的页面没有注入到窗口中。它在 XAML 中用标签声明 ``` ``` 如果我删除无参数页面构造函数,它将停止加载。我想这就是我断开连接的地方。在 ASP.Net 中,似乎任何我注册为服务的东西都可以注入到构造函数中。我认为它在 WPF 中似乎不像那样工作?似乎需要一个复杂的依赖链,我调用主窗口,然后将页面注入其中,然后将视图模型注入其中,依此类推。
  • @JoeK,不,这不是 DI 的工作方式 - 没有必要将依赖项从顶部转发到底部。由于您没有展示整个设置,因此我无法指出您的问题到底出在哪里。
  • @dyamanoid 你是什么意思我不知道整个设置?有没有我在回复中没有解决的问题?
猜你喜欢
  • 2023-01-03
  • 2018-12-24
  • 2015-07-16
  • 2018-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多