我遇到问题,因为我不知道如何将服务注入(inject)页面。 这就是我的 App.xaml.cs 的样子
public partial class App : Application
{
public IServiceProvider ServiceProvider { get; set; }
public IConfiguration Configuration { get; set; }
protected override void OnStartup(StartupEventArgs e)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", false, true);
Configuration = builder.Build();
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
ServiceProvider = serviceCollection.BuildServiceProvider();
var mainWindow = ServiceProvider.GetRequiredService<MainWindow>();
mainWindow.Show();
}
private void ConfigureServices(ServiceCollection serviceCollection)
{
serviceCollection.AddTransient<IPage1ViewModel, Page1ViewModel>();
serviceCollection.AddTransient(typeof(MainWindow));
}
}
我有带有框架的 MainWindow,在框架中我有带有按钮的名为 Home.xml 的默认页面。
<Window x:Class="WpfDITest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfDITest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Menu Grid.Row="0" Grid.Column="0">
<MenuItem Header = "Help" HorizontalAlignment="Center" VerticalAlignment="Center">
<MenuItem Name="about" Header = "about t" HorizontalAlignment = "Stretch"/>
</MenuItem>
</Menu>
<Frame Grid.Row="1" Grid.Column="0" Source="/Views/Home.xaml" NavigationUIVisibility="Hidden" />
</Grid>
当您单击按钮时,它会将您导航到名为 Page1 的新页面。
public partial class Home : Page
{
public Home()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var uri = "/Views/Page1.xaml";
NavigationService.Navigate(new Uri(uri, UriKind.Relative));
}
}
我想将 IPage1ViewModel 注入(inject)到我的 Page1 中,所以我想我只需将服务注入(inject)到构造函数中,就像在 ASP.NET 应用程序中一样,但问题是导航服务会在没有参数的情况下触发构造函数,所以现在我不知道如何实现这一点。
public partial class Page1 : Page
{
private readonly IPage1ViewModel _page1ViewModel;
public Page1(IPage1ViewModel page1ViewModel)
{
_page1ViewModel = page1ViewModel;
InitializeComponent();
}
public Page1() //this constructor fires
{
InitializeComponent();
GetData();
}
public void GetData()
{
_page1ViewModel.GetTitle(); // How to get this?
}
}
Page1ViewModel
public class Page1ViewModel : IPage1ViewModel
{
public Page1ViewModel()
{
}
public string GetTitle()
{
return "Welcome";
}
}
在我的例子中使用依赖注入(inject)是个好主意吗?如果是这样,我该如何解决我的问题?
最佳答案
您必须实例化 Page
显式使用工厂(抽象工厂模式或工厂委托(delegate))。
当您通过 XAML 实例化控件时,无论是通过定义元素还是通过 URI,XAML 引擎将始终使用默认构造函数创建实例(因此这对于 XAML 实例化是必需的)。
如果您的控件必须使用依赖项注入(inject),则必须显式实例化它们,以便通常在工厂的帮助下调用适当的构造函数。
- 制造
Home
请求Page1
工厂委托(delegate)(Func<Page1>
)作为构造函数依赖项。然后用它来创建Page1
显式实例:
public partial class Home : Page
{
private Func<Page1> Page1Factory { get; }
public Home(Func<Page1> page1Factory)
{
InitializeComponent();
this.Page1Factory = page1Factory;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Page1 nextPage = this.Page1Factory.Invoke();
NavigationService.Navigate(nextPage);
}
}
- 注册
Page1
类型(具有适当的生命周期)及其工厂委托(delegate)(或者抽象工厂类型)
.NET Microsoft.Extensions.DependencyInjection示例
private void ConfigureServices(ServiceCollection serviceCollection)
{
serviceCollection.AddTransient<IPage1ViewModel, Page1ViewModel>()
.AddTransient<MainWindow>()
.AddTransient<Page1>()
.AddSingleton<Func<Page1>>(serviceProvider => serviceProvider.GetService<Page1>);
}
等效 Autofac 示例
private void ConfigureServices(ContainerBuilder containerBuilder)
{
_ = containerBuilder.RegisterType<Page1ViewModel>()
.As<IPage1ViewModel>()
.InstancePerDependency();
_ = containerBuilder.RegisterType<MainWindow>().InstancePerDependency();
_ = containerBuilder.RegisterType<Page1>().InstancePerDependency();
// Autofac will automatically create the Func<T> instance, when it detects a corresponding constructor parameter.
// But you can define the delegate instance explicitly if required.
// The following line is therefore optional.
_ = containerBuilder
.Register<Func<Page1>>(context => context.Resolve<Page1>)
.SingleInstance();
IContainer container = containerBuilder.Build();
}
- 通常,您会绑定(bind)
Page1
的 UI 元素到其DataContext
。因此,您必须将注入(inject)的 View 模型设置为DataContext
。另外,您应该删除默认构造函数(或至少将其设置为private
),因为它不会正确初始化类型( View 模型,即DataContext
丢失)。此外,一个public
名为Get...
的方法预计会返回结果。要么重命名该方法,要么让它返回结果,或者使其至少为private
也是。
public partial class Page1 : Page
{
private IPage1ViewModel Page1ViewModel { get; }
// This constructor also calls the private default constructor
// (in case this type has multiple constructor overloads.
// Otherwise, move the private default constructor code to this constructor).
public Page1(IPage1ViewModel page1ViewModel) : this()
{
this.Page1ViewModel = page1ViewModel;
this.DataContext = this.Page1ViewModel;
Initialize();
}
private Page1()
{
InitializeComponent();
}
private void Initialize()
{
this.Page1ViewModel.GetTitle();
}
}
- 由于建议使用数据绑定(bind) ( Data binding overview (WPF .NET) ),因此您的 View 模型类必须实现
INotifyPropertyChanged
(参见 Microsoft docs for an example
关于导航到新页面时 WPF 依赖注入(inject),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72675530/