Daver
Daver

Reputation: 503

Getting legacy objects to be imported when they are not attributed as Exports in MEF

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.

Upvotes: 3

Views: 633

Answers (3)

Wim Coenen
Wim Coenen

Reputation: 66753

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.)

Upvotes: 0

Wim Coenen
Wim Coenen

Reputation: 66753

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.

Upvotes: 1

Daver
Daver

Reputation: 503

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.

Upvotes: 1

Related Questions