views:

227

answers:

2

I am relatively new to MEF so I don't fully understand the capabilities. I'm trying to achieve something similar to Unity's InjectionMember.

Let's say I have a class that imports MEF parts. For the sake of simplicity, let's take the following class as an example of the exported part.

[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class Logger {

    public string Category {
        get;
        set;
    }

    public void Write(string text) {
    }

}

public class MyViewModel {

    [Import]
    public Logger Log {
        get;
        set;
    }

}

Now what I'm trying to figure out is if it's possible to specify a value for the Category property at the import. Something like:

public class MyViewModel {

    [MyImportAttribute(Category="MyCategory")]
    public Logger Log {
        get;
        set;
    }

}

public class MyOtherViewModel {

    [MyImportAttribute(Category="MyOtherCategory")]
    public Logger Log {
        get;
        set;
    }

}

For the time being, what I'm doing is implementing IPartImportsSatisfiedNotification and setting the Category in code. But obviously I would rather keep everything neatly in one place.

+1  A: 

In the MEF programming guide, read the section on Exports and Metadata. It shows how you can add metadata on the exported part, either by using the ExportMetadata attribute or by defining your own custom export attribute.

You can then define a ILoggerMetadata interface like this:

public interface ILoggerMetadata
{
    string Catagory { get; }
}

and do an ImportMany of a IEnumerable<Lazy<ILogger,ILoggerMetadata>> and select the one you want in code like this:

private ILogger fooLogger;

[ImportMany]
public IEnumerable<Lazy<ILogger,ILoggerMetadata>> Loggers
{
    set
    {
        this.fooLogger = value.First(x => x.Metadata.Catagory == "foo").Value;
    }
}

I agree that it would be nicer to put the metadata constraints directly in the import attribute, but this is currently not possible in MEF out of the box. (It might be possible to extend MEF to do this.)

Another approach is to derive a IFooLogger interface from ILogger, and use that in your import and export. This is simple and has essentially the same effect as putting the constraint in the import. However, this approach doesn't work if you have multiple metadata attributes and/or many possible values.

edit: I had subtly misunderstood your question; I thought it was about constraining the import, instead of configuring the imported object with some extra parameter(s).

I think this recent post by Kathleen Dollard is about the same problem. Also, in this post about component relationships, Nicholas Blumhardt models such a "parameterisation" relationship as the injection of Func<X,Y> (or Func<ILogger,string> in your case).

You can do the same in MEF by putting an [Export(typeof(Func<ILogger,string>))] attribute directly on a method. Or if you need a less ambiguous contract you could define a ILoggerFactory interface and import/export that:

public ILoggerFactory
{
    ILogger Create(string category);
}

In the end, you will still have to invoke the factory in code though.

Wim Coenen
But if I understand correctly, that would require me to specify (at the export) which categories there are. Basically I want to let the importer bring in their own (non-shared) instance of an ILogger and specify its category declaratively. But I don't think MEF currently has an extension point to let me do that with the attributed model. Either way it seems I'll have to do it in the code.
Josh Einstein
Although after posting I did realize that while I still have to do it in code, I could set the category in the setter if I use a normal field-backed property. That's at least better than doing it in the IPartImportsSatisfiedNotification.
Josh Einstein
+2  A: 

After more digging into MEF, it seems there's no way to do this declaratively. While you can derive your own export attributes, there doesn't appear to be any mechanism for deriving an import attribute in any meaningful way.

But instead of implementing IPartImportsSatisfiedNotification, what I can do (seems obvious now) is set the category in the setter. I have to give up the automatic property but that's life.

public class MyViewModel {

    private Logger log;

    [Import]
    public Logger Log {
        get { return log; }
        set {
            log = value;
            log.Category = "MyCategory";
        }
    }

}
Josh Einstein
Yes this is about as close as you are going to get to what you want.
Wes Haggard