Default constructor and open generics in MEF 2 using conventions

青春壹個敷衍的年華 提交于 2019-12-12 03:37:47

问题


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


回答1:


Try marking the constructor with the [ImportingConstructor] attribute.

using System.Composition;
...
[ImportingConstructor]
public Logger(ISetting settings)
{
    this.settings = settings;
}



回答2:


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



来源:https://stackoverflow.com/questions/38797527/default-constructor-and-open-generics-in-mef-2-using-conventions

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!