MEF: Passing different constructor parameters to a part when using CreationPolicy.NonShared
Asked Answered
M

2

10

I know there have been lot of questions regarding constructor parameter injection using MEF, but mine is a bit different.

I want to know that is there any way to pass different parameter values to the constructor of a part when I am using the combination of PartCreationPolicy(CreationPolicy.NonShared) and GetExportedValue?

For example:

[PartCreationPolicy(CreationPolicy.NonShared)]
[Export]
public partial class Foo
{
    [ImportingConstructor]
    public Foo([Import("SomeParam")]object parameter)
    {
        ...
    }
}

and somewhere else...

container.ComposeExportedValue("SomeParam", "Some value...");
var instance = container.GetExportedValue<Foo>();

In the above example, I can use ComposeExportedValue only once, as running it a second time will cause a ChangeRejectedException.

So, my questions are:

  1. Is there any other way to change the value of SomeParam in the above scenario, for each new instance?
  2. If not, what are the other ways this can be accomplished without using any other DI framework? One thing which comes to mind is to create a service to expose something like System.Collections.Concurrent.ConcurrentQueue where I enqueue a parameter value before calling GetExportedValue and then dequeue the value in the constructor of the part. But that is a hack and also creates more issues than it solves.
  3. If the answer to both the above questions is no, then are there any other ways to accomplish this with a combination of MEF and some other DI/IOC framework?

Thanks for any help. :)
Regards,
Yogesh Jagota

Margarine answered 29/3, 2012 at 18:31 Comment(0)
B
2

If the answer to both the above questions is no, then are there any other ways to accomplish this with a combination of MEF and some other DI/IOC framework?

I think the answer to question 1 and 2 is indeed no.

I would try AutoFac which gives you more fine grained control and integrates with MEF. For example, it allows you to set up registrations like this so that Bar and Baz instances get their Foo instance with a different parameter:

builder.Register(c => new Bar(new Foo(param));
builder.Register(c => new Baz(new Foo(param2));
Bauxite answered 30/3, 2012 at 8:49 Comment(3)
I am looking at AutoFac/MEF Integration but how can I handle registration when using RegisterComposablePartCatalog? I can't use Register here as it is done by AutoFac automatically. How can I tell AutoFac that a certain export needs to be instantiated using a non default constructor with parameters I supply without using the [ImportingConstructor]?Margarine
@Yogesh: You can have some components registered with AutoFac (when you need fine-grained control), and other exported with MEF (when you need dynamic discovery of plugins). But you can't mix both for the same component. Another option is to switch to AutoFac entirely; you can use Scanning to get MEF-like dynamic discovery where needed.Bauxite
It actually worked. The way to do it is using the Update method of IContainer which allows to add new registrations to an existing container. Thanks. :)Margarine
D
2

If you want to use different instances of same interface depending upon some logic (apply strategy pattern) in MEF one way to use ExportMetadata Attribute. For example if you have IDbManager and if you have two implementation of it say one Oracle and One Sql then 1. Create metadata interface which will hold metadata

public interface IDbManagerMetadata
{
    DataProvider DataProvider { get; }
}

2. Create Attribute class as below

[MetadataAttribute]
public class DbManagerMetadataAttribute : Attribute, IDbManagerMetadata
{
    public DataProvider DataProvider { get; set; }
}
  1. Strategy example
public enum DataProvider
{
    Oracle,
    Sql,
}
[InheritedExport]
public interface IDbManager
{
    void Initialize();
}

[InheritedExport(typeof(IDbManager))]
public class DbManager : IDbManager
{
    public DbManager(DataProvider providerType)
    {
        _providerType = providerType;
    }

    public void Initialize()
    {
        Console.WriteLine("provider : {0}", _providerType);
    }

    public DataProvider _providerType { get; set; }
}

And Two different Implementations

[Export(typeof(IDbManager))]
[DbManagerMetadata(DataProvider = DataProvider.Oracle)]
public sealed class OracleDataProvider : DbManager
{
    public OracleDataProvider():base(DataProvider.Oracle)
    {

    }
}

And

[Export(typeof(IDbManager))]
[DbManagerMetadata(DataProvider = DataProvider.Sql)]
public sealed class SqlDataProvider : DbManager
{
    public SqlDataProvider()
        : base(DataProvider.Sql)
    {
    }
}

And you can decide which one to use by using Metadata interface we created in first step as in repository shown below

[Export]
public class Repository
{
    private IDbManager _dbManager;

    private readonly IEnumerable<Lazy<IDbManager, IDbManagerMetadata>> DbManagers;

    [ImportingConstructor]
    public Repository([ImportMany(typeof(IDbManager))]IEnumerable<Lazy<IDbManager, IDbManagerMetadata>> dbManagers)
    {
        this.DbManagers = dbManagers;
        var _dbManager = DbManagers.First(x => x.Metadata.DataProvider == DataProvider.Oracle).Value;
    }

    public void Execute()
    {
        var oracleDbManager = DbManagers.First(x => x.Metadata.DataProvider == DataProvider.Oracle).Value;

        oracleDbManager.Initialize();

        var sqlDbManager = DbManagers.First(x => x.Metadata.DataProvider == DataProvider.Sql).Value;

        sqlDbManager.Initialize();
    }
}
Douche answered 1/7, 2014 at 18:4 Comment(2)
Where is the answer for setting parameters of ImportingConstructor with different values for different instances?Buonomo
@Buonomo DbManagerMetadataAttribute is your answerPamela

© 2022 - 2024 — McMap. All rights reserved.