I am working on a middle sized WPF application (MVVM) that should be extensible and maintainable in the future. Thus I decided to use an IoC container (Unity in this case) to keep things flexible.
However I am not sure where to place and configure Unity in a WPF application.
I guess container should be accessible globally so it should probably go to Application class. But should I make it as static property? Should I configure it in Application_Startup() event handler?
Eg:
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public static UnityContainer MyUnityContainer;
private void Application_Startup(object sender, StartupEventArgs e)
{
// instantiate and configure Unity
}
}
This way I will be able to access container from any place in the application via static property:
App.MyUnityContainer
I guess this is one way to do it but I am not sure if there are better practices for this issue, specifically for WPF apps.
Have a look at the Composition Root Pattern. What you want to do is to initialize it in your Startup event handler and forget about its existence for the rest of the application.
You are trying to implement the Service Locator Pattern, which according to many is an inferior solution to this problem.
Let me post what I've concluded and hopefully it'll help people out. Correct if there's anything wrong! :P
I guess we'd be looking into something like this:
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
UnityContainer myUnityContainer = new UnityContainer();
//make sure your container is configured
myUnityContainer.RegisterType<ISomeDependency, SomeDependencyImplementation>();
myUnityContainer.RegisterType<IMainWindow, MainWindow>();
myUnityContainer.Resolve<IMainWindow>().Show();
}
}
public partial class MainWindow : Window, IMainWindow
{
private ISomeDependency _someDependency;
public MainWindow(ISomeDependency someDependency)
{
_someDependency = someDependency;
}
}
Note there are no globals or singletons, the container survives as long as MainWindow does and all dependencies behind this point of entry further into the composition graph are automagically resolved as long as the container knows about them.
As per new version of Unity container, we have to register it's own instance as well to get it in view models via constructor injection.
App.xaml.cs file:
protected override void OnStartup(StartupEventArgs e)
{
var unityIoC = new UnityContainer();
unityIoC.RegisterTypes(AllClasses.FromAssembliesInBasePath(), WithMappings.FromMatchingInterface, WithName.Default);
unityIoC.RegisterInstance(typeof(IUnityContainer), unityIoC);
}
View Model class
[InjectionConstructor]
public MyViewModel(IUnityContainer container)
{
}
Now unity container would be available for us in view model and can be used to resolve.
Related
I got an application mostly following this MSDN article.
Here is my bootstrapper class:
internal class Bootstrapper : UnityBootstrapper
{
// Wire up the dependencies using Unity container
protected override void ConfigureContainer()
{
base.ConfigureContainer();
// Register the connection manager
Container.RegisterType<IConnectionManager, ConnectionManager>(new ContainerControlledLifetimeManager());
}
// Return an instance of the main window
protected override DependencyObject CreateShell()
{
return ServiceLocator.Current.GetInstance<Shell>();
}
// For WPF, initialising shell is simply setting the data context and showing the main window
protected override void InitializeShell()
{
base.InitializeShell();
Application.Current.MainWindow = (Window)this.Shell;
Application.Current.MainWindow.DataContext =
ServiceLocator.Current.GetInstance<MainViewModel>();
Application.Current.MainWindow.Show();
}
}
The connection manager in the code is registered with ContainerControlledLifetimeManager which means it will be treated as a singlton.
Now my connection manager is implementing IDisposable and I want to know the conventional way to dispose the container in order for it to dispose all related ContainerControlledLifetimeManager-ed objects.
I started by overriding OnExit of the application, but I found that UnitBootstrapper doesn't have a dispose method. I can write all the code manually to dispose the container on exit, but I am guessing there must be a formal way to do this kind of usual stuff.
This question is based on the old-style (Prism 6) Unity Bootstrapper. This answer is compatible with Prism 7 Unity containers.
An IUnityContainer object may be retrieved from the Prism IContainerProvider using the a Unity extension method, GetContainer(). The IUnityContainer can be disposed from the OnExit override of an Application that inherits from PrismApplication:
protected override void OnExit(ExitEventArgs e)
{
base.OnExit(e);
Container.GetContainer().Dispose(); // must be called to dispose singletons
}
I have 3 ViewModels:
App.ViewModels.LoginViewModel
App.ViewModels.NavigationViewModel
App.ViewModels.AbcViewModel
and 3 Views:
App.Views.LoginView
App.Views.NavigationView
App.Views.AbcView
In my AppBootstrapper, LoginView is loaded like so:
protected override void OnStartup(object sender, System.Windows.StartupEventArgs e)
{
var windowManager = IoC.Get<IWindowManager>();
var loginModel = IoC.Get<ILogin>("Login");
windowManager.ShowWindow(loginModel, "LoginView");
}
However, this returns that the view cannot be found for that ViewModel. Unless I change the namespace of the LoginView to App.Views.Login.LoginView and leave the VM namespace as it is. It then works fine.
After a succesfful login, I use the same process to load my NavigationViewModel. (After having changed the namespace to the App.Views.Navigation.NavigationViewModel so that it actually works)
Currently, this leaves me with the following namespaces for the views:
App.Views.Login.LoginView
App.Views.Navigation.NavigationView
App.Views.AbcView
NavigationViewModel is a conductor, it has a list of ViewModels and a TabControl on the view to display them.
Unfortunately I then have to manually bind my AbcViewModel to the view, otherwise nothing gets displayed. For example:
AbcView abcv= new AbcView ();
AbcViewModel abcvm= IoC.Get<AbcViewModel>("Abc");
ViewModelBinder.Bind(abcvm, abc, null);
I want everything to be done using the Caliburn ViewModel first approach, so that adding new ViewModels and Views I don't need to worry about binding the view manually. I've adhered to the structure and yet it isn't working. Where am I going wrong?
Basically, is there a way that caliburn can create and then bind my view when I create my ViewModel?
Do I need to somehow call the ViewLocator for each of my models? If so, how is this any different to the manual bind that I'm doing at the moment?
Does anyone know of a full example (Whole project) of a view model first caliburn project that I can sneak a look at?
Any help appreciated, thanks in advance.
Matt
You don't need to bind any Views/ViewModels yourself, Caliburn.Micro takes care of it. You have to only tell Caliburn.Micro where your start class is by overriding OnStartup in your Bootstrapper class (see tutorial).
You also have to provide ResourceDictionary with your Bootstrapper class in App.xaml and remove Startup:
<Application.Resources>
<!--Declare your bootstrapper class-->
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<local:YourBootstrapperClass x:Key="bootstrapper" />
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Your Bootstrapper class must derive from BootstrapperBase and call Initialize() in constructor. That should be enough for Caliburn.Micro to bind views/viewmodels.
Additionally, if you want to use Dependency Injection in your project you can setup a SimpleContainer and register types you want to inject. You can do this in Bootstrapper:
public class Bootstrapper : BootstrapperBase
{
// Container for your registered types.
private SimpleContainer _container = new SimpleContainer();
public Bootstrapper()
{
Initialize();
}
protected override void OnStartup(object sender, System.Windows.StartupEventArgs e)
{
// Tells Caliburn.Micro where the starting point of your application is.
DisplayRootViewFor<ShellViewModel>();
}
protected override void Configure()
{
// Register types you want to inject.
_container.Singleton<IWindowManager, WindowManager>();
_container.Singleton<IEventAggregator, EventAggregator>();
base.Configure();
}
protected override object GetInstance(Type service, string key)
{
// This is called every time a dependency is requested.
// Caliburn.Micro checks if container contains dependency and if so, returns it.
var instance = _container.GetInstance(service, key);
if (instance != null)
return instance;
throw new InvalidOperationException("Could not locate any instances.");
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
// Get all registered classes...
return _container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
}
After that you can just inject dependecies via constructor like this:
public class ShellViewModel
{
public ShellViewModel(IEventAggregator eventAggregator)
{
// You can use eventAggregator here. It is already injected.
}
}
Caliburn.Micro uses naming convention so that it can discover Views/ViewModels automatically. You should have folders named: Views and ViewModels and your classes should be named YourClassView and YourClassViewModel. This way Caliburn.Micro can find them. So if you setup OnStartup like this:
protected override void OnStartup(object sender, System.Windows.StartupEventArgs e)
{
DisplayRootViewFor<ShellViewModel>();
}
Then your viewmodel must sit in ViewModels/ShellViewModel and your view for this ViewModel must sit in Views/ShellView.
How can we bind a user-control to a view-model object, when this last contains parameters in his constructor ???
Does the binding using "DataContext" in the view ensure that when we create a view-model, the view is automatically created ??
If you are using an IoC container, this is supported out-of-the-box.
It really depends on the IoC container you are using, but here is an example using Prism Unity container.
The following examples are taken out from the Prism QuickStarts guide
So, at first, we will have to set up the unity container:
public class QuickStartBootstrapper : UnityBootstrapper
{
private readonly CallbackLogger callbackLogger = new CallbackLogger();
/// <summary>
/// Configures the <see cref="IUnityContainer"/>.
///May be overwritten in a derived class to add specific
/// type mappings required by the application.
/// </summary>
protected override void ConfigureContainer()
{
// Here you can do custom registeration of specific types and instances
// For example
this.Container.RegisterInstance<CallbackLogger>(this.callbackLogger);
base.ConfigureContainer();
}
}
Baisically, youre done!
All you have to do now is have your view recieve the viewModel as a parameter in his constructor, like this:
public partial class OverviewView
{
public OverviewView(OverviewViewModel viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
}
Unity IoC container will take care of your parameters in the ViewModel even without you having to register those types most of the times.
Please note that in my answer I only refered to the IoC part of the configuration. setting up an entire MVVM application requires a bit more work and varies depending the MVVM framework you are using
Normally I just but the bootstrapper in the resources of the App.xaml, but for an app I'm building I need code execution to begin elsewhere and then start the bootstrapper up once I'm done my initialization code.
How can I start the bootstrapper?
I set the App.xaml to call a function as then did this:
using System.Windows;
namespace WpfApplication9
{
public partial class App : Application
{
private AdminBootstrapper b;
private void App_OnStartup(object sender, StartupEventArgs e)
{
//DO initialization
b = new AdminBootstrapper();
b.Start();
}
}
}
When I run nothing happens, and my view does not appear. I know the view/viewmodel work because if I put the bootstrapper in the resources section of the App.xaml it appears.
What am I doing wrong here?
Use the Initialize() method instead of Start(). I had the same error and searched the internet and found a post that said to use Initialize() instead. I tried it on the sample app in the Caliburn.Micro docs and it worked for me.
This is not the "correct" way to use the bootstrapper, the bootstrapper IS the place were you do the setup you talk of. The whole point is to provide a place were Caliburn.Micro knows you are going to set up. Without seeing your bootstrapper though it is impossible to know what is wrong.
The root ViewModel is not loaded until after Configure is called. This is your extensibility point. If you need to do complex configuration you could call a couple of overrides here and then derive a class which exposes these methods.
In general though you would need to provide more information about what you are doing before I could give you a concrete recipe.
Maybe not the correct way (wanted to remove it from the markup) but still, this code seems to work...
public partial class App : Application
{
MyBootstrapper _bootstrapper;
protected override void OnStartup(StartupEventArgs e)
{
_bootstrapper = new MyBootstrapper();
base.OnStartup(e);
}
}
public class MyBootstrapper : BootstrapperBase
{
public MyBootstrapper()
{
Start();
}
protected override void OnStartup(object sender, System.Windows.StartupEventArgs e)
{
DisplayRootViewFor<ShellViewModel>();
}
}
For those that don't know, you can mark an assembly with the PreApplicationStartMethod, which will define a method that gets called before Application_Start in an ASP.NET site (if you're using .NET 4). I love using this in an Onion Architecture for defining a method that does all the setup for Dependency Injection.
My question is... is there any equivalent way of doing the same thing for a thick client application, such as one written in WPF?
For a WPF application it doesn't make much sense to mark an assembly with an attribute, since you are in control of which code will execute anyway.
A good place to do this initialization would be the OnStartup method.
In your App.xaml remove the StartupUri="MainWindow.xaml"
Then in your App.xaml.cs I do this:
public partial class App : Application
{
private IWindsorContainer _container;
private IView _view;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
_container = new WindsorContainer();
/// Register your interfaces with your concrete implementations.
// we'll do View first in this example (some do view first others do ViewModel first)
_view = _container.Resolve<IView>();
_view.Show();
}
}