Magellan and IOC/DI Containers

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

A picture of me

Welcome, my name is Paul Stovell. I live in Brisbane and work on Octopus Deploy, an automated deployment tool for .NET applications.

Prior to founding Octopus Deploy, I worked for an investment bank in London building WPF applications, and before that I worked for Readify, an Australian .NET consulting firm. I also worked on a number of open source projects and was an active user group presenter. I was a Microsoft MVP for WPF from 2006 to 2013.

10 Nov 2009

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.

10 Nov 2009

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();

10 Nov 2009

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 ServiceLocatorControllerFactory and ServiceLocatorViewActivator. 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.

10 Nov 2009

Dropping IViewActivator completely 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.

10 Nov 2009

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).

David Miller
David Miller
09 Feb 2010

Hi Paul,

Finally had a reason to play with Magellan today - replacing a small utility app - and boy am I impressed.

I've wired Magellan with the newest version of Autofac and love the combination of constructor injection for views / controllers and setter injection for things like ActionFilter attributes using a custom ActionInvoker.

This affords me (in most cases) a way to avoid using a Service Locator approach outside of the Infrastructure namespace.

Very nice indeed!

09 Feb 2010

Hi David,

Glad to hear you had some success with it. I'd like to make the IOC/ActionFilter story a bit better than having to create an ActionInvoker - right now ServiceLocator is the easy path but it could be better. At the least the ActionInvoker could defer to some kind of IActionFilterInitializer to wire up the dependencies via setter injection.

Paul

David Miller
David Miller
09 Feb 2010

I think there is more to dynamic Action Filters than just initialization. Discovery is just as important. The current ActionInvoker abstraction has all the hooks to let it happen.

You can achieve this with a custom ActionInvoker implementation that overrides the FindActionFilters() method and performs container resolution yielding an IEnumerable.

I could definitely see the benefit in using AutoFac's component metadata query system as a mechanism for resolving a collection of action filters based on metadata derived from attributes on the controller or action. The fact that this collection is ordered as well as filtered is really useful for sequencing filters with different priorities (e.g authorisation code executes before all other code)

For Instance:

[Logged(Asynchronous = true)]
public ActionResult Index()
{
    return Page();
}

public class LoggedAttribute : Attribute, IActionFilterMetadata
{
     public LoggedAttribute() {
        ServiceType = typeof(ILoggedActionFilter);
        Priority = 50d;
     }

     public double Priority { get; set; }
     public bool Asynchronous { get; set; }
     public Type ServiceType { get; set; }
}

The responsibility of the IoC container is to resolve all IActionFilters that support matching metadata. If a particular logger is synchronous, then it would not be resolved for use on this action. The aim is that the Container knows all (by convention usually) and the caller / consumer should provide hints for usage.

The attribute is no longer the implementation but now just a constraint that the container can use for qualification.

09 Feb 2010

Hi David,

I was considering something like this:

[ActionFilter(typeof(MyFilter))]
public ActionResult Get() 
{ 
    ...

But I discounted it because there would be no way to pass parameters to the filter. Would your solution be able to do that? I suppose the IActionFilterMetadata could have the ability to supply custom arguments to the container.