Monday, February 11, 2013

WinRT, Caliburn.Micro and IoC - Part 1 (Ninject)

I had been preparing to roll out a Windows 8 Store application (a rewrite of an existing Windows Phone app LEAF Buddy) and was trying to find the best components to use together. I like Caliburn.Micro, and it does have a build for WinRT. Unfortunately its SimpleContainer got even more simple in version 1.4.1, and will not auto-create concrete classes! You have to register everything you ever want to resolve.

This left me in the market for a good IoC for WinRT.  I have used Ninject in the past and really like it.  I have also used Unity.  I could really go either way.  I figured I would see how bad it was to use both and if I ran into any issues further on in my project.  To isolate myslef from any future migration issues I decided I would wrap any IoC I used in an IServiceLocator from the CommonServiceLocator project.  This way it should be fairly trivial to switch (as long as I stuck with constructor injection.

First up was Ninject.  I needed a ServiceLocator wrapper, and a CaliburnApplication wrapper.  Well, first I needed an instance of Ninject that would run on WinRT...  If you do what I did, your first stop was NuGet, which fails...

This was packaged 5/9/2012, so it must have been built with the pre-release WinRT build and .Net 4.5 framework.  The latest release on the Ninject download site is from March, 2012, and has an even earlier version number.

Anyway Remo Gloor has code in a github fork with a start towards a WinRT build. You will have to add a few conditional compile directives and fix up 2 or 3 class references, with the biggest part being disabling lightweight code generation... But, it shouldn't take more than an hour to get it compiling.




Once you get a building Ninject for WinRT, you will also need a IServiceLocator wrapper.  Fear not, they do have a .Net 4.5 build of this.

If you have used Caliburn.Micro before you know you would start with a bootstrapper.  But in WinRT you inherit from CaliburnApplication.  Pull down the project source code and you will see a nice clean WinRT sample HelloWorld.  Which also has instructions and demonstrations of Caliburn.Micro WinRT features.



I have a few issues with the application, mainly that it doesn't demonstrate best practices for state persistence.  Personally I think I will use a combination of SuspensionManager from the VS2012 templates to track frame history and transient data, and custom OnSuspend/OnResume code.

I also wonder if they should have played more nicely with LayoutAwarePage.  It has a nice VisualState management wrapper for orientation change and mouse/keyboard calls.  As well as wrapping up page session state using the SuspensionManager.  It's only real big bugbear is the DefaultViewModel and embedded ObservableDictionary.  I think I will definitely want a new base page inherited from something like this.

Finally, on to the topic at hand.  How do you use Ninject in WinRT and Caliburn.Micro.  Well, since I may well be swapping out IoC in the future, to compare and contrast....  I decided to create an intermediary class CaliburnNinjectApplication (and later CaliburnUnityApplication).  Then use it as I normally would with the rest of the Caliburn.Micro instructions.

Here's what I came up with:




public class CaliburnNinjectApplication : CaliburnApplication
{
    IKernel kernel = new StandardKernel();

    public CaliburnNinjectApplication()
    {
        NinjectServiceLocator locator = new NinjectServiceLocator(kernel);
        ServiceLocator.SetLocatorProvider(() => locator);
        //IKernel gets bound by default.  Let's just add a few other variations
        kernel.Bind().ToConstant(kernel as StandardKernel);
        kernel.Bind().ToConstant(locator);
    }

    protected override void Configure()
    {
        base.Configure();
        //IEventAggregator bind here??
    }

    protected override object GetInstance(Type service, string key)
    {
        if (string.IsNullOrWhiteSpace(key))
        {
            return kernel.Get(service);
        }
        else
        {
            return kernel.Get(service, key);
        }
    }

    protected override IEnumerable<object> GetAllInstances(Type service)
    {
        return kernel.GetAll(service);
    }

    protected override void BuildUp(object instance)
    {
        kernel.Inject(instance);
    }

    protected override void PrepareViewFirst(Windows.UI.Xaml.Controls.Frame rootFrame)
    {
        kernel.Bind().ToConstant(new FrameAdapter(rootFrame));
    }
}

Update: For those interested in a sample implementation where I try to wrap the best aspects of LayoutAwarePage and SuspensionManager into a Caliburn.Micro WinRT project. Check out part 3 of this series.

7 comments:

Nigel Sampson said...

I'd highly recommend using something like LayoutAwarePage with Caliburn Micro (after stripping out that terrible Default View Model stuff)

I hate to call the WinRT sample a "sample app", it wasn't designed as an example of building a fully featured app using Caliburn Micro but demonstrating / testing various features of the framework (and the port). This is why it's missing things like PLM and Visual States.

Long term I'd like add PLM to Caliburn something very similar to the SuspensionManager, although the Frame control really adds a lot of road blocks to this.

Tom Baker said...

What I have ended up doing is registering the rootframe with SuspensionManager, making a trimmed down LayoutAwarePage that defers OnLoad and OnSave to the DataContext if it implements IHaveState, then making a BaseScreen for use in my app that wraps IHaveState and exposes virtual methods OnLoadState and OnSaveState.

I added SuspensionManager.KnownTypes to the OnSaveState method to indicate that you should set any custom types into it for serialization.

I will probably make a post showing how I implemented it later.

Rick Ratayczak said...

I like autofac, which has a portable class library for Winrt. It's my favorite. You may want to include this in your articles too.

Rick

Andy Wilkinson said...

You might be interested in the Okra App Framework (http://okra.codeplex.com) that is designed from the ground up to work great with Windows 8 and MVVM applications. This by default uses MEF for composition but is factored so that you can use whichever IoC container you wish.

It addresses some of the issues you mentioned such as an easy way for view-models to register for suspension/reactivation events (in fact this is abstracted away so your view-model only needs to know what state to save/restore, and the framework handles the when and how). It also works great with the LayoutAwarePage so you can get all the visual-state functionality from here.

One other thing that might peak your interest - the next release is going to include Visual Studio MVVM project & item templates. These are based on the standard VS Windows Store templates, but with real view-models rather than the DefaultViewModel hack. You can see the templates in source control if you can't wait.

Andy

Tom Baker said...

Thanks for your comments about Okra, I will have to look into it.

I have heard about autofac, but haven't tried it. There is a post that shows how to integrate autofac with Caliburn.Micro for WinRT here.

Mostly Anonymous said...
This comment has been removed by the author.
Mostly Anonymous said...

this line
_ninjectKernal.Bind().ToConstant(new FrameAdapter(rootFrame));
as seen in your example was insufficient, I was getting null reference exceptions when it tried to create view/model pairs
I had to be explicit that the rootFrame was the NavigationService
_ninjectKernal.Bind<INavigationService>().ToConstant(new FrameAdapter(rootFrame));


other than that this example was really helpful.

I just had to redo this comment because the system ate the angle brackets for the type on the Bind call, I bet that is what happened in the example.