3

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.

W3Max
  • 3,328
  • 5
  • 35
  • 61

2 Answers2

2

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>();
Community
  • 1
  • 1
Wim Coenen
  • 66,094
  • 13
  • 157
  • 251
  • Sorry if I am slow, but I don't understand how this could help with constructor injection. – W3Max Mar 02 '12 at 14:31
  • There is no conflict with constructor injection, we use that as well. – Wim Coenen Mar 02 '12 at 15:15
  • I did not say that there was a problem, I just said that I don't understand how it works. Sorry again, but I am a newbie in the MEF world. Could you explain in the context of the example above? – W3Max Mar 02 '12 at 15:57
  • I saw your answer for this question: http://stackoverflow.com/questions/7750640/intercepting-dependencies-in-mef Do you think the InterceptingCatalog would be of any help for my situation? – W3Max Mar 02 '12 at 16:26
  • Thanks! I will try this tonight, but it seems do do the job. I guess I just have to filter the services on priority and keep only the ones with the highest priority for the priorityCatalog and add all the services to the mainCatalog? – W3Max Mar 02 '12 at 18:37
  • @W3Max: That could work. In our case we simply give higher priority to the assemblies in a certain subdirectory ("plugins"). – Wim Coenen Mar 02 '12 at 21:08
  • It works (constructor injection) but I now have a duplicate when I do ImportMany. I don't know if I could filter it out... – W3Max Mar 05 '12 at 14:19
  • @W3Max: that's the expected result if you have the same part in both catalogs. You could make sure that you use different assemblies for each catalog, or you could use something like a [FilteredCatalog](http://kentb.blogspot.com/2010/10/mef-filtered-catalog.html). – Wim Coenen Mar 06 '12 at 09:21
1

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();
    }
}
Community
  • 1
  • 1
blindmeis
  • 22,175
  • 7
  • 55
  • 74
  • With respect, this is too much ceremony. 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. – W3Max Mar 02 '12 at 14:18
  • the good thing for the factory solution is, that you have one container and you can get what you want out of it. – blindmeis Mar 06 '12 at 06:19
  • but you are right, instead of IService you have to import IServiceFac – blindmeis Mar 06 '12 at 10:32