views:

270

answers:

2

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.

First of all, here is a simple UML diagram of my test application:

alt text

I have tried two approaches:

  • left approach: the App implements IError
  • right approach: the App has a member that implements IError

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.

A: 

It would help if you would include the error message that you are getting.

However, if you go with the left approach, I think putting a PartNotDiscoverableAttribute on your Window1 class may fix the problem.

The issue is that the DirectoryCatalog is going to include the assembly that includes Window1, so there is going to be an IError export available from the catalog (and MEF would create an instance of Window1 if you requested that export's value). When you add the Window1 you created via ComposeParts, you are trying to add another IError export to the container. Since your plugin is only requesting a single IError export, it won't work when there is more than one available. Adding the PartNotDiscoverableAttribute on the Window1 class will prevent it from being included in the catalog.

Daniel Plaisted
Sorry, I forgot to post the error, but the error I would have posted would have been incorrect. :) It turns out that the MEF error tells me that I *must* use a parameterless constructor. In both cases, I only had one IError implementation, though.
Dave
@Dave You can use constructors with parameters if you put an ImportingConstructorAttribute parameter on them, which you've done for Plugin in the code you posted.
Daniel Plaisted
@Daniel: yes, I've done that in one instance. I did a poor job of explaining what I was referring to. I got that error because I didn't have a default constructor and the other constructors weren't marked up with [ImportingConstructor]. However, all I've seen are examples where the constructor parameters are other interfaces that will get resolved by MEF, so I got confused because I just want a couple of strings passed. I didn't want to have to do some weird wrapping of said strings just to support MEF.
Dave
+1  A: 

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.

Dave
ah, so *you* are the anonymous commenter on my blog post :-)
Wim Coenen