WPF MVVM:无需构造函数重载许多参数的依赖注入

WPF MVVM: Dependency Injection Without Constructor Overloading Many Parameters

我正在构建一个 WPF 应用程序,它有一个 Window,一个带有多个视图模型的选项卡式界面(2 个视图模型,第一个选项卡是 ViewModel1,其余的将始终是 ViewModel2)这些已加载通过使用数据触发器的用户控件。

最后还有其他windows如Dialogs/Settings等

项目结构

我的项目分为几个层,如下所述:

我遇到的一个问题是在 WPF/MVVM 设置中使用 DI,我在网上看到很多资源,其中很多是 ASP.NET 核心 DI,与我的内容无关专注于。我发现的问题是,在 WPF 中似乎只有一个应用程序启动点 (MainWindow),当使用 DI 时,所有内容都被注入到构造函数中,然后进一步传递给其他 dialogs/models 这看起来很麻烦(或者我做错了)。例如,我目前在使用 Microsoft DI 的应用程序代码中有类似的东西。

(对话框用于创建 dialogs/opening windows 等,消息用于消息框)

services.AddSingleton<IRepo1, Repo1>();
services.AddSingleton<IRepo1Service, Repo1Service>();
services.AddSingleton<IDialog, Dialog>();
services.AddSingleton<IMessaging, Messaging>();

要在视图模型中使用它们,需要像这样将它们注入 MainWindow 构造函数中

MainWindow(repo1service, dialog, messaging)

然后将这些传递给视图模型

MainWindowViewModel(repo1service,dialog,messaging)

这个过程似乎我在构造函数中做了很多工作并从单个应用程序点向下遍历,而且其中一些视图模型甚至可能不使用对话框或消息传递,而只使用数据库上下文

MainWindowViewModel 有选项卡式控件,然后在添加另一个选项卡时调用相应的视图模型,所以例如我需要做:

Tabs.Add(new ViewModel1(repo1service,dialog,messaging))

当用户点击新标签时。

我意识到我可以使用 DI 并像单例一样添加视图模型:

 services.AddSingleton<ViewModel1>();

但是,如果仅在单击按钮后调用此视图模型,我仍然有需要将参数传递给构造函数的问题。

如何避免向viewmodel传递很多参数,DI如何为我解决这个问题?我可以将 IServiceCollection 传递给模型并根据需要进行检索吗?或者这是一种不好的方法吗?

我已经阅读了这些资源,但我仍然不确定如何解决我的问题。

  1. https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-usage

  2. How to handle dependency injection in a WPF/MVVM application

  3. Dependency injection & constructor madness revised

  4. How to avoid Dependency Injection constructor madness?

一个简单的示例,说明如何在使用依赖注入时连接 WPF 应用程序。它还展示了如何注册工厂(在本例中为委托)以启用动态实例创建。这个想法是让容器为您创建任何实例。 DI 允许您避免使用 new 关键字,因此免除了您关心构造函数及其依赖项的责任。

在 WPF 中显示视图的一般推荐方法是视图模型优先原则:基本上,将数据模型添加到视图(例如通过将视图模型绑定到 ContentControlContentPresenter) 并使用 DataTemplate 让框架加载相关视图。

App.xaml.cs

private async void OnStartup(object sender, StartupEventArgs e)
{
  var services = new ServiceCollection();
 
  // Look how you can chain the service registrations:
  services.AddSingleton<IMainViewModel, MainViewModel>()
    .AddSingleton<IRepository, Repository>()
    .AddSingleton<IViewModel1, ViewModel1>()
    
    // Factory is used to create instances dynamically.
    // Alternatively, instead of a delegate you can register a factory class.
    .AddSingleton<Func<IViewModel1>>(serviceProvider => serviceProvider.GetService<IViewModel1>)

    // Register a factory delegate taking an argument
    .AddSingleton<Func<User, IViewModel2>>(serviceProvider => user =>
      { 
        var repository = serviceProvider.GetService<IRepository>();
        var dependency1 = serviceProvider.GetService<IDependency1>();
        var dependency2 = serviceProvider.GetService<IDependency2>();
        return new ViewModel2(
          repository, dependency1, dependency2, user);

        // Alternativelyly set property instead of using constructor
        var viewModel2 = serviceProvider.GetService<IViewModel2>();
        viewModel2.User = user;
        return viewModel2;
      })

    // Alternatively register an abstract factory
    .AddSingleton<IViewModel2Factory, ViewModel2Factory>()

    .AddSingleton<IDependency1, Dependency1>()
    .AddSingleton<IDependency2, Dependency2>()
    .AddSingleton<IViewModel2, ViewModel2>()
    .AddSingleton<IRepository, Repository>()
    .AddSingleton<MainView>();
  
  ServiceProvider container = services.BuildServiceProvider();

  // Let the container compose the dependency graph 
  // and export the MainView to start the GUI
  var mainWindow = container.GetService<MainView>();

  // Launch the main view
  mainWindow.Show();
}

ViewModel2Factory.cs

class ViewModel2Factory : IViewModel2Factory
{
  private IRepository Repository { get; }
  private Func<IRepository> RepositoryFactory { get; }
  private Func<IDependency1> Dependency1Factory { get; }
  private Func<IDependency2> Dependency2Factory { get; }

  public (
    IRepository repository, 
    IDependency1 dependency1, 
    IDependency2 dependency2)
  {
    // TDODO::Initialize properties
  }

  public IViewModel2 Create(User user)
  {
    var repository = this.RepositoryFactory.Invoke();
    var dependency1 = this.Dependency1Factory.Invoke(); 
    var dependency2 = this.Dependency2Factory.Invoke(); 
    return new ViewModel2(repository, dependency1, dependency2, user);
  }
}

MainViewModel.cs

class MainViewModel : IMainViewModel
{
  public IViewModel2 ViewModel2 { get; }
  private IViewModel1 ViewModel1 { get; }
  private Func<IViewModel1> TabItemFactory { get; }

  public MainViewModel(
    IViewMode1 viewModel1, 
    Func<IViewModel1> viewModel1Factory, 
    IViewModel2 viewModel2)
  {
    this.ViewModel = viewModel2; // public read-only for data binding
    this.ViewModel1 = viewModel1; // private read-only for internal use only
    this.TabItemFactory = viewModel1Factory; // private read-only for internal use only
  }

  private void ExecuteAddTabCommand(object commandParameter)
  {
    // Uses a shared instance for every tab
    this.Tabs.Add(this.ViewModel1);

    // Create a fresh instance for every tab using a factory
    IViewModel1 tabViewModel = this.TabItemFactory.Invoke();
    this.Tabs.Add(tabViewModel);
  }
}

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public MainWindow(IMainViewModel mainViewModel)
  {
    InitializeComponent();

    this.DataContext = mainViewModel;
  }
}

MainWindow.xaml

<Window>

 <!-- Bind to a nested view model of the MainViewModel -->
 <MyUserControl DataContext="{Binding ViewModel2}" />
</Window>

ViewModel1.cs

class ViewModel1 : IViewModel1
{
  private IRepository Repository { get; }
  private IViewModel2Factory ViewModel2Factory { get; }

  public ViewModel1(IRepository repository, IViewModel2Factory viewModel2Factory)
  {
    this.Repository = repository; // private read-only for internal use only
    this.ViewModel2Factory = viewModel2Factory;
  }

  public void CreateViewModel2()
  {
    IViewModel2 newTab = this.ViewModel2Factory.Create(this.User);
  }
}

ViewModel2.cs

class ViewModel2 : IViewModel2
{
  public ViewModel2(
    IRepository repository, 
    IDependency1 dependency1, 
    IDependency2 dependency2,
    User user)
  {
    ...
  }
}