How to declare Lazy ImportMany in this case?

Aug 10, 2010 at 4:26 AM
Edited Aug 10, 2010 at 6:44 AM

I have several assemblies containing exported WPF classes declared like this:

namespace Module3
{

    [Export("CustomBarTypes")]
    [ExportMetadata("CustomBar", "Bar3")]
    public partial class Bar : UserControl
    {
     ....
    }
...

namespace Module4
{

   [Export("CustomBarTypes")]
   [ExportMetadata("CustomBar", "Bar4")]
   public partial class Bar : UserControl
   {
     ....
   }

How can I declare a Lazy ImportMany collection in order to get these exports imported? 

I am trying to avoid creating another assembly with the sole purpose of defining some dummy interface
so that all Bars could inherit from it. Thus I am exporting using the string "CustomBarTypes".
Am I stuck with explicitly calling:
var bars = container.GetExports<object, object>("CustomBarTypes");
?     
 

 

Aug 10, 2010 at 4:31 PM

That's a shame, you shouldn't have to result to plain objects, strongly typed is the way to go, but you need a common denominator for that.

MEF uses both the contract name as well as the type identity to satisfy imports, both need to be specified for this to work. Try exporting like this:

[Export("CustomBarTypes", typeof(UserControl))]

...and importing like this...

[ImportMany("CustomBarTypes")]
IEnumerable<UserControl> Bars { get; set; }

Aug 10, 2010 at 7:07 PM
Edited Aug 10, 2010 at 10:07 PM

Unfortunately that didn't work either. Bars remains null. I was never sure if MEF is supposed to create empty IEnumerable. But even when I initialized it using new List<UserControl>();  it remains empty, nothing is added as an imported export. If it worked I would still need metadata. So I'm afraid that creating a custom attribute or common interface is the only way to be typesafe.

The following worked:

 IEnumerable<Lazy<UserControl, IDictionary<string, object>>> Bars { get; set; }

but I had to explicitly initialize it with the following call:

    Bars = container.GetExports<UserControl, IDictionary<string, object>>("CustomBarTypes");

as ImportMany would do nothing.

Aug 10, 2010 at 7:32 PM
Edited Aug 10, 2010 at 7:50 PM
Is the object that contains your [ImportMany] declaration of CustomBarTypes getting composed by the CompositionContainer? If it's not then Bars would be null as the containing object hasn't been told to compose itself.
Aug 10, 2010 at 10:12 PM
Edited Aug 10, 2010 at 10:33 PM

Ah, now I understand why in all examples with [ImportMany] the containing object is also exported. So I guess it would get composited if I add [Export] declaration, even if nobody will actually use that export to import it somewhere else? Or is there some other way to tell it to compose itself?

[Later edit]

This is what did it:

     var batch = new CompositionBatch();
     batch.AddPart(this);
     container.Compose(batch);

Aug 11, 2010 at 12:16 AM

The [Export] would do nothing unless you were using the class somewhere else as an Import.

When you tell the container to compose an object it goes down through the object and satisfies all its imports. And if those imports also have imports it continues down the graph until all parts are satified. It's a bit like a Treeview.

An object is not smart enough to know what to do with the Import and Export attributes which is what the container and its catalogs are used for. The container will try to satisfy the imports on an object you tell it to by using the catalog you provide it to find those imports and exports.

The examples out there don't really tell you this or maybe it's implied but it wasn't something I picked up on straight away either.

Aug 11, 2010 at 7:37 AM
Edited Aug 11, 2010 at 7:41 AM

To get this, I suggest you fire up a small test project with the sole purpose of working out these things. Assert expected behavior in isolation. I do this, and it does help me understand where MEF does what (besides reading the source).

Maybe this will clear up the export/import confusion a bit.

The basic MEF objects are Catalogs, Containers and Parts. Each part can have several export or import definitions. However, only exports can be created within the container.

When you ask the container for a part you do this by specifying a contract name (this is just a string that defaults to something which looks like a type name). You'll always get back an export or exception (the many situation returns an non-empty collection or an empty collection) but either way never null (unless you change your import definition to allow the default value). But before you gain access to the created export, the import definitions of the part are all satisfied. If this is not possible the part is rejected (this is called stable composition). All this occurs within the container.

Some times you don't create the part through the container, and when that happens you need to tell the container to satisfy the imports yourself.

var batch = new CompositionBatch();
batch.AddPart(this);
container.Compose(batch);

That snippet actually does two things. It searches the part for import definitions and ensures that they are satisfied but also if the part itself is an export it will put that instance into the container (it now being available for satisfying other imports). If your part is an export and you compose it you also add it to the container. Normally you add parts through catalogs, but it can be done this way as well.

Make sure you are using the namespace System.ComponentModel.Composition and try the extension method SatisfyImportsOnce provided by the AttributedModelServices.

container.SatisfyImportsOnce(this);

If you have a type (no export attribute) with imports and you want them to be satisfied that's the call you wanna make. Pass an instance of some type which has import definitions and see that it will either throw an exception or have non null values for those imports.

Aug 11, 2010 at 4:54 PM
Edited Aug 12, 2010 at 3:50 PM

This clarifies it enough to make me feel like I'm beginning to understand it. Thanks to both of you guys!

I am using SatisfyImportsOnce because after adding the part I became concerned about its lifetime and tried to remove it later in the code. However, RemovePart can't work on this because it has no overload taking object parameter like AddPart does.  Now I understand that my type, which has no export attribute, won't be put in container when imports are satisfied and I need not worry about something holding an extra reference to it.