MVVM Light, Ninject in a mostly notification area

2019-04-14 23:23发布

问题:

I need an application architecture advise.

I am building a .Net 4 WPF desktop application with notification area icon support.

Application has a few windows, that show up on startup, then get closed, and only the Notification Area icon remains.

The notification area icon is purely WPF control that I got from this codeproject example.

Since my app should remain running even when all the windows are closed, I have set a

ShutdownMode="OnExplicitShutdown"

in App.xaml.

I will describe my idea of an architecture and startup mechanism, and you tell me where I am going wrong and correct me if possible.

In App.xaml.cs I create a Ninject StandardKernel, let's call it appKernel and load Ninject modules into it. At first only one interface is to be resolved by Ninject - a heartbeatManager instance. A HeartbeatManager is a class I am planing to use for:

a) Hosting my NotifyIcon instance as a field variable, so that it reamins visible as long as the class instance is in memory.

b) Implementation of shutdown event, that I will subscribe for in app.xaml.cs and shutdown the application explicitly, when the heartbeat class requests it.

At this point a heartbeatManager instance is created just left to hang in the memory.

In App.xaml I set the StartupUri="MainWindow.xaml" so the MainWindow is created and shown. Following the MVVM Light ViewModelLocator pattern the MainWindow tries to resolve it's data context from the static ViewModelLocator instance defined in App.xaml:

<Application.Resources>
    <ViewModels:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
</Application.Resources>

ViewModelLocator instance is created and in the constructor it initializes another StandardKernel (is there any other type of kernel I shout use at this point?) that will be used to resolve the view model bindings. A MainWindow's view model is provided to satisfy the windows's request and the whole application continues to operate.

A very important implication, is that the hearbeatManger instance is bound to an interface definition in a singletone scope, so that view models that require it as a constructor parameter can resolve and get the already created instance. Will such dependency resolution work, if the view model and the heartbeatManager are loaded in a different kernels? If not, how can I solve this problem?

Is the above planned scenario any good architecture-wise, is it implementable in code, or have I made a mistake when I was thinking through the architecture?

回答1:

...At this point a heartbeatManager instance is created just left to hang in the memory...

If it is registered with your kernel, it isn't any magic that causes it to hang in memory - it will stick around as long as the kernel/container does. That container should probably be a member of your App class, since your app class sticks around as long as the (compiler generated) Main method does, and you should dispose it on app shutdown anyhow.

...Following the MVVM Light ViewModelLocator pattern the MainWindow tries to resolve it's data context from the static ViewModelLocator instance defined in App.xaml...

I'm not sure this will mix well with NInject. It is a Dependency Injection container, not a Service Locator container (even though it uses one under the hood). The main purpose of NInject (and similar frameworks) is to avoid requiring you to use the Service Locator pattern, and instead to inject all your dependencies.

...ViewModelLocator instance is created and in the constructor it initializes another StandardKernel...

Unless your scenario is quite complicated (which it really isn't for this app), then I suggest sticking to a single NInject kernel.

Suggested Solution

The view model locator class itself doesn't do much for you other than let you split up your initialization logic, and do selective initialization depending on whether you're in design mode or runtime mode. You could achieve the same with NInject modules (which the view model locator article you linked in comments already uses).

I suggest that instead of using a view model locator, you specify all your components in your module class, and do the IsInDesignMode logic in the Load method.

This might be a bit tricky with MVVM though, since the view model needs to bind to an object property you didn't create, and can't annotate.

There are a few ways to solve this directly in NInject, instead of resorting to a service locator:

  • Use Dependency Injection on your view, making it require a view model.
    If this doesn't work using constructor injection (as you mentioned in your comments), use property injection instead, with a custom property that forwards its setter to DataContext.
  • Use NInject factory methods (ToMethod) to create your view, and bind the view model to the view in each factory method.
    E.g. Bind<MainView>().ToMethod(context => new MainView() { DataContext = new MainViewModel() });

If you can, I'd go for the second method. This way your view doesn't have to handle data binding, and doesn't have to have any code behind. It is also simple to understand, would let you avoid having to register both views and view models in the kernel, and would let you avoid making any sort of well known interface for your view model.