As I am in the process of rebuilding my Dimecasts.net WP7 application from the ground up to work with WP7 Mango one of the things I wanted to do was fully use the Caliburn.Micro framework. Caliburn.Micro has its own built in IoC container called the PhoneContainer which provides basic Dependency Injection and this container works great but I have a bit of history using Ninject with my Wp7 applications so I wanted to replace the usage of the PhoneContainer with my Ninject, or more precisely the CommonServiceLocator implementation of Ninject. I knew this had to be doable because there are examples of using MEF with Caliburn.Micro
Turns out I was right using Ninject w/ the CommonServiceLocator inside of Caliburn.Micro is doable and is very easy to boot. Of course before I set out to build my own implementation of this I decided to google for it and this post was about the closest I could find to working code, but this code was NOT complete, at least for the current CM implementation. Even though the post I found was not complete working code it was a great headstart in what needed to be done.
Here is how I got Caliburn.Micro to use Ninject as its IoC container.
Step 1: Register the setup bootstrapper that you want to use
You will need to open up your App.xaml file and put in the code below.
<!--Application Resources-->
<Application.Resources>
<local:NinjectBootstrapper x:Key="bootstrapper" />
</Application.Resources>
Step 2: Create the a NinjectBootstrapper which inherits off of PhoneBootstrapper
Caliburn.Micor uses its bootstrapper to do some basic setup and configuration work. It is here that we want to extend this and add support for Ninject and the CommonServiceLocator. Go ahead and create a class as you see below.
public class NinjectBootstrapper : PhoneBootstrapper
{
private NinjectContainer _ninjectContainer;
protected override void Configure()
{
_ninjectContainer = new NinjectContainer(RootFrame);
_ninjectContainer.RegisterDefaultBindings( typeof ( DashboardViewModel ), typeof ( IEventAggregator ) );
_ninjectContainer.RegisterPhoneServices();
var ninjectServiceLocator = new NinjectServiceLocator(_ninjectContainer.Kernel);
ServiceLocator.SetLocatorProvider(() => ninjectServiceLocator);
}
protected override object GetInstance(Type service, string key)
{
if (service != null)
{
return ServiceLocator.Current.GetInstance( service, key );
}
throw new ArgumentNullException("service");
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return ServiceLocator.Current.GetAllInstances( service );
}
protected override void BuildUp(object instance)
{
_ninjectContainer.Kernel.Inject(instance);
}
}
Step 3: Implement the Ninject Setup logic
As you can see from step 2 I have created a class which actually performs all the direct Ninject setup and I called it the NinjectContainer. Below is the code for this container. You should also note 2 more things:
- This container implements IPhoneContainer from Caliburn.Micro
- In the RegisterPhoneServices method this is a very manual rebinding of Caliburn.Micro and it is likely that I missed something. This code was a direct port from the default implementation of the Caliburn IoC container setup.
public class NinjectContainer : IPhoneContainer
{
private readonly IKernel _kernel;
private readonly Frame _rootFrame;
public NinjectContainer( Frame rootFrame )
{
_kernel = new StandardKernel();
_rootFrame = rootFrame;
}
public event Action<object> Activated;
public IKernel Kernel
{
get { return _kernel; }
}
public void RegisterWithPhoneService( Type service, string phoneStateKey, Type implementation )
{}
public void RegisterWithAppSettings( Type service, string appSettingsKey, Type implementation )
{}
public void RegisterPhoneServices(bool treatViewAsLoaded = false)
{
var phoneService = new PhoneApplicationServiceAdapter(_rootFrame);
var navigationService = new FrameAdapter(_rootFrame, treatViewAsLoaded);
Kernel.Rebind<IPhoneContainer>().ToMethod(x => this);
Kernel.Rebind<INavigationService>().ToMethod(x => navigationService);
Kernel.Rebind<IPhoneService>().ToMethod(x => phoneService);
Kernel.Rebind<IEventAggregatorv().To<EventAggregator>().InSingletonScope();
Kernel.Rebind<IWindowManager>().To<WindowManager>().InSingletonScope();
Kernel.Rebind<IVibrateController>().To<SystemVibrateController>().InSingletonScope();
Kernel.Rebind<ISoundEffectPlayer>().To<XnaSoundEffectPlayer>().InSingletonScope();
Kernel.Rebind<StorageCoordinator>().To<StorageCoordinator>().InSingletonScope();
Kernel.Rebind<TaskController>().To<TaskController>().InSingletonScope();
var coordinator = Kernel.Get<StorageCoordinator>();
coordinator.Start();
var taskController = Kernel.Get<TaskController>();
taskController.Start();
}
public void RegisterDefaultBindings( params Type[] typesToScanAssembliesFor )
{
// scan services
Kernel.Scan(x =>
{
foreach ( var type in typesToScanAssembliesFor )
{
x.FromAssemblyContaining( type );
}
x.Where(type => type.Name.EndsWith("Service"));
x.BindWithDefaultConventions();
x.InSingletonScope();
});
// scan the rest
Kernel.Scan(x =>
{
foreach (var type in typesToScanAssembliesFor)
{
x.FromAssemblyContaining(type);
}
x.Where(type => !type.Name.EndsWith("Service"));
x.BindWithDefaultConventions();
});
}
}
Step 4: Hit F5 and see the fruits of your effort
Now if we did everything right your application should not be able to be run and Caliburn.Micro and Ninject (and CSL) as its IoC container.
As you can see you can swap out the built in IoC container w/ Ninject inside of Caliburn.Micro with almost no effort. I hope this helps and others enjoy.
Till next time,
Posted
07-08-2011 4:35 AM
by
Derik Whittaker