I am trying to use MEF 2 in my project, I usually use SimpleInjector but this time I wanted to try MEF. My main problem is dealing with open generics, this is what i got so far
public interface ISetting {}
public class Setting : ISetting {}
public interface ILogger<TLog>
{
TLog Fetch()
}
public class Logger<TLog> : ILogger<TLog>
{
private ISetting settings;
public Logger(ISetting settings)
{
this.settings = settings;
}
public TLog Fetch()
{
return default(TLog);
}
}
Now for the container part I do
var conventions = new ConventionBuilder();
conventions.ForType<Setting>()
.Export<ISetting>()
.Shared();
conventions.ForType(typeof(Logger<>))
.Export(t => t.AsContractType(typeof(ILogger<>)))
.Shared();
var configuration = new ContainerConfiguration()
.WithAssembly(typeof(Program).Assembly,conventions);
using (var container = configuration.CreateContainer)
{
var export = container.GetExport<ILogger<object>>(); //Exception :(
}
When it's trying to retrieve the export I am getting this exception
No importing constructor was found on type 'MEFTest.Logger`1[System.Object]'.
If I remove the constructor from the Logger class the container construct the closed generic just fine. I am 98% sure that the problem is related with the constructors but I feel I am missing something here
Edit 1: Doing some reading I have actually discovered that there are 2 versions of MEF, one that is a Nuget package and another one that ships with .NET40, the one that I am using is the Nuget package. I did some refactoring to use the one that ships with .NET40
All the code is the same except for the part that creates and use the container
var category = new AssemblyCatalog(Assembly.GetExecutingAssembly(), conventions);
using (var container = new CompositionContainer(category))
{
var logic = container.GetExport<ILogger<int>>().Value; //Lazy Initialization O.o
var test = logic.Fetch();
// the rest of the code …
}
This works :) just fine so definitely I am missing something in the version of the Nuget package
Edit 2: With the removal of the "auto-detection" of the generic parts in the WithAssembly method the code works, here is the code refactored
The convention part:
var conventions = new ConventionBuilder();
conventions.ForType<Setting>()
.Export<ISetting>();
conventions.ForType<Logger<int>>()
.Export<ILogger<int>>();
The container part:
var types = new Type[] { typeof(Setting), typeof(Logger<int>) };
var configuration = new ContainerConfiguration()
.WithParts(types, conventions);
using (var container = configuration.CreateContainer())
{
var logic = container.GetExport<ILogger<int>>();
var test = logic.Fetch();
// the rest of the code …
}
I changed the specific type to integer .When it executes Fetch() it returns correctly 0 as the default value for int
The interesting part is why the "auto-detection" of the generics force the constructor to be marked
Edit 3: I think the "auto-detection" part is not the one at fault here because I have tried this
var conventions = new ConventionBuilder();
conventions.ForType<Setting>()
.Export<ISetting>();
conventions.ForType(typeof(Logger<>))
.Export(t => t.AsContractType(typeof(ILogger<>)));
var types = new Type[] { typeof(Setting), typeof(Logger<>) };
var configuration = new ContainerConfiguration()
.WithParts(types, conventions);
using (var container = configuration.CreateContainer())
{
var logic = container.GetExport<ILogger<int>>();
var test = logic.Fetch();
// the rest of the code …
}
With that code I am back to square one because it produces the same exception, it enforces the use of the marking attribute
Edit 4: The actual MEF project has gone to the CoreFx GitHub page under System.Composition . I went to the unit tests and in the RegistrationBuilderCompatibilityTest lines 40-58
public interface IRepository<T> { }
public class EFRepository<T> : IRepository<T> { }
[Fact]
public void ConventionBuilderExportsOpenGenerics()
{
var rb = new ConventionBuilder();
rb.ForTypesDerivedFrom(typeof(IRepository<>))
.Export(eb => eb.AsContractType(typeof(IRepository<>)));
var c = new ContainerConfiguration()
.WithPart(typeof(EFRepository<>), rb)
.CreateContainer();
var r = c.GetExport<IRepository<string>>();
}
They never tested it without the default constructor
Results: I end up opening an issue on the CoreFx Github page and submitted a PR with a fix for the bug
Try marking the constructor with the [ImportingConstructor] attribute.
using System.Composition;
...
[ImportingConstructor]
public Logger(ISetting settings)
{
this.settings = settings;
}
Until they merge my PR and release a version with the bug fixed I recommend going with the solution on Edit 2 . I think it is the least intrusive way of achieving the functionality required
Basically you need to build conventions with your CLOSED generic types and create a collection with these closed generics. In the container use the WithParts method along with the conventions that were defined, see Edit 2 for code snippet
UPDATE
My PR has been merged and this functionality is now supported. It will be released in corefx 2.0 due by April 30, 2017
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.