导航到新页面时 WPF 依赖注入(inject)

标签 wpf dependency-injection

我遇到问题,因为我不知道如何将服务注入(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),则必须显式实例化它们,以便通常在工厂的帮助下调用适当的构造函数。

  1. 制造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/

    相关文章:

    c# - 我怎样才能通过键绑定(bind)在 xaml 中聚焦文本框?

    c# - 基于 ViewModel 状态显示 UI 错误消息的最佳实践

    wpf - 如何让 wpf listboxitem 在选择时拉伸(stretch)列表框的整个高度

    c# - LightInject 尝试创建一个没有当前作用域的作用域实例

    java - 是否可以使用 Guice 注入(inject)请求注入(inject)的类?

    php - Laravel 5 目标不可实例化

    c# - ViewModel 中的可绑定(bind)字段

    c# - 在 MVVM 世界中更改 CollectionViewSource 源

    angular - 它是服务还是提供者?被 Angular 教程弄糊涂了

    multithreading - 使用依赖注入(inject)时如何使共享资源线程安全?