使用NInject在WPF中注入没有无参数构造函数的视图模型类

本文关键字:构造函数 参数 视图 模型 NInject WPF 注入 使用 | 更新日期: 2023-09-27 18:12:27

我使用NInject来解决我的第一个WPF应用程序的依赖关系。以下是我的代码片段:

My App.xaml.cs如下。

public partial class App : Application
{
    private IKernel container;
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        ConfigureContainer();
        ComposeObjects();
    }
    private void ComposeObjects()
    {
        Current.MainWindow = this.container.Get<MainWindow>();
    }
    private void ConfigureContainer()
    {
        this.container = new StandardKernel();
        container.Bind<ISystemEvents>().To<MySystemEvents>();
    }
}

应用程序。Xaml是这样的

<Application x:Class="Tracker.App"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Application.Resources>
    </Application.Resources>
</Application>

MainWindow.xaml .

<Window x:Class="Tracker.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:viewmodel="clr-namespace:Tracker.ViewModel"
        Title="MainWindow" Height="150" Width="350">
    <Window.DataContext>
        <viewmodel:TrackerViewModel>
        </viewmodel:TrackerViewModel>
    </Window.DataContext>
    <Grid>
    </Grid>
</Window>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
}

和viewmodel

internal class TrackerViewModel : System.ComponentModel.INotifyPropertyChanged
{
    public TrackerViewModel(ISystemEvents systemEvents)
    {
        systemEvents.SessionSwitch += SystemEvents_SessionSwitch;
    }
    private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
    {
    }
}

现在当我启动应用程序时,我在InitializeComponent()方法中得到一个异常An unhandled exception of type 'System.NullReferenceException' occurred in PresentationFramework.dll

我知道这是因为视图模型类没有无参数构造函数。但我不能理解为什么依赖注入器不能解决这个问题?我做错了什么吗?

使用NInject在WPF中注入没有无参数构造函数的视图模型类

首先,我推荐阅读。net中的依赖注入这本书,特别是关于WPF的部分。但是,即使你没有读过它,在本书的代码下载中也有一个有用的例子。


您已经计算出您需要从App.xaml文件中删除StartupUri="MainWindow.xaml"

然而,当使用DI时,你不能声明性地连接DataContext,否则它将只能使用默认构造函数。

<Window x:Class="WpfWithNinject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="150" Width="350">
</Window>
当涉及到DI时,WPF中使用的模式有点令人困惑。主要的问题是,如果你想让你的ViewModel能够控制它自己的窗口环境,在主窗口和它的ViewModel之间有一个循环依赖问题,所以你需要创建一个抽象工厂来实例化ViewModel,这样依赖关系才能得到满足。

创建ViewModel Factory

internal interface ITrackerViewModelFactory
{
    TrackerViewModel Create(IWindow window);
}
internal class TrackerViewModelFactory : ITrackerViewModelFactory
{
    private readonly ISystemEvents systemEvents;
    public TrackerViewModelFactory(ISystemEvents systemEvents)
    {
        if (systemEvents == null)
        {
            throw new ArgumentNullException("systemEvents");
        }
        this.systemEvents = systemEvents;
    }
    public TrackerViewModel Create(IWindow window)
    {
        if (window == null)
        {
            throw new ArgumentNullException("window");
        }
        return new TrackerViewModel(this.systemEvents, window);
    }
}

TrackerViewModel也需要进行一些修改,以便它可以接受IWindow进入其构造函数。这允许TrackerViewModel控制自己的窗口环境,例如向用户显示模态对话框。

internal class TrackerViewModel : System.ComponentModel.INotifyPropertyChanged
{
    private readonly IWindow window;
    public TrackerViewModel(ISystemEvents systemEvents, IWindow window)
    {
        if (systemEvents == null)
        {
            throw new ArgumentNullException("systemEvents");
        }
        if (window == null)
        {
            throw new ArgumentNullException("window");
        }
        systemEvents.SessionSwitch += SystemEvents_SessionSwitch;
        this.window = window;
    }
    private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
    {
    }
    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
}

调整窗口

您需要用一个用于窗口的抽象类型IWindow和一个用于帮助管理每个窗口的DI的抽象WindowAdapter来修复框架。

internal interface IWindow
{
    void Close();
    IWindow CreateChild(object viewModel);
    void Show();
    bool? ShowDialog();
}
internal class WindowAdapter : IWindow
{
    private readonly Window wpfWindow;
    public WindowAdapter(Window wpfWindow)
    {
        if (wpfWindow == null)
        {
            throw new ArgumentNullException("window");
        }
        this.wpfWindow = wpfWindow;
    }
    #region IWindow Members
    public virtual void Close()
    {
        this.wpfWindow.Close();
    }
    public virtual IWindow CreateChild(object viewModel)
    {
        var cw = new ContentWindow();
        cw.Owner = this.wpfWindow;
        cw.DataContext = viewModel;
        WindowAdapter.ConfigureBehavior(cw);
        return new WindowAdapter(cw);
    }
    public virtual void Show()
    {
        this.wpfWindow.Show();
    }
    public virtual bool? ShowDialog()
    {
        return this.wpfWindow.ShowDialog();
    }
    #endregion
    protected Window WpfWindow
    {
        get { return this.wpfWindow; }
    }
    private static void ConfigureBehavior(ContentWindow cw)
    {
        cw.WindowStartupLocation = WindowStartupLocation.CenterOwner;
        cw.CommandBindings.Add(new CommandBinding(PresentationCommands.Accept, (sender, e) => cw.DialogResult = true));
    }
}
public static class PresentationCommands
{
    private readonly static RoutedCommand accept = new RoutedCommand("Accept", typeof(PresentationCommands));
    public static RoutedCommand Accept
    {
        get { return PresentationCommands.accept; }
    }
}

然后我们有一个专门的MainWindow窗口适配器,它确保DataContext属性与ViewModel正确初始化。

internal class MainWindowAdapter : WindowAdapter
{
    private readonly ITrackerViewModelFactory vmFactory;
    private bool initialized;
    public MainWindowAdapter(Window wpfWindow, ITrackerViewModelFactory viewModelFactory)
        : base(wpfWindow)
    {
        if (viewModelFactory == null)
        {
            throw new ArgumentNullException("viewModelFactory");
        }
        this.vmFactory = viewModelFactory;
    }
    #region IWindow Members
    public override void Close()
    {
        this.EnsureInitialized();
        base.Close();
    }
    public override IWindow CreateChild(object viewModel)
    {
        this.EnsureInitialized();
        return base.CreateChild(viewModel);
    }
    public override void Show()
    {
        this.EnsureInitialized();
        base.Show();
    }
    public override bool? ShowDialog()
    {
        this.EnsureInitialized();
        return base.ShowDialog();
    }
    #endregion
    private void DeclareKeyBindings(TrackerViewModel vm)
    {
        //this.WpfWindow.InputBindings.Add(new KeyBinding(vm.RefreshCommand, new KeyGesture(Key.F5)));
        //this.WpfWindow.InputBindings.Add(new KeyBinding(vm.InsertProductCommand, new KeyGesture(Key.Insert)));
        //this.WpfWindow.InputBindings.Add(new KeyBinding(vm.EditProductCommand, new KeyGesture(Key.Enter)));
        //this.WpfWindow.InputBindings.Add(new KeyBinding(vm.DeleteProductCommand, new KeyGesture(Key.Delete)));
    }
    private void EnsureInitialized()
    {
        if (this.initialized)
        {
            return;
        }
        var vm = this.vmFactory.Create(this);
        this.WpfWindow.DataContext = vm;
        this.DeclareKeyBindings(vm);
        this.initialized = true;
    }
}

组合根

最后,你需要一种方法来创建对象图。你在正确的地方做了这件事,但是你把它分成许多步骤对你自己没有任何好处。而且,将容器作为应用程序级别的变量并不一定是一件好事——它使容器作为服务定位器被滥用。

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        // Begin Composition Root
        var container = new StandardKernel();
        // Register types
        container.Bind<ISystemEvents>().To<MySystemEvents>();
        container.Bind<ITrackerViewModelFactory>().To<TrackerViewModelFactory>();
        container.Bind<Window>().To<MainWindow>();
        container.Bind<IWindow>().To<MainWindowAdapter>();
        // Build the application object graph
        var window = container.Get<IWindow>();
        // Show the main window.
        window.Show();
        // End Composition Root
    }
}

我认为你遇到的主要问题是你需要确保在MainWindow上手动调用Show()

如果你真的想把注册分解成另一个步骤,你可以通过使用一个或多个Ninject Modules来实现。

using Ninject.Modules;
using System.Windows;
public class MyApplicationModule : NinjectModule
{
    public override void Load()
    {
        Bind<ISystemEvents>().To<MySystemEvents>();
        Bind<ITrackerViewModelFactory>().To<TrackerViewModelFactory>();
        Bind<Window>().To<MainWindow>();
        Bind<IWindow>().To<MainWindowAdapter>();
    }
}

然后App.xaml.cs文件看起来像这样:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        // Begin Composition Root
        new StandardKernel(new MyApplicationModule()).Get<IWindow>().Show();
        // End Composition Root
    }
}

trackerviewmodel将由自动生成的xaml设计器代码实例化,而不是通过ninject。我从来没有使用过ninject,但我认为你需要配置容器知道你的viewModel,然后注入viewModel ninject来解决它和它的依赖关系:

public class MainWindow : Window
{
    [Inject]
    public TrackerViewModel ViewModel { get; set; }
    public MainWindow()
    {
        InitializeComponent();
        DataContext = ViewModel;
    }
}