Register workspace specifics in mef catalog

Topics: Bootstrappers & IoC, Extensibility
Jun 1, 2011 at 1:35 PM


I have been refactoring the Caliburn.Micro.HelloScreens sample to show how to decouple it.

Caliburn.Micro.HelloScreens.Framework - class library
Caliburn.Micro.HelloScreens - xap
Workspaces as xaps
Caliburn.Micro.Customers - xap
Caliburn.Micro.Orders - xap

I dynamically download xaps and register them with LoadCatalog recipe. So far so good.
The problem I have is how to add this to the container when the workspace is added

batch.AddExportedValue<Func<CustomerViewModel>>(() => container.GetExportedValue());
batch.AddExportedValue<Func<OrderViewModel>>(() => container.GetExportedValue());

I can't get my head around where/how to add the extension point for the workspace so it may register components to the container itself...
Any ideas?

Br Christoffer

Jun 1, 2011 at 1:53 PM

It would require some redesign to make this work. What you need is a convention that every workspace zap has some class you will locate, to pass the container to, so that it can register whatever it needs. You would need to extend the LoadCatalog result to search the assembly for that special interface and, if found, invoke it. Does that make sense? Something like this:

    public interface IModule
        void Initialize(CompositionContainer container);

Jun 1, 2011 at 2:13 PM

I will try to figure it out. Thanks for input.

Jun 1, 2011 at 2:55 PM

Can't you use MEF itself to export the modules used to register elements?

You can define a proper interface (as Rob suggested) in an assembly shared between the main application and the external ones:

public interface IModule
       void Initialize(CompositionContainer container);

Then, you can export the modules directly using MEF in the external/plugin assemblies

public class MyModule : IModule
      public void Initialize(CompositionContainer container)
              //Do the registering here...

Finally, you call the initialization code as soon as the Container has been initialized and the assemblies are loaded:

IoC.GetAllInstances<IModule>().Apply(x => x.Initialize(container));
Depending on what kind of exports you have to provide, it could be possible to export the VMs directly using MEF, without using the IModule as an 'entry point' (given that the LoadCatalog is able to locate the assemblies containing the objects to export).

Jun 1, 2011 at 4:04 PM

I already created an attribute to export the workspaces

namespace Caliburn.Micro.HelloScreens.Framework
    using System.ComponentModel.Composition;
    using System;

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class ExportWorkspaceAttribute : ExportAttribute
        public ExportWorkspaceAttribute()
            : base(typeof(IWorkspace))
        { }
        public string WorkspaceName { get; set; }

The thing that seem quite obvious when you say it was missing

"Initialize" method... I will add it and see what happens

Jun 1, 2011 at 5:34 PM
Edited Jun 1, 2011 at 6:09 PM


I register the container itself and try to

public CompositionContainer Container { get; set; }

in LoadCatalog but it can't get it :(

Any ideas on how to get hold of the container another way?

EDIT - I can send in a reference when doing new LoadCatalog..

If you do it right instead.. it will work.. getting tired.. but now I get a bit closer :)

Jun 1, 2011 at 6:19 PM

I finally got it working!!!

But I had to struggle with how MEF works to get it right.

Anyway, it was an interesting experiment :).

Jun 1, 2011 at 6:41 PM

For interested.. the "big" steps how I accomplished decoupled workspaces for the Caliburn.Micro.HelloScreens sample

1. Refactored the sample :

Caliburn.Micro.HelloScreens.Framework - class library

Caliburn.Micro.HelloScreens - main xap

Caliburn.Micro.Customers - xap

Caliburn.Micro.Orders - xap

Caliburn.Micro.HelloScreens.Web - hosting web app

2. On the default aspx page added this (ripped from )

<script runat="server">
    public string GetXAPs()
        StringBuilder sb = new StringBuilder();
        System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(Server.MapPath("ClientBin"));
        System.IO.FileInfo[] files = di.GetFiles("*.xap");

        foreach (System.IO.FileInfo file in files)

        ip.Attributes.Add("value", sb.ToString());
        return string.Empty;
directly under 
<% GetXAPs(); %>

and the initParams parameter

<param name="initParams" id="ip" runat="server" />

3. Override the StartRuntime in bootstrapper and add

protected override void StartRuntime()
        public IEnumerable<IResult> DownloadAndAddXAPs()
            return (from keyValue in Application.Current.Host.InitParams 
                    where keyValue.Key != "Caliburn.Micro.HelloScreens.xap" 
                    select new LoadCatalog(keyValue.Key)).Cast<IResult>();

4. In the ShellViewModel and the interface IPartImportsSatisfiedNotification

    public class ShellViewModel : Conductor<IWorkspace>.Collection.OneActive, IShell, IPartImportsSatisfiedNotification

change so the workspaces becomes and lazy import

        private IEnumerable<Lazy<IWorkspace, IWorkspaceMetadata>> _workspaces;
        [ImportMany(typeof(IWorkspace), AllowRecomposition=true)]
        public IEnumerable<Lazy<IWorkspace, IWorkspaceMetadata>> Workspaces
            get { return _workspaces; }
            set { _workspaces = value; }

and instead add the container through constructor injection

        public ShellViewModel(CompositionContainer container)
            _container = container;
            CloseStrategy = new ApplicationCloseStrategy();

5. Implement the IPartImportsSatisfiedNotification

        public void OnImportsSatisfied()
            foreach (var ws in from workspace in Workspaces
                               where Items.Where(item => item.WorkspaceName == workspace.Metadata.WorkspaceName).Count() <= 0
                               select workspace.Value)


Jan 19, 2012 at 7:41 AM

I am trying to debug your  decoupled Caliburn.Micro.HelloScreens solutions, however every breakpoint I put results unreachable.

No way for me to debug LoadCatalog.cs nor ScreensBootstrapper.cs or any other files. I'm launching the application from Visual Studio and then I tryed to attach to every iexplorer process but no way...

Any suggestions? 

Thanks in advance,

Jan 19, 2012 at 9:11 AM


Unfortunately I have no suggestions. This was long ago and an experiment so I don't remember all the details right away... ooh maybe one question.

Have you set the web project as startup?