3
votes

How can I override MEF composition so that if an importing constructor ask for a single dependency and there is multiple implementation of this required dependency it would take the implementation (export) with the highest priority (metadata)?

[Export(typeof(IService))]
[ExportMetadata("Priority", 1)]
public class Service1 : IService
{
}

[Export(typeof(IService))]
[ExportMetadata("Priority", 2)]
public class Service2 : IService
{
}

public interface IService
{
}

public class ServiceWithDependencies
{
    [ImportingConstructor]
    public ServiceWithDependencies(IService service)
    {

    }
}

ServiceWithDependencies would normally not be satisfied since there is more than one implementation of IService. But I would like to modify MEF (override/intercept something) so that it would use the priority metadata and inject the implementation with the highest priority into the ServiceWithDependencies constructor.

Edit 1

I don't want MEF to dictate the way I do things. I want it to be invisible as much as possible. Furthermore, this is for a framework and I don't have control on how people will require dependencies. I need to support basic constructor injection. I know about [ImportMany], but this question is precisely about constructor injection.

2

2 Answers

2
votes

Instead of putting priority metadata on your parts, you can also put them in different catalogs and then prioritize the catalogs.

In our applications, we make use of this to give highest priority to parts in a plugins folder, then the parts in the application exe, and finally all the other libraries.

edit: Here's a full sample.

First, the types:

[Export(typeof(IService))]
public class Service1 : IService { }

[Export(typeof(IService))]
public class Service2 : IService { }

public interface IService { }

[Export]
public class ServiceWithDependency
{
    [ImportingConstructor]
    public ServiceWithDependency(IService service)
    {
    }
}

[Export]
public class ServiceWithMultipleDependencies
{
    [ImportingConstructor]
    public ServiceWithMultipleDependencies(
        [ImportMany] IEnumerable<IService> services)
    {
    }
}

And here is how you can set up the container:

var priorityCatalog = new TypeCatalog(typeof(Service1));
var priorityProvider = new CatalogExportProvider(priorityCatalog);

var mainCatalog = new TypeCatalog(
    typeof(ServiceWithDependencies),
    typeof(ServiceWithMultipleDependencies),
    typeof(Service2));
var mainProvider = new CatalogExportProvider(mainCatalog);

// priorityProvider gets priority because it is first
var container = new CompositionContainer(
    priorityProvider, mainProvider);
priorityProvider.SourceProvider = container;
mainProvider.SourceProvider = container;

// will get constructed with Service1
var foo = container.GetExportedValue<ServiceWithDependency>();

// will get constructed with both, proving that both are available
var bar = container.GetExportedValue<ServiceWithMultipleDependencies>();
1
votes

the easy way would be to [ImportMany] and let chosse the one with the highest Metadata. <-- Thats what we do in our projects.

More information of what you can do - you find here.

public class ServiceWithDependencies
{
    [ImportingConstructor]
    public ServiceWithDependencies([ImportManyAttribute]IEnumerable<Lazy<IService, IServiceMetadata>> services)
    {
       this._TheServiceIWant = services.Max(x=>x.Metadata.Prio).First().Instance;
    }
}

ps: code is handwritten, so linq would not be right - maybe

EDIT:

instead of handle the services in your class directly you can also import and factory wich has some methods like:

 MyPluginFac.GiveServiceWithHighestPrio();
 MyPluginFac.GiveServiceWithPrio(1);
 ...

public class ServiceWithDependencies
{
    [ImportingConstructor]
    public ServiceWithDependencies(IServiceFac serviceFac)
    {
       this._TheServiceIWant = serviceFac.GiveServiceWithHighestPrio();
    }
}