tags:

views:

163

answers:

3

I'm starting to use MEF to build up a plugin based application, and I'm slowly adding MEF to the mix. There is a lot of existing code that does not have any MEF DNA yet, but I still want to get that code into the new objects that are being automatically created by composition.

Let's make this concrete.

I have a list of objects that implement the IFoo interface and operate on the application model in specific but useful ways.

interface IFooCool : IFoo {}

class FooCool : IFooCool {...}

interface IFooAwesome : IFoo {}

class FooAwesome : IFooAwesome {}

IEnumerable<IFoo> fooCollection = ProvidedTheOldFashionWay(not, yet, MEF);

Now, I want to create some useful tools that map the IFooX interfaces to various user actions like menu commands or button clicks.

[Export(ITool)]
class CoolTool : ITool
{
    IFooCool _fooCool;
    [ImportingConstructor]
    CoolTool(IFooCool fooCool) 
    {
        _fooCool = fooCool;
    }

    [Export(MenuAction)]
    void DoSomething() { _fooCool.SomeWork(...); }
}

Here's what I'd like to do:

var batch = new CompositionBatch();
foreach(var foo in fooCollection)
{
    batch.AddPart(foo);  //add those legacy objects to the batch
}

var catalog = new TypeCatalog(typeof(CoolTool));  //or assembly or directory, ...
var container = new CompositionContainer(catalog);

container.Compose(batch);

The CoolTool will be instantiated and the FooCool legacy object will be passed to it. Then I can get the exported functions and display them nicely in the menu and off we go. When the user clicks a menu item, the new CoolTool will use the existing functionality of the IFooCool interface to do something, well, cool.

Of course, that doesn't work. Since the legacy objects are not attributed as exports, adding them to the composition batch does not help. In the code above, I'm adding the foo instances to the batch with batch.AddPart(object) instead of batch.AddPart(ComposablePart). The first method uses the attributed model to discover composable information from the object.

How can I use the second overload? Can I wrap my existing non-MEF object in a ComposablePart on the fly? Something like:

batch.AddPart(CreateComposablePart(typeof(IFooCool), foo));

BTW, I'm use preview 8 in a non-silverlight app.

+1  A: 

And there is a way - sortof.

batch.AddExportedValue(typeof(IFooCool).Fullname, (IFooCool)foo);

Unfortunately, the problem is a little more complex than that. The foo's I need to mark as exports are actually in this:

Dictionary<Type, IFoo> _registeredFoos;

IFooCool      => FooCool
IFooAwesome   => FooAwesome

And, the following (without the IFooCool cast) does not work:

batch.AddExportedValue(typeof(IFooCool).Fullname, foo);

So I really have to loop over the foos like this:

foreach(var entry in _registeredFoos)
{
    batch.AddExportedValue(entry.Key.Fullname, // that was easy...
                          (?)entry.Value);     // what?  This is a generic method...
}

Well, I'll just crack open the source and look what's going on. Great solution right? Knowing and taking advantage of the internal details of a framework function is always a healthy way to develop your apps. I'll do this:

foreach(var entry in _registeredFoos)
{
    string typeIdentity = AttributedModelServices.GetTypeIdentity(entry.Key);
    IDictionary<string, object> metadata = new Dictionary<string, object>();
    metadata.Add(CompositionConstants.ExportTypeIdentityMetadataName, typeIdentity);

    Export export = new Export(entry.Key, metadata, () => entry.Value);
    batch.AddExport(export);
}

Sure. Now I need to go take a shower.

Daver
It looks like you figured out how to do this, but you're not very happy with the ugly code you had to write. Is this correct?
Daniel Plaisted
It's not that code is ugly, it just seems like I had to use details that might change in future implementations and should have been hidden (from me anyways). And I always hate duplicating code.
Daver
+1  A: 

It looks like you are trying to call this extension method:

AttributedModelServices.AddExportedValue<T>(
   this CompositionBatch batch,
   string contractName,
   T exportedValue);

Your problem is that you only know the type parameter T at runtime; it's the key of your dictionary. One solution could be to use reflection to call the method, which enables you to fill in type parameters at runtime. First, get the MethodInfo of the generic method like this:

MethodInfo genericAddExportedValue = 
   typeof(AttributedModelServices).GetMethods()
   .Where(x=>x.Name == "AddExportedValue")
   .First(x=>x.GetParameters().Count() == 3);

Now you can close the type parameter inside your loop and invoke the method:

foreach(var entry in _registeredFoos)       
{       
    MethodInfo addExportedValue = 
       genericAddExportedValue.MakeGenericMethod(entry.Key);
    addExportedValue.Invoke(
       null,
       new object[] {batch, entry.Key.FullName, entry.Value});
}

Alternatively, you could create an implemention of the abstract ExportProvider class which knows how to use your _registeredFoos dictionary to provide exports. But that's probably a lot more work.

Wim Coenen
Yep. That works. I was hoping to avoid reflection, but this technique does avoid breaking encapsulation. Thanks
Daver
I agree that it is unfortunate that reflection is required. It would be better if there was an non-generic overload which takes a `Type` object instead of a type parameter.
Wim Coenen
That's for sure. I just posted in the MEF discussion group about this issue. Hopefully, they can add the method(s) in the near future.
Daver
A: 

To use legacy classes without attributes as MEF parts, you can use the ConfigurableDefinitionProvider which is part of MEFContrib. This allows you to define the imports and imports with a configuration file instead of attributes.

(Your question as clarified by your own answer is actually how to add parts which you already have available as a Dictionary<Type,object>, but I figured it might also be interesting to answer the simpler question suggested by the question title.)

Wim Coenen