MEF Constructor Injection
Asked Answered
O

3

44

I'm trying to figure out MEF's Constructor Injection attribute. I have no idea how I tell it to load the constructor's parameters.

This is the property I'm trying to load

[ImportMany(typeof(BUsers))]
public IEnumerable<BUsers> LoadBUsers { get; set; }

Here is the code I'm using to import the assemblies.

try
{
    var catalog = new AggregateCatalog();
    catalog.Catalogs.Add(new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly()));
    catalog.Catalogs.Add(new DirectoryCatalog("DI")); 
    var container = new CompositionContainer(catalog);
    container.ComposeParts(this);
}

Here is the class I'm trying to load

[Serializable]
[Export(typeof(BUsers))]
public class EditProfile : BUsers
{
    [ImportingConstructor]
    public EditProfile(string Method, string Version)
    {            
        Version = "2";
        Action = "Edit";
        TypeName = "EditProfile";
    }
Opportune answered 5/1, 2010 at 18:19 Comment(0)
L
61

When you use the ImportingConstructor attribute, the parameters to the constructor become imports. By default, what you are importing (the contract name) is based on the type of the parameter or property that your are importing into. So in this case the contract type for both your imports is string, and there's no real difference between the first and second parameter.

It looks like you are trying to use imports to supply configuration values, which isn't necessarily what it was designed for. To get it to do what you want, you should override the contract name for each of the parameters, like this:

[ImportingConstructor]
public EditProfile([Import("Method")] string Method, [Import("Version")] string Version)
{ }

Then you need exports for Method and Version in your container. One way to do this is just to add them directly:

var container = new CompositionContainer(catalog);
container.ComposeExportedValue("Method", "MethodValue");
container.ComposeExportedValue("Version", "2.0");
container.ComposeParts(this);

(Note that ComposeExportedValue is actually an extension method defined on the static AttributedModelServices class.)

If you want to read these values from a configuration file of some sort, you could create your own export provider which reads the configuration and provides the values in it as exports to the container.

An alternative way to handle this would be to just import an interface that provides access to the configuration values by name, and get the values you need from the body of the constructor.

Leora answered 5/1, 2010 at 18:48 Comment(4)
I just downloaded the new one at CodePlex. The method of ComposeExportedValue() is not in the class of CompositionContainer. Where is it?Neckerchief
I think I found the method. It is in the class of AttributedModelServices, where the method is defined as extended method to class CompositionContainer.Neckerchief
@Neckerchief Yes, ComposeExportedValue is an extension method on the AttributedModelServices class.Leora
Good answer. But what if I have an NonShared export that needs to be configured. Importing a config getter interface is valid (as you wrote) but what name should I use? There is no way to intercept between object creation and OnImportsSatisfied-call to set this name - but in my case it is valid to access to config in OnImportsSatisfied. - I've another question to your answer: What is this at container.ComposeParts(this)? I don't think that this is the instance of EditProfile that's currently creating.Mccaskill
N
26

I like Daniel's solution; however, only one thing I see is the tight coupling of parameter names between the actor (who creates CompopositionContrainer()) and Export part with [ImportingConstructor] for customized CTOR. For example, "Method" has two be matched in both places. It makes hard to maintain the Export part if the actor and Export part are in difference projects.

If it is possible, I would add the second CTOR to the Export part class. For example:

[Export(typeof(BUsers))] 
public class EditProfile : BUsers
{
    [ImportingConstructor]
    public EditProfile(EditProfileParameters ctorPars)
    : this(ctorPars.Method, ctorPars.Version) {}

    public EditProfile(string Method, string Version)
    {
        Version = "2";
        Action = "Edit";
        TypeName = "EditProfile";
    }

The class of EditProfileParameters should be straightforward: two properties of Method and Version:

[Export]
public class EditProfileParameters{
   public string Method { get; set; }
   public string Version { get; set; }
}

The key point is to add Export attribute to the class. Then MEF should be able to map this class to the parameter of EditProfile's CTOR.

Here is example to add the Export part to container:

var container = new CompositionContainer(catalog);
var instance1 = new EditProfileParameters();
// set property values from config or other resources
container.ComposeExportedValue(instance1);
container.ComposeParts(this);
Neckerchief answered 21/1, 2010 at 18:25 Comment(0)
S
2

Although late to the game, here's another approach that leverages a lesser-known feature of MEF: Property Exports

public class ObjectMother
{
    [Export]
    public static EditProfile DefaultEditProfile
    {
        get
        {
            var method = ConfigurationManager.AppSettings["method"];
            var version = ConfigurationManager.AppSettings["version"];

            return new EditProfile(method,version);
        }
    }
}

No usages are required for ObjectMother for this to work, and no attributes are required on EditProfile.

Smyrna answered 5/10, 2015 at 17:16 Comment(2)
Nice feature but what if EditProfile contains any imports? In that case you need to call SatisfyImportsOnce on the container. That hurts. Currently I've no real solution for that problem.Mccaskill
In this case, we're building a factory that manually instantiates your EditProfile. If EditProfile requires additional dependencies in the constructor, you can always bring those dependencies into the Factory through an ImportingConstructor. I should point out, that if you add additional dependencies to EditProfile, this is the only reference in your application to this constructor and you will get compile time errors. It's a fair trade, IMHO.Smyrna

© 2022 - 2024 — McMap. All rights reserved.