带参数的依赖注入

问题描述 投票:0回答:1

我有一个 WPF 应用程序。在左侧的主窗口中,是一个包含多个条目的列表框,右侧是一个 ContentControl,当选择其中一个条目时,应将 UserControl 以及视图模型加载到其中。

接下来,当选择列表框中的条目之一时,应创建一个具有视图模型的 UserControl 实例,列表框中选定的元素或其字段之一应传递给视图模型构造函数。

我不知道如何在不手动创建 UserControl 和视图模型的新实例的情况下正确执行此操作,而不违反 DI 原则,如果手动创建实例,则应用程序在关闭时不会从内存中清除。

MainView.Xaml

<ListBox ItemsSource="{Binding ContainerList}" SelectedItem="{Binding SelectedContainer}" 
                 HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseLeftButtonUp">
                    <i:InvokeCommandAction Command="{Binding ShowContent}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
</ListBox>
<ContentControl Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Content="{Binding TheContent}"></ContentControl>

背后代码:

public partial class MainView : Window
{
    public MainView(IMainViewModel viewModel)
    {
        this.DataContext = viewModel;
        InitializeComponent();
    }
}

public class MainViewModel : ViewModelBase, IMainViewModel
{
    private readonly IRepositories _repositories;
    private readonly IAbstractFactory<ChangeExecutorView> _factory;

    public ObservableCollection<Container> ContainerList { get; set; }
    private Container _SelectedContainer { get; set; }
    public Container SelectedContainer { get { return _SelectedContainer; } set { _SelectedContainer = value;  OnPropertyChanged(nameof(SelectedContainer)); } 
    }

    private object _TheContent { get; set; }
    public object TheContent
    {
        get { return _TheContent; }
        set {_TheContent = value; OnPropertyChanged(nameof(TheContent)); }
    }
    
    // public MainViewModel(IContainerRepository repContainer, IAbstractFactory<ChangeExecutorView> factory)
    public MainViewModel(IRepositories repositories, IAbstractFactory<ChangeExecutorView> factory)
    {
        _repositories = repositories;
        _factory = factory;
        ContainerList = new ObservableCollection<Container>(_repositories.ContainerRepository.GetAll());
    }

    // Here is action for create new UserControl
    public ICommand ShowContent 
    {
        get {
            return new RelayCommand(delegate (object param) 
                       {
                           // var content = new ContainerContentView(
                           // new ContainerContentViewModel(_repositories, SelectedUserID));
                       });
        }
    }
}

public interface IMainViewModel
{
    ObservableCollection<Container> ContainerList { get; set; }
}

app.xaml

public static IHost AppHost { get; set; }

public App()
{
    AppHost = Host.CreateDefaultBuilder()
                .ConfigureHostConfiguration((hostConfiguration => {
                    hostConfiguration.AddJsonFile("appsettings.json",false,true)
                    .AddEncryptedProvider()
                    .AddJsonFile($"appsettings.json", false, true);

                }))
                .ConfigureServices((hostConext, services) => 
                {
                    services.AddSingleton<MainView>();
                    services.AddTransient<IMainViewModel, MainViewModel>();
                    services.AddTransient<IRepositories,Repositories>();
                    services.AddFormFactory<ChangeExecutorView>();
                    services.AddScoped<ContainerContentViewModel>();
                })
                .Build();
    }

    public static T GetService<T>() where T : class 
    {
        var service = AppHost.Services.GetService(typeof(T)) as T;
        return service;
    }

    protected override async void OnStartup(StartupEventArgs e)
    {
        await AppHost.StartAsync();//.ConfigureAwait(true);

        var startupForm = AppHost.Services.GetRequiredService<MainView>();
        startupForm.Show();
        base.OnStartup(e);
    }

    protected override async void OnExit(ExitEventArgs e)
    {
        await AppHost.StopAsync();

        base.OnExit(e);
    }
}
wpf dependency-injection inversion-of-control code-injection
1个回答
0
投票

改进代码的一些注意事项:

  • GetService<T>
    不应该是
    public static
    ,而应该是
    private
    private static
    。将方法声明为 public 为从应用程序代码中引用容器打开了大门。如果您需要动态创建实例,请使用工厂(例如抽象工厂模式)。

  • 不要仅使用

    Interaction
    触发器来对所选项目的更改做出反应。不需要让鼠标事件触发命令。在视图模型中处理属性更改会更直接。因为当
    SelectedContainer
    更改时
    ListBox.SelectedItem
    属性也已更改,因此您不需要额外的冗余通知。请参阅下面的示例。

  • 您完整的财产申报是错误的。您当前正在定义一个

    private
    自动属性,并从
    public
    完整属性引用它,如下所示:

    // Wrong: this is an auto property to backup the full property. 
    // Instead, the backup property must be a field.
    private Container _selectedContainer { get; set; }
    
    // Wrong: this property is backed by a property instead of a field
    public Container SelectedContainer { get => _selectedContainer; } set => _selectedContainer = value; } 
    

    但是你必须使用支持fields来代替:

    // Correct: the backing FIELD
    private Container _selectedContainer;
    
    // Correct: the full property that uses the backing FIELD
    public Container SelectedContainer { get => _selectedContainer; } set => _selectedContainer = value; } 
    
  • 此外,您的类和符号名称具有误导性,因为 container 是 UI 上下文中使用的术语。在视图中,容器包装数据项,例如

    ListBoxItem
    是项目容器。以防万一:如果您正在视图模型中处理容器控件,那么这是错误的,可以而且应该避免。您的问题不清楚
    Container
    是什么。

  • 标准 C# 命名约定要求字段名称为 camelCase 而不是 PascalCase,例如

    private int _numericValue;
    。你的命名风格看起来很奇怪。

回答你的问题:

通常,您不会在 C# 代码中创建控件。相反,您可以使用工厂动态创建数据模型,并使用

DataTemplate
让框架为您创建控件 - 为数据模型的每个实例创建一个控件。

  1. 首先注册一个将由
    MainViewModel
    导入的工厂服务。您可以创建自定义抽象工厂以进行更复杂的创建(例如,当工厂方法包含许多参数或创建的实例需要额外配置时)或注册一个简单的
    Func<T>
    (无参数或机智参数):
ServiceProvider container = new ServiceCollection()
  .AddSingleton<Func<ContainerContentViewModel>>(serviceProvider => serviceProvider.GetRequiredService<ContainerContentViewModel>)
  .AddSingleton<IMainViewModel, MainViewModel>()
  .BuildServiceProvider(new ServiceProviderOptions() { ValidateOnBuild = true });

  // More examples to show how to register factories 
  // for more complex instance creation:
  //
  // If you need to register a factory that requires dynamic parameters 
  // (provided by the caller of the factory):
  .AddSingleton<Func<string, int, ContainerContentViewModel>>(
    serviceProvider => (name, number) => new ContainerContentViewModel(name, number))

  // If you need to configure the instance created by the factory 
  // with dynamic parameters (provided by the caller of the factory):
  .AddSingleton<Func<string, int, ContainerContentViewModel>>(serviceProvider => (name, number) 
    => 
    {
      var viewModel = serviceProvider.GetRequiredService<ContainerContentViewModel>();
      viewModel.Name = name;
      viewModel.Number = number;
   
      return viewModel;
    }
  
  1. 使用构造函数将工厂注入到
    MainViewModel
    中,并使用它来动态创建实例:

MainViewModel.cs

public class MainViewModel : ViewModelBase, IMainViewModel
{
  private readonly Func<ContainerContentViewModel> _containerContentViewModelFactory;

  private Container _selectedContainer;
  public Container SelectedContainer
  {
    get => _selectedContainer;
    set 
    { 
      _selectedContainer = value; 
      OnPropertyChanged(nameof(SelectedContainer)); 

      // Load the new view content by assigning a data model to 
      // the TheContent property. 
      // Use this variant over an InteractionBehavior and a ICommand 
      // (which were introduced only to react to property changes).
      OnSelectedContainerChanged();
    }
  }

  private object _theContent;
  public object TheContent
  {
    get => _theContent;
    set 
    { 
      _theContent = value; 
      OnPropertyChanged(nameof(TheContent)); 
    }
  }

  // Declare the factory as constructor dependency
  public MainViewModel(Func<ContainerContentViewModel> containerContentViewModelFactory)
  {
    _containerContentViewModelFactory = containerContentViewModelFactory;
  }

  // Here is action for create new UserControl
  public void OnSelectedContainerChanged()
  {
    ContainerContentViewModel content = _containerContentViewModelFactory.Invoke();

    // The property will update the ContentControl
    // which is using a DataTemplate to load the actual control
    TheContent = content;
  }
}

MainView.xaml

<Window>
  <Window.Resources>

    <!-- 
         The implicit DataTemplate that is automatically loaded 
         by the ContentControl 
    -->
    <DataTemplate DataType="{x:Type ContainerContentViewModel}">
      <ContainerContentView />
    </DataTemplate>
  </Window.Resources>

  <Grid>
    <ListBox ItemsSource="{Binding ContainerList}" 
             SelectedItem="{Binding SelectedContainer}" />

    <ContentControl Content="{Binding TheContent}" />
  </Grid>
</Window>
© www.soinside.com 2019 - 2024. All rights reserved.