Back to: Magellan Home
Magellan was designed to work without an Inversion of Control or Dependency Injection container, to keep it simple and accessible. However, as applications become more complicated, modern WPF applications can benefit immensely from IOC containers.
Magellan's extensibility points make using a container easy. Magellan resolves and instantiates two main types of objects - controllers and views. The out of the box implementation uses conventions, but they can be overridden.
To take control of resolving controllers, implement the IControllerFactory interface. Here is an example using Ninject:
public class NinjectControllerFactory : IControllerFactory
{
private readonly IKernel _container;
public NinjectControllerFactory(IKernel container)
{
_container = container;
}
public IController CreateController(NavigationRequest request, string controllerName)
{
var controller = _container.Get<IController>(controllerName);
return new ControllerFactoryResult(controller);
}
}
The ControllerFactoryResult has a second overload that also allows you to pass a callback that is invoked when the request has been processed - this can be used for disposing the controller if required.
Then assign it as the default controller factory:
var container = new StandardKernel();
container.Bind<IController>().To<HomeController>().Named("Home");
container.Bind<IController>().To<SettingsController>().Named("Settings");
ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory(container));
When a navigation request is processed, the Navigator will consult the ControllerBuilder.Current for the controller factory. It will use this to ask the factory for a controller, then execute the controller. The ReleaseController method will be called as soon as the navigation request has completed.
Typically, each navigation will create a new controller - if you want to use the same controller, you could have your controller factory recycle the objects, or make them singletons.
The second object that Magellan creates is views. This is done through the IViewActivator implementation. The default implementation looks for a public, parameterless constructor.
Again, let's use Ninject to control view instantiation.
public class NinjectViewActivator : IViewActivator
{
private readonly IKernel _container;
public NinjectViewActivator(IKernel container)
{
_container = container;
}
public object Instantiate(Type viewType)
{
return _container.Get(viewType);
}
}
We then need to tell the view engines that it is available. Since the view engines are automatically registered, we need to manually re-register them:
var activator = new NinjectViewActivator(container);
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new PageViewEngine(activator));
ViewEngines.Engines.Add(new WindowViewEngine(activator));
If you are using Magellan with Composite WPF, you will also need to register the region view engine:
ViewEngines.Engines.Add(new CompositeViewEngine(activator));
Back to: Magellan Home
Discussion
Artem Govorov
Hi Paul,
Nice article.
Just as an idea: you could possibly use Common Service Locator (http://www.codeplex.com/CommonServiceLocator) instead of NinjectViewActivator : IViewActivator (and possibly instead of IControllerFactory as well). Most of containers support CSL and it could give Magellan easier container abstraction options.
I'd also like to share how the same registration would look like with Unity and my auto registration extension (http://autoregistration.codeplex.com/) to it:
var container = new UnityContainer(); container .ConfigureAutoRegistration() .IncludeAllLoadedAssemblies() .Include(If.Implements, Then.Register().WithPartName(WellKnownAppParts.Controller)) .ApplyAutoRegistration();
This allows to register all Magellan controllers (Home, Settings, etc) by their names without having to list them explicitly.
Artem Govorov
Sorry for repeating, just to make previously posted code to look better:
var container = new UnityContainer();container
.ConfigureAutoRegistration()
.IncludeAllLoadedAssemblies()
.Include(If.Implements, Then.Register().WithPartName(WellKnownAppParts.Controller))
.ApplyAutoRegistration();
Paul Stovell
Hi Artem, thanks for the comment. In a section on Magellan with Composite WPF (which I didn't publish to RSS yet, but you can see it online), I include an example of a
ServiceLocatorControllerFactoryandServiceLocatorViewActivator. I added those primarily to support Composite WPF, but as you say they can be used to forward onto any IOC container.I did bundle the service locator adapters into Magellan.Composite.dll; do you think it would be better to seperate this into a different assembly for those that want to use CSL but not Composite WPF?
Your suggestion to use conventions to register controllers automatically is a good one and should be considered by anyone who wants to create a low-friction developer experience.
Paul Stovell
Dropping
IViewActivatorcompletely in favour of CSL would make sense but it worries me. First, it would require you to reference CSL even if you had no intention of using it, which would be a shame. Second, it would require the user to use a container, which I don't think should be a requirement (it should certainly be possible, just not required). So I'm playing it safe by having a Magellan interface and then an implementation that talks to CSL for those that can use it.Artem Govorov
Hi Paul,
I see what you mean and agree with your points about cons of dropping IViewActivator completely in favour of CSL. It makes sense to have Magellan interfaces and CSL implementation (in a separate assembly for those who want to use IoC via CSL and not Composite WPF but say Caliburn or another framework). This way you would allow to use most of containers with Magellan without having to implement anything (Magellan has CSL implementation of interfaces it works with, most of containers have CSL implementations).