WPF Discover internal and external modules

Dec 10, 2010 at 3:02 AM

I was looking a the bootstrapper example you have.  The problem I am running into is that I cannot get imports in its own assembly and ones dropped into my module folder.  How would that be done?  I basically want to do this, which is plain MEF:

private string ModuleDirectory = @"C:\Projects\DMF\DMF.WPF\Modules"
var objCatalog = new AggregateCatalog();
var objAssembly = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly());
objCatalog.Catalogs.Add(objAssembly);
objCatalog.Catalogs.Add(new DirectoryCatalog(ModuleDirectory));
var container = new CompositionContainer(objCatalog);
container.ComposeParts(this);

 


Dec 10, 2010 at 8:42 AM

My container is initialized this way

var container = new CompositionContainer(new AggregateCatalog(new DirectoryCatalog("My modules directory"), 
                                         new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType())))

which is more or less the same, I suppose... note that AssemblySource.Instance returns the application entry assembly.

By the way, instead of using Assembly.GetExecutingAssembly(), have you tried using Assembly.GetEntryAssembly() or something like GetType().Assembly?


Dec 10, 2010 at 10:58 AM

It should work with something like (in the Configure override):

 

       container = CompositionHost.Initialize(
            new AggregateCatalog(
                AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()
					.Concat( new ComposablePartCatalog[] { new DirectoryCatalog(ModuleDirectory } )
                )
            );

        var batch = new CompositionBatch();

        batch.AddExportedValue<IWindowManager>(new WindowManager());
        batch.AddExportedValue<IEventAggregator>(new EventAggregator());
        batch.AddExportedValue(container);

        container.Compose(batch);
     

You also have to override Bootstrapper.SelectAssemblies if you want to take statically referenced assemblies into account as well.

Dec 10, 2010 at 6:54 PM

Thanks for the response.  I put that in, but for some reason it is not picking up my Imports for an external assembly in my Modules folder.  But it picks up an Export!.  It shows it as an Export when I view the catalog.  I wonder if it has to do with that I am importing IHost (HostViewModel) as the first window, which in turn imports IShell (ShellViewModel) to actually show content in the HostViewModel.  So this is my bootstrapper:

  private CompositionContainer container;
        private string ModuleDirectory = @"C:\Projects\DMF\DMF.WPF\Modules";
        protected override void Configure() {
            container = new CompositionContainer(
                new AggregateCatalog(
                AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()
     .Concat( new ComposablePartCatalog[] { new DirectoryCatalog(ModuleDirectory) }))
                );

Now in my HostViewModel (IHost) I am

   [Export]
    public class HostViewModel: IHost
    {
        public HostViewModel()
        {
            Console.Write(TestShell.ToString());
        }
        //Hack for putting breakpoint to see if TestShell has anything in it
        [Import]
        public DMF.WPF.Common.IShell TestShell { get; set; }
    } 

 But TestShell is always null, and it will crash telling me it cannot find IHost.  What is odd is that if I comment out the {Import], I get no error.


Dec 10, 2010 at 10:46 PM
Edited Dec 10, 2010 at 10:46 PM

Can you provide a sample code? I fear I miss something in your scenario.

The [Import] should identify the need for a DMF.WPF.Common.IShell, which I suppose is exported in your module. But then you say that it (the application?) crashes, since it cannot find IHost. Now, from the code you provided you have not exported IHost, but HostViewModel, so if another object has an [Import] over IHost, it will not find it.

This means that if DMF.WPF.Common.IShell has an [Import] over IHost, composition will fail, since there is no

 

[Export(typeof(IHost))]

 

If the scenario I pictured is correct (HostViewModel imports DMF.WPF.Common.IShell and DMF.WPF.Common.IShell imports IHost), then the reported error is indeed correct, since no IHost is ever exported.

Dec 11, 2010 at 12:27 AM

Thanks for the response again.   I had changed things a bit to use the direct class instead of the interface so that is why my code posted was inconsistent with what I was saying.  But I will put here how it is now:

1) External Project Assembly that uses IShell common interface

[Export(typeof(DMF.WPF.Common.IShell))]
    public class ShellViewModel : DMF.WPF.Common.IShell    {
    }

2) CM Bootstrapper, which is using the IHost inferface to load the HostViewModel, which it does fine:

public class MefBootstrapper : Bootstrapper<IHost>
    {
        private CompositionContainer container;
        private string ModuleDirectory = @"C:\Projects\DMF\DMF.WPF\Modules";
        protected override void Configure() {
            container = new CompositionContainer(
                new AggregateCatalog(
                AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()
     .Concat( new ComposablePartCatalog[] { new DirectoryCatalog(ModuleDirectory) }))
                );

            var batch = new CompositionBatch();

            batch.AddExportedValue<IWindowManager>(new WindowManager());
            batch.AddExportedValue<IEventAggregator>(new EventAggregator());
            batch.AddExportedValue(container);
            container.Compose(batch);
        }

3) Now in my HostViewModel, I am trying to Import the IShell, which my external assembly in Step One uses:

   [Export(typeof(IHost))]
    public class HostViewModel: IHost
    {
        [Import]
        public DMF.WPF.Common.IShell TestShell { get; set; }

        public HostViewModel()
        {
            //Dummy to put breakpoint to see  if import worked
            Console.Write(TestShell.ToString());
        }
    }

So what is bizarre is that if I put a breakpoint at the end  of the Bootstrapper.Configure override and look at the catalog, there are two parts.  IHost shows one import and one export.  IShell shows one Export, but no import.  And I cannot figure out why it would read the Export in the external assembly but not the Import in its own assembly.

Dec 11, 2010 at 12:45 AM

Uhmmm, maybe I still miss something (or it's just the late hour), but in the provided code I can just see

  • 1 export for DMF.WPF.Common.IShell
  • 1 export for IHost
  • 1 import inside HostViewModel requesting DMF.WPF.Common.IShell

So, unless there is an [Import] in ShellViewModel, I think that the content of the Catalog during when you hit the breakpoint is pretty much correct.

Now, is there some more code in the IShell that requests an import and was omitted?

Dec 11, 2010 at 5:15 AM

Maybe I am not understanding how MEF works then.  The IHost (HostViewModel) loads fine.  As you pointed out, there is both an import and export for IShell.  So why would my catalog say it only finds an export of IShell?  That is the part I cannot get.  Yet, HostViewModel (IHost) works fine!

Here is basically what I am trying to do.  I want the base project to be nothing more than loading the catalog and a HostViewModel(IHost) with an empty ContentControl. That way,  all the content of the project really is in an external assembly, which is ShellViewModel (IShell).  So the HostViewModel (IHost) works fine, but I can never get an import of ShellViewModel (IShell).

Should I be doing this differently then?

Dec 11, 2010 at 12:05 PM

Uhmmm, maybe a repro-project would help.

I am currently working on a similar scenario (the Shell is just a host for dockable windows, child windows and specific UI components that get discovered at runtime) but I am not yet at the point of injecting the sub-view-model inside the shell. If I reach that spot sometime soon - and everything works - I will post the code here. In the meantime can you check if imports are working for other view-models declared in the module assembly?

Dec 13, 2010 at 5:27 PM

yeah, let me know how that works for you.  What I find is that no matter how many external DLLs I put in my Modules directory, when I set a break in the Bootstrapper and check the Catalog, there is always an Export detected in the External DLL but never an Import detected in the host project, no matter what interface I use or where I set  the Import

Dec 13, 2010 at 10:53 PM
Edited Dec 13, 2010 at 11:26 PM

Ok, I resovled this and I wanted to pass this along to anyone that might have struggled with this.  What I was missing was the ImportingConstructor.  Trying to use the Property simple wasn't working:

    [Export(typeof(IHost))]
    public class HostViewModel: IHost
    {
      [Import(typeof(IStatusBar))]
        public IStatusBar TestStatusBar { get; set; }

        [ImportingConstructor]
        public HostViewModel(IShell mainshell)
        {
            Console.Write(mainshell.ToString());
        }
    }