MEF+Composite Application Guidance for WPF (Prism)

Jan 8, 2009 at 4:58 PM
Hi Glenn/all, 

I’m attempting to get MEF+Prism working together with a bit of guidance from the MEFContrib project – have noted you’re doing a demo yourselves within the MEF team but in the meantime I’m giving it a crack. 

Here’s my tack: I’m hoping to build a MEF catalog of Prism IModules (from Microsoft.Practices.Composite.Modularity), injecting each one with an IRegionManager (again via MEF import/export) so when they instantiate everything works as normal for Prism – correct me if this is an entirely off-the-mark tactic. (Conversely, if I’m on the right track and you have an exact how-to then that would also be appreciated!) 

I’ve got to the stage where I’m trying to Export & Import a module implementing IModules using MEF but I’m getting a “No exports were found that match the constraint” whenever I reference anything from Microsoft.Practices.Composite in the Module (ie. if I create my own interface it works). 

Into the code ... here’s the test:  

[Import]
public IModule2 Module { get; set; }

[TestMethod]
public void using_mef_load_directory_should_find_module()
{
    AggregatingComposablePartCatalog catalog = new AggregatingComposablePartCatalog();

    var path = @"<some valid path>";
    catalog.Catalogs.Add(new DirectoryPartCatalog(path));
    CompositionContainer container = new CompositionContainer(catalog);

    container.AddPart(this);
    container.Compose();
    Assert.IsNotNull(Module);
}

This works: 

public interface IModule2 { void Initialize(); }

[Export(typeof(IModule2))]
public class BuildModule : IModule2
{
    public BuildModule(){}

    public void Initialize() { }
}

This doesn’t (changing the type of the Module in the Test to public IModule Module { get; set; } ) 

[Module(ModuleName = "BuildModule")]
[Export(typeof(IModule))]
public class BuildModule : IModule
{
    public BuildModule(IRegionManager regionManager)
    {
        this.RegionManager = regionManager;
    }

    [Import(typeof(IRegionManager))]
    public IRegionManager RegionManager { get; set; }

    public void Initialize()
    {
        RegionManager.Regions[RegionNames.BuildNavigatorRegion].Add(new BuildNavigatorView());
    }
}

Any ideas/suggestions would be greatly appreciated! 


Jan 8, 2009 at 5:23 PM
Update: 

I have just got this working using CAL/Prism alone, ie. not decorating the IModule with a MEF Export attribute, and adding a DirectoryLookupModuleEnumerator(ModulesPath) to the GetModuleEnumerator() in the MEFBootStrapper provided by MEFContrib

protected override IModuleEnumerator GetModuleEnumerator()
{
    if (!string.IsNullOrEmpty(ModulesPath))
    {
        var enumerator = new DirectoryLookupModuleEnumerator(ModulesPath);
        return enumerator;
    }
    else
    {
        return LoadReferencedModules.GetModules(_application.MainWindow.GetType());
    }
}



Jan 8, 2009 at 6:40 PM
Can we add you to the MEFContrib project so that you can upload your work for the benefit of the Community?   I understand these will become obsolete/redundant (and probably removed) when MEF is officially in Prism/CompositeWPF but until then it could be beneficial for those needing it.

Bill
Coordinator
Jan 8, 2009 at 7:19 PM
The problem is that you don't have a default constructor.  You don't need to have one: you can add an [ImportingConstructor] attribute to the constructor, and the constructor arguments will be treated as imports.  If you do this then you wouldn't need to put the import on the RegionManager property.

You could also add a default constructor to the class and leave it as is.

Daniel
Jan 8, 2009 at 8:42 PM
Hi @znite

That can work, but its not really taking advantage of MEF. 

MEF includes catalogs as a means for discovering parts. In Prism we developed a very specific mechanism for scanning assemblies and locating IModules. With MEF catalogs are generic, they can be scanning directories, invoking web services to get remote parts, etc. Also with catalogs you can take advantage of recomposition, which means that the app is notified as new parts / modules appear in the catalog.

To use MEF to it's fullest, I'd recommend having a MEF Module Loader, that uses catalogs for locating modules.

Glenn

Jan 8, 2009 at 8:51 PM
Edited Jan 8, 2009 at 9:29 PM
One other interesting door that using MEF opens up for module loading is sub modules. For example I could have the following.

[Export(typeof(IModule))
public class MainModule : IModule {
  [ImportingConstructor]
  public MainModule(IEnumerable<IModule<MainModule>> subModules) [
  }
}

[Export(typeof(IModule<MainModule))]
public class MainSubModuleA : IModule<MainModule> {
}

[Export(typeof(IModule<MainModule))]
public class MainSubModuleB : IModule<MainModule> {
}

So now, when MainModule is loaded, it will get sub modules imported in as well. This could be useful for breaking up a monolithic module into smaller pieces, that are still packaged together, it can also allow you to further extend existing modules.

Glenn



Jan 9, 2009 at 7:59 PM
Cheers Glenn & everyone for responses, I believe I have a solution. Bill - happy to add this to MEFContrib if you'd like. Here's the code

BootStrapper.cs:
[Import(typeof(IModule))] public IEnumerable<IModule> Modules { get; set; }
[Export] public IRegionManager RegionManager { get; set; }

protected override void InitializeModules()
{
    RegionManager = Container.Resolve<IRegionManager>();
    ComposeMef();
    Modules.ForEach(m => m.Initialize());
}

private void ComposeMef()
{
    var catalog = new AggregatingComposablePartCatalog();
    catalog.Catalogs.Add(new DirectoryPartCatalog("."));
    var container = new CompositionContainer(catalog);
    container.AddPart(this);
    container.Compose();
}
The rest:
[Export(typeof(IModule))]
public class BuildModule : IModule
{
    [Import] public IRegionManager RegionManager { get; set; }
    [Import] public BuildNavigatorViewModel BuildNavigatorViewModel { get; set; }
    public void Initialize() { RegionManager.Regions[Constants.RegionNames.BuildNavigatorRegion].Add(BuildNavigatorViewModel.View);     }
}
[Export]
public class BuildNavigatorViewModel
{
    public IBuildNavigator BuildNavigatorService { get; set; }
    public IBuildNavigatorView View { get; set; }
    public List<string> BuildServers { get; set; }

    [ImportingConstructor]
    public BuildNavigatorViewModel(IBuildNavigator buildNavigatorService, IBuildNavigatorView view)
    {
        BuildNavigatorService = buildNavigatorService;
        View = view;
        BuildServers = BuildNavigatorService.GetTeams().ToList();
        // Need to hit to the view to load viewmodel as datacontext as we're instantiating viewmodel outside the view
        View.LoadData(this);
    }
}
and I have an [Export(typeof(IBuildNavigator))] on my BuildNavigator.

Any tips/code review appreciated.

Cheers,
Andy

 


Jan 9, 2009 at 9:00 PM
[znite] Bill - happy to add this to MEFContrib if you'd like.

You are signed up as a developer, please don't hesitate to contact me (bill@global-webnet.com) if you need any assistance getting hooked up to the source code.  At your convenience, if you could put together a small demo (doesn't have to be fancy) with some documentation/comments on what it does -  it would be great!