Bootstrapping Caliburn.Micro with Autofac

It has been about two weeks since my last post in my Learning Caliburn.Micro series, so it far past the time for this post.

Prepare the Project

This post is going to focus on using the Autofac IoC with Caliburn.Micro. From the point of view of the framework it fulfills the same task as the MEF bootstrapper I discussed in Caliburn.Micro the MEFtacluar, or Rob discussed in Caliburn.Micro Soup to Nuts Pt. 2 – Customizing The Bootstrapper. The difference is that it uses Autofac instead of MEF.

By now, you should know how to create a Caliburn.Micro WPF application. If not, please go back to the earlier posts and create a WPF application using standard conventions. I suggest including a logger, such as the DebugLogger to help you see the actions the framework, and the Autofac bootstrapper we will create are taking.

Last, add a reference to Autofac assembly to the project. You do not need the Autofac configuration assembly for this project. However, in your own projects you may want to use it, if you prefer XML based configuration. I tend to use the fluent interface, so I almost never use it. Also, I am using Autofac 2.2.4, as that is the most current version I have. Autofac is a very active project and there tends to be a release every couple of months, but there is rarely any breaking changes in the same major version.

Application BootStrapper class

You should have noticed that every Caliburn.Micro application will have a bootstrapper, so lets start with that class. Instead of working our way up to the to the application bootstrapper, we are going to start with it, just as the framework does.

Our new MyBootStrapper does not look much different from the MEFBootStrapper. We will cover the new base class a little later in the post, but I want to point out the generic argument to it. This replaces the IShell we used to tag our root view model when we used MEF. The other item to notice is the ConfigureContainer method. This override allows you to add your own registrations to the Autofac container during the bootstrapper configuration process.

Lets also ensure the bootstrapper is created. This should pretty familiar by now as well.

The TypedAutofacBootStrapper Class

Time for the class that does all the work. Notice that I called this bootstrapper Typed. That is because it will take the type of view-model explicitly, similar to the generic Bootstrapper<> provided by Caliburn.Micro. You can still use the IShell model if you choose, by I didn’t see the need for another new convention.

Lets dissect the class in detail, and then I will provide the whole thing.

public class TypedAutofacBootStrapper<TRootViewModel> : Bootstrapper<TRootViewModel>
{
  #region Fields
  private readonly ILog _logger = LogManager.GetLog(typeof(TypedAutofacBootStrapper<>));
  private IContainer _container;
  #endregion

  #region Properties
  protected IContainer Container
  {
    get { return _container; }
  }
  #endregion

The class is derived off the generic bootstrapper because I can reuse the DisplayRootView logic with Autofac. I also expose the Autofac container as a protected property so it is directly available in the derived bootstrappers. I choose to do this because I do occasionally use child containers for managing resource bound work, and the Caliburn.Micro IoC abstraction does not provide that functionality. I also did not want to add that functionality since I only use it specific use cases and not as norm.

I override the following four methods.

Configure

protected override void Configure()
{ //  configure container
  var builder = new ContainerBuilder();

  //  register view models
  builder.RegisterAssemblyTypes(AssemblySource.Instance.ToArray())
    //  must be a type that ends with ViewModel
    .Where(type => type.Name.EndsWith("ViewModel"))
    //  must be in a namespace ending with ViewModels
    .Where(type => !(string.IsNullOrWhiteSpace(type.Namespace)) && type.Namespace.EndsWith("ViewModels"))
    //  must implement INotifyPropertyChanged (deriving from PropertyChangedBase will statisfy this)
    .Where(type => type.GetInterface(typeof(INotifyPropertyChanged).Name) != null)
    //  registered as self
    .AsSelf()
    //  always create a new one
    .InstancePerDependency();

  //  register views
  builder.RegisterAssemblyTypes(AssemblySource.Instance.ToArray())
    //  must be a type that ends with View
    .Where(type => type.Name.EndsWith("View"))
    //  must be in a namespace that ends in Views
    .Where(type => !(string.IsNullOrWhiteSpace(type.Namespace)) && type.Namespace.EndsWith("Views"))
    //  registered as self
    .AsSelf()
    //  always create a new one
    .InstancePerDependency();

  //  register the single window manager for this container
  builder.Register<IWindowManager>(c => new WindowManager()).InstancePerLifetimeScope();
  //  register the single event aggregator for this container
  builder.Register<IEventAggregator>(c => new EventAggregator()).InstancePerLifetimeScope();

  ConfigureContainer(builder);

  _container = builder.Build();
}    

This is where the magic of registration happens. Those familiar with Autofac should recognize what is going on, but for those not familiar, here is a break down.

First, I create a ContainerBuilder (line 3) which is the class used to configure an Autofac container. The I register all types found in any of the assemblies in the AssemblySource (remember this one from the MEFBootStrapper?) where the name of the type ends in ‘ViewModel’ and the namespace of the type ends with ‘ViewModels’ and the type implements the INotifyPropertyChangedinterface. In hindsight, I should probably add logic to ensure the type is a non-abstract class with a public constructor, but I will save that for a future post. Line 14 states that the type should be registered as its own type, so a type of ShellViewModel will be registered as ShellViewModel, and not as one of its implemented interfaces. Autofac does allow us to register those as well if we want, but the view-model was all I needed. Line 16 states that Autofac should create a new instance every time I ask for this view-model.

Now that the view-models are registered, we need to register the views. This begins on line 19. I use the same group of assemblies, and this time restrict the types to those with named ending in ‘View’ that are also in namespaces ending in ‘Views’. These are also registered as themselves, and are also newly created every time they are requested from the container. Then we register the WindowManager and EventAggregator services as singletons for that container.

Line 34 is a call to the new virtual method that provides your code a place to register your own types using the ContainerBuilder. The Autofac model favors an immutable container approach, and you should register all your types before the container is built. The container gets built in the next step, and stored in a field.

GetInstance

protected override object GetInstance(Type serviceType, string key)
{
  if (string.IsNullOrWhiteSpace(key))
  {
    if (Container.IsRegistered(serviceType))
      return Container.Resolve(serviceType);
  }
  else
  {
    if (Container.IsRegistered(key, serviceType))
      return Container.Resolve(key, serviceType);
  }
  throw new Exception(string.Format("Could not locate any instances of contract {0}.", key ?? serviceType.Name));
}

This method is how Caliburn.Micro retrieves an instance from the container. No real rocket-science here, just some logic to retrieved unnamed types as well as named typed.

GetAllInstances

protected override IEnumerable<object> GetAllInstances(Type serviceType)
{
  return Container.Resolve(typeof(IEnumerable<>).MakeGenericType(serviceType)) as IEnumerable<object>;
}

Simple wrapper to retrieve a collection of all instances that implement the requested type.

BuildUp

protected override void BuildUp(object instance)
{
  Container.InjectProperties(instance);
}

This method allows the container to inject dependencies into an already created instance. In the case of Autofac, this is restricted to properties.

ConfigureContainer

protected virtual void ConfigureContainer(ContainerBuilder builder)
{
}

This is the virtual method you can override to configure the container.

Full Implementation

Summary

The work to get Autofac configured was not much more than wiring up MEF. I am pretty sure that this process would be similar for most other IoC containers, and I am thinking about doing one more just to prove it. One of the joys of working with Caliburn.Micro is how easy it can be extended, and how quick it is to get up and running. This was no exception.

Now that we have the basic infrastructure out of the way, with logging, MEF, and IoC. The next set of posts will start focusing on feature sets. The posts coming up will be on visualizing collections with lists & trees, eventing, multiple views, master-detail views, and multiple views per view-model.

One project note. I have modified the solution to directly include Caliburn.Micro. A few people requested it, and it really does make it easier to debug into the framework.

As a reminder, all of the source code for this project is located in my Github Learning Caliburn.Micro repository.

Related

comments powered by Disqus