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;
}
就是这样,你已经准备好了。
您缺少某些依赖项的配置。根据您发布的代码,您错过了配置 IJMDataIntegration
和 IBalanceIntegrationContext
:
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
。该模板包含实际页面。看到这个 .
如果您需要更多详细信息,请搜索视图模型优先模式。基本上,视图可以定义为数据模板并与视图模型类型相关联。数据模板可以定义为资源,也可以在将显示视图模型的控件内内联定义。控件的内容是视图模型实例,数据模板用于可视化表示。此技术是先实例化视图模型,然后创建视图的情况的示例。
这是首选方式,尤其是与依赖注入结合使用时。
我正在使用 .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;
}
就是这样,你已经准备好了。
您缺少某些依赖项的配置。根据您发布的代码,您错过了配置 IJMDataIntegration
和 IBalanceIntegrationContext
:
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
。该模板包含实际页面。看到这个
如果您需要更多详细信息,请搜索视图模型优先模式。基本上,视图可以定义为数据模板并与视图模型类型相关联。数据模板可以定义为资源,也可以在将显示视图模型的控件内内联定义。控件的内容是视图模型实例,数据模板用于可视化表示。此技术是先实例化视图模型,然后创建视图的情况的示例。
这是首选方式,尤其是与依赖注入结合使用时。