This is sort of a continuation of one of my earlier posts, which involves the resolving of modules in my WPF application. This question is specifically related to the effect of interdependencies of modules and the method of constructing those modules (i.e. via MEF or through new
) on MEF's ability to resolve relationships.
I have tried two approaches:
Left approach
My code behind looked like this (just the MEF-related stuff):
// app.cs
[Export(typeof(IError))]
public partial class Window1 : Window, IError
{
[Import]
public CandyCo.Shared.LibraryInterfaces.IPlugin Plugin { get; set; }
[Import]
public CandyCo.Shared.LibraryInterfaces.ICandySettings Settings { get; set; }
private ICandySettings Settings;
public Window1()
{
// I create the preferences here with new, instead of using MEF. I wonder
// if that's my whole problem? If I use MEF, and want to have parameters
// going to the constructor, then do I have to [Export] a POCO (i.e. string)?
Settings = new CandySettings( "Settings", @"c:\settings.xml");
var catalog = new DirectoryCatalog( ".");
var container = new CompositionContainer( catalog);
try {
container.ComposeParts( this);
} catch( CompositionException ex) {
foreach( CompositionError e in ex.Errors) {
string description = e.Description;
string details = e.Exception.Message;
}
throw;
}
}
}
// plugin.cs
[Export(typeof(IPlugin))]
public class Plugin : IPlugin
{
[Import]
public CandyCo.Shared.LibraryInterfaces.ICandySettings CandySettings { get; set; }
[Import]
public CandyCo.Shared.LibraryInterfaces.IError ErrorInterface { get; set; }
[ImportingConstructor]
public Plugin( ICandySettings candy_settings, IError error_interface)
{
CandySettings = candy_settings;
ErrorInterface = error_interface;
}
}
// candysettings.cs
[Export(typeof(ICandySettings))]
public class CandySettings : ICandySettings
{
...
}
Right-side approach
Basically the same as the left-side approach, except that I created a class that inherits from IError in the same assembly as Window1. I then used an [Import] to try to get MEF to resolve that for me.
Can anyone explain how the two ways I have approached MEF here are flawed? I have been in the dark for so long that instead of reading about MEF and trying different suggestions, I've added MEF to my solution and am stepping into the code. The part where it looks like it fails is when it calls partManager.GetSavedImport()
. For some reason, the importCache is null, which I don't understand. All the way up to this point, it's been looking at the part (Window1) and trying to resolve two imported interfaces -- IError and IPlugin. I would have expected it to enter code that looks at other assemblies in the same executable folder, and then check it for exports so that it knows how to resolve the imports...
I had found a mistake in my code, and when I fixed it, the MEF exception changed, and was also more useful. It clearly pointed out that it couldn't find a CandySettings default constructor! And digging more, I found a good post from Glenn Block that discusses this. So I need to finish reading it and see if his workaround will do the trick or not. I would still appreciate more answers, since there's no telling if the workaround is the right thing to do or not.
This post really helped. I hadn't seen this information before, but it totally did the trick for me.
http://mindinthewater.blogspot.com/2010/01/using-mef-with-classes-which-take.html
Basically, my problem was that I needed to pass values to the constructor. All of my past tests involved passing interfaces to other shared libraries, but in my case, I just wanted to pass a couple of strings. I obviously didn't want to try to wrap these strings in an interface just to pass POCOs.
My first attempt in getting around this inconvenience was to do the best I could with the default constructor. I then left it up to fate that a developer would remember to call the Init() method. This was bad for obvious reasons, but I wanted to try it out anyway. In the end, it just didn't work -- the problem here is that MEF wants to resolve imports and exports, but my Init() method wouldn't get called until after composing the parts... so any other dependents of that particular library would end up with a not-truly-initialized instance of the library since Init() won't get called until later.
Anyhow, this trick of importing strings for the constructor parameters worked like a charm.