0 Comments

imageBackground

Keeping the Windows Phone app’s assemblies small is one of the optimization techniques when you want to improve the application’s startup time. As the documentation states:

When you submit your applications to the Windows Phone marketplace, they are signed for verification purposes and the signature is checked each time the application starts. The amount of time it takes to check the assembly signatures increases as the size of the application assemblies increases.

In this tutorial I’ll demonstrate how to split a Windows Phone app into multiple assemblies – which are loaded on-demand, not on startup - when using Caliburn.Micro.

The Problem

If you want to use Caliburn.Micro and you also want to split the app into multiple assemblies, the problems starts with the app’s bootstrapper and IOC. Given the scenario that we have the following assemblies:

  • LoadAssembliesOnDemand – The main assembly, which starts the app.
  • Module1 – Another assembly which will contains views (pages and user controls) and view models
  • Module2 – Another assembly with views and view models

Now if we configure the Caliburn.Micro’s container as we usually do, the code ends up looking like the following:

container.PerRequest<MainPageViewModel>(); 
container.PerRequest<Module1.MyPageViewModel>(); 
container.PerRequest<Module2.AnotherViewModel>();

And here’s the debug output from our app’s startup:

image

As you can see, configuring the container as we did forces the platform to load both the Module1 and Module2 assemblies, even though we don’t need them yet. What we need is a way to configure the IOC container  later, so that the external assemblies are loaded only on-demand.

The Solution

The solution to this problem is to use NuGet to get the following package:

Caliburn.Micro.WP71.Recipes.ExternalModules

This Caliburn.Micro recipe allows you to add assembly-level bootstrappers into your app.

If we get back to our example, with the help of this recipe we can remove all the Module1 and Module2 container initialization code from our main bootstrapper:

container.PerRequest<MainPageViewModel>();

Instead we call the recipe’s Initialize-method. Here’s our bootstrapper’s complete Configure-method:

protected override void Configure() 
{ 
    container = new PhoneContainer(RootFrame); 
    container.RegisterPhoneServices(); 

    container.PerRequest<MainPageViewModel>();

    Caliburn.Micro.WP71.Recipes.ExternalModules.ExternalModuleLoader.Initialize(); 
}

Now we can add the following class into our Module1 project:

public class Bootstrapper : ExternalAssemblyBootstrapper 
{ 
    protected override void ConfigureContainer(PhoneContainer container) 
    { 
        container.PerRequest<MyPageViewModel>(); 
        container.PerRequest<FeaturePageViewModel>(); 
    } 
}

And the following into Module2:

public class Bootstrapper : ExternalAssemblyBootstrapper 
{ 
    protected override void ConfigureContainer(PhoneContainer container) 
    { 
        container.PerRequest<AnotherViewModel>(); 
    } 
}

These bootstrappers are executed only when Caliburn.Micro tries to access the given assembly for the first time. If we now start the app, we can see that Module1 and Module2 aren’t loaded on the startup:

image

Now if we navigate to MyPage.xaml inside the Module1:

navigation.UriFor<MyPageViewModel>() 
    .Navigate();

We can see that the Module1.dll is loaded:

image
Also if add a breakpoint to the Module1’s Bootstrapper, you can see that the code in there is executed. But only once, when you navigate to the page the first time:

image

The bindings between views and view models work as they should:

image

The implementation

The recipe works by making modifications to Caliburn.Micro’s ViewModelLocator and ViewLocator. The modifications gets around the issues caused by Windows Phone platform not allowing the call to Assembly.Load, prohibiting the use of Caliburn.Micro’s built-in SelectAssemblies and AssemblySource capabilities.

There are some limitation with the recipe’s current implementation. For example, it only works with the Caliburn.Micro’s built-in PhoneContainer. Also it expects that the ViewModel class names all end with ViewModel.

Links

The recipe’s source code is available from GitHub. The assembly is available through Nuget.

The example app’s source code is available from the same GitHub repository.