views:

484

answers:

1

UPDATE

As I've tried to get MEF working throughout my application, I'm coming across more an more places where I just don't get why it's not automatically creating my library when I expect it to. I think it all comes back to what Reed was saying about needing MEF to create everything. So right now, I have an XML reader class that needs to use my CandySettings, but even though its ICandySettings property has the [Import] attribute, it doesn't get imported. First I found out that [Import] doesn't work on statics, so I changed this. But after that it still didn't work. I think it's because I manually create the XML reader object, and what MEF wants me to do instead is to [Import] the XML reader... which means that I now have to have an interface for that as well.

It's almost like using IoC (or for MEF, at least), it's an all-or-nothing affair. You can't just arbitrarily use it here and there, because ultimately whatever class you want to inject properties into also needs to be created by MEF.

Please correct me if I am wrong!


Original post

Well, it's not THAT bad yet. :) But I do have questions after Reed has pointed me at MEF as a potential alternative to IoC (and so far it does look pretty good).

Consider the following model: alt text

As you can see, I have an App, and this app uses Plugins (whoops, missed that association!). Both the App and Plugins require usage of an object of type CandySettings, which is found in yet another assembly.

I first tried to use the ComposeParts method in MEF, but the only way I could get this to work was to do something like this in the plugin code.

var container = new CompositionContainer();
container.ComposeParts(this, new CandySettings());

But this doesn't make any sense, because why would I want to create the instance of CandySettings in the plugin? It should be in the App. But if I put it in the App code, then the Plugin doesn't magically figure out how to get at ICandySettings, even though I am using [Import] in the plugin, and [Export] in CandySettings. EDIT (probably because I should be calling ComposeParts() from the App and then passing it the plugin?)

The way I did it was to use MEF's DirectoryCatalog, because this allows the plugin, when constructed, to scan all of the assemblies in the current folder and automagically import everything that is marked with the [Import] attribute. So it looks like this, and potentially in every plugin:

var catalog = new DirectoryCatalog(".");
var container = new CompositionContainer(catalog);
container.ComposeParts(this);

This totally works great, but I can't help but think that this is not how MEF was intended to be used?

+5  A: 

The "trick" here is that you want to have MEF create your plugins for you.

The way you'll do this is to have your Application compose itself, with the Plugin types specified:

class PluginRepository
{
    [ImportMany(typeof(IPlugin))]
    IEnumerable<IPlugin> Plugins { get; set; }
}

If you do this, and have MEF Compose your "repository" class, MEF will construct the objects. It'll then automatically Compose those as it constructs them, so ICandySettings will get composed without any intervention for you.

You only need to manually "compose" an object if MEF isn't constructing it for you.

Reed Copsey
Ok, I will give that a try, thanks! If that works in the test app, then the likely end result in the real app is to replace my plugin loader (the one that currently uses Reflection) with code like yours, right?
Dave
@Dave: Yep. You don't really need to use reflection at all. MEF makes this extremely simple, very reliable, and very short on the code....
Reed Copsey
@Reed: Ok, so I had my App [Import] my plugin interface. My App also uses the DirectoryCatalog to look for all assembly's imports. My Plugin [Import]s ICandySettings, and my CandySettings library exports via the [Export(typeof(ICandySettings))] attribute. I stepped through the code, and in the App, I confirmed that the CandySettings object and Plugin object get instantiated. But the Plugin's constructor needs CandySettings, and at the point of construction CandySettings does not get resolved... is this a limitation or do you think I wired something wrong? I get a null exception.
Dave
@Reed: Just for fun, I went ahead and moved my usage of CandySettings until after plugin construction, and now it works! So I guess MEF has the same limitation as Unity, in that you can't expect property dependencies to get injected in the constructor. I should probably consider changing this to constructor injection, I assume?
Dave
@Dave: Yes. With MEF, you can have the constructor use [ImportingConstructor] and just have ICandySettings be a parameter. This makes MEF construct the CandySettings first, and pass it to the constructor for you.
Reed Copsey
@Reed: thanks again for the response, that totally worked great!
Dave
@Reed: one more question (sorry!) -- I should just do this and see if it works, but doesn't it now make sense to put all interfaces into one assembly, so that potential outside users can just be given that assembly, and not need the concrete class to work with? So ICandySettings should **not** be in the same assembly as CandySettings?
Dave
@Dave: For plugins, yes. Having the interfaces or base classes in a "library" assembly makes life nicer.
Reed Copsey
@Reed: okie dokie. I'll give that a whirl and will change my libraries around. I'm still having issues really understanding how to make things easier with MEF. It does seem a bit all or nothing, but I'll wait until I work with it a little more before I ask more questions. Thanks for the suggestion, it is a promising framework for me.
Dave
@Reed: I'm really good at getting more and more errors as I work with MEF. :) Is my intuition correct about the first problem in my updated post above? Do you understand why my proxy solution wouldn't work?
Dave
@Dave: difficult to tell what's happening - in general, I'd recommend starting a new question (not editing) in this situation, btw. That being said, my guess is that it has to do with your app trying to import and export types simulatenously, but not be constructed via MEF. In any case, I'd recommend having your IError implementation be a separate class (plugin?) that is injected appropriately where needed. You might wnat to look at CompositionInitializer, too - it makes some of these types of issues MUCH simpler, especially since you're working with WPF...
Reed Copsey
@Reed: yeah, I am always conflicted about editing vs. new questions, but in this case that might be the right thing to do. My IError implementation is a separate class, but in the same assembly and namespace as the App. I'll look at CompositionInitializer now. thanks!
Dave
@Dave: In general, if hte question is a different subject (even slightly), ask a new question. There's no penalty for asking too many questions here (provided they aren't duplicate questions). You'll find you get better responses if you have separate, well worded, focused questions.
Reed Copsey