使用NInject在WPF中注入没有无参数构造函数的viewmodel类

我正在使用NInject来解析我的第一个WPF应用程序的依赖项。 以下是我的代码片段。

我的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(); } private void ConfigureContainer() { this.container = new StandardKernel(); container.Bind().To(); } } 

App.xaml是这样的。

     

MainWindow.xaml。

         

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) { } } 

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

我知道它因为viewmodel类没有无参数构造函数。 但我无法理解为什么dependency injection器无法解决这个问题? 难道我做错了什么?

任何帮助将不胜感激。

首先,我建议阅读.NET中的dependency injection一书,特别是关于WPF的部分。 但即使您没有阅读它,本书的代码下载中也有一个有用的示例。


您已经知道需要从App.xaml文件中删除StartupUri="MainWindow.xaml"

但是,在使用DI时,您不能以声明方式连接DataContext否则它只能使用默认构造函数。

   

在涉及DI时,WPF中使用的模式有点令人困惑。 主要问题是,如果您希望ViewModel能够控制自己的窗口环境,那么MainWindow与其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还需要进行一些返工,以便它可以将TrackerViewModel它的构造函数中。 这允许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和抽象来修复框架,以帮助管理每个窗口的IWindowWindowAdapter

 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提供了一个专门的窗口适配器,它确保使用ViewModel正确初始化DataContext属性。

 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().To(); container.Bind().To(); container.Bind().To(); container.Bind().To(); // Build the application object graph var window = container.Get(); // Show the main window. window.Show(); // End Composition Root } } 

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

如果您确实希望将注册分解为另一个步骤,则可以使用一个或多个Ninject模块 。

 using Ninject.Modules; using System.Windows; public class MyApplicationModule : NinjectModule { public override void Load() { Bind().To(); Bind().To(); Bind().To(); Bind().To(); } } 

然后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().Show(); // End Composition Root } } 

trackerviewmodel将由自动生成的xaml设计器代码实例化,而不是由ninject实例化。 我从未使用过ninject,但我认为您需要配置容器以了解您的viewModel,然后为Ninject注入viewmodel以解决它及其依赖关系:

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