Caliburn.Micro the MEFtacluar

Categories: Programming
Comments: 2 Comments
Published on: August 4, 2010

I was going to call this post ‘How I learned to stop worrying and love MEF.’, but I am not sure enough people would get the reference. Besides, it would put the focus too much on MEF, and not on Caliburn.Micro, which is the topic of the post.

This is the second in my series of using Caliburn.Micro for WPF applications. The first post, Caliburn.Micro – Hello World, covered an introduction to setting up a project, and the basics of view and view-model interaction. This week I am going to introduce how to make your applications more loosely coupled by leveraging MEF. As this series progresses, we will be leveraging MEF more, so I wanted to cover it early on.

Rob Eisenberg posted a MEFbootstrapper in the Support for MEF discussion on the Caliburn.Micro CodePlex site, and on his blog Caliburn.Micro Soup to Nuts Pt. 2 – Customizing The Bootstrapper. This article starts with a new project copied from the Hello World project of my last post. We then add Rob’s bootstrapper, and modify the project to demonstrate its use.

Before adding the MEF support, lets add a new interface called IShell. This will be the contract used by MEF to find our view-models. I place my contracts in a Contracts directory, but that is up to you. Here is the code to IShell. Right now, it is just a placeholder, but later on we will use for some more interesting things.

1 namespace HelloMef.Contracts 2 { 3 public interface IShell 4 { 5 } 6 }

So now on to MEF. First thing we need to do when we use MEF is reference System.ComponentModel.Composition. So go ahead and add that reference. Next, we need the MEFBootStrapper. Once again all the hard work has been done by Rob. However, the one he has on his blog, and in the Caliburn.Micro discussion forum works with Silverlight, but it does not work as is with WPF. I will post my version, then explain the difference.

1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel.Composition; 4 using System.ComponentModel.Composition.Hosting; 5 using System.ComponentModel.Composition.Primitives; 6 using System.Linq; 7 using System.Reflection; 8 using Caliburn.Micro; 9 using HelloMef.Contracts; 10 11 namespace HelloMef 12 { 13 public class MefBootStrapper : Bootstrapper<IShell> 14 { 15 #region Fields 16 private CompositionContainer _container; 17 #endregion 18 19 #region Overrides 20 protected override void Configure() 21 { // configure container 22 #if SILVERLIGHT 23 _container = CompositionHost.Initialize( 24 new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>())); 25 #else 26 _container = new CompositionContainer( 27 new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>())); 28 #endif 29 30 var batch = new CompositionBatch(); 31 32 batch.AddExportedValue<IWindowManager>(new WindowManager()); 33 batch.AddExportedValue<IEventAggregator>(new EventAggregator()); 34 batch.AddExportedValue(_container); 35 36 _container.Compose(batch); 37 } 38 protected override object GetInstance(Type serviceType, string key) 39 { 40 string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key; 41 var exports = _container.GetExportedValues<object>(contract); 42 43 if (exports.Count() > 0) 44 return exports.First(); 45 46 throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract)); 47 } 48 protected override IEnumerable<object> GetAllInstances(Type serviceType) 49 { 50 return _container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType)); 51 } 52 protected override void BuildUp(object instance) 53 { 54 _container.SatisfyImportsOnce(instance); 55 } 56 #endregion 57 } 58 }

So here is the difference, beyond the use of regions and my own personal coding style of using a leading underscore for fields. Lines 22-28 are a little different. Here is the original version that Rob posted:

1 _container = CompositionHost.Initialize( 2 new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()));

In Silverlight, MEF is initialized using CompositionHost.Initialize(), and in WPF we new up a CompositionContainer. To fix this, I just wrapped Rob’s original code in a Silverlight compile time check. While this class is not part of the Caliburn.Micro, it is first on my list for addition into a Caliburn.Micro.Contrib project.

We are almost there. Three more changes, and we will be a MEF enabled application. First, lets open up the MainViewModel.cs file. We need to add the IShell type as a base interface for the MainViewModel class. Then we need to add the MEF attribute to export the MainViewModel as type IShell. Here is the new version of MainViewModel.

1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel.Composition; 4 using System.Linq; 5 using System.Text; 6 using Caliburn.Micro; 7 using HelloMef.Contracts; 8 9 namespace HelloMef.ViewModels 10 { 11 [Export(typeof(IShell))] 12 public class MainViewModel : PropertyChangedBase, IShell 13 { 14 private string _name; 15 private string _helloString; 16 17 public string Name 18 { 19 get { return _name; } 20 set 21 { 22 _name = value; 23 NotifyOfPropertyChange(() => Name); 24 NotifyOfPropertyChange(() => CanSayHello); 25 } 26 } 27 public string HelloString 28 { 29 get { return _helloString; } 30 private set 31 { 32 _helloString = value; 33 NotifyOfPropertyChange(() => HelloString); 34 } 35 } 36 public bool CanSayHello 37 { 38 get { return !string.IsNullOrWhiteSpace(Name); } 39 } 40 public void SayHello(string name) 41 { 42 HelloString = string.Format("Hello {0}.", Name); 43 } 44 } 45 }

Finally, open up App.xaml. Remember back in Caliburn.Micro – Hello World we started the framework by having the application class instantiate our bootstrapper class on start up. We need to change the name of the bootstrapper class from HelloWorldBootStrapper to MefBootStrapper. Here is the new App.xaml:

1 <Application x:Class="HelloMef.App" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:local="clr-namespace:HelloMef"> 5 <Application.Resources> 6 <ResourceDictionary> 7 <ResourceDictionary.MergedDictionaries> 8 <ResourceDictionary> 9 <local:MefBootStrapper x:Key="bootstrapper" /> 10 </ResourceDictionary> 11 </ResourceDictionary.MergedDictionaries> 12 </ResourceDictionary> 13 </Application.Resources> 14 </Application>

That’s it. We are done. Build and run the application and you should see the familiar program we did in the last post. The difference is that our coupling has been reduced so that the framework can now find our main view model by the contract IShell, and from there it can start our application. Over the next few posts we will add logging support, multiple views, third-party controls, and maybe a surprise or two. So stay tuned.

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

Technorati Tags: ,,,

Kick It on DotNetKicks.com
Share this
2 Comments - Leave a comment
  1. Steve Ghattas-Smith says:

    Thanks for the sample. I added this mod to the Configure() to support an MEF directory catalog for plug-ins as well:

    _container = new CompositionContainer(
    new AggregateCatalog(
    AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType()
    .Union(new List() { new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory) })
    )

  2. XOS says:

    Great post.

    How would you load in external assemblies. With the SelectAssemblies method?

Leave a comment

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>


Welcome , today is Wednesday, June 19, 2013
Statistical data collected by Statpress SEOlution (blogcraft).