Multiple export providers

Aug 12, 2010 at 2:55 PM

I have a situation where I have two MEF export providers passed to a CompositionContainer (there's also a DirectoryCatalog).  If the imported types are not all in the first MEF export provider then I get an ImportCardinalityMismatchException because the first container does not have the imported type, even though the second one does.

This is different behaviour to the CatalogExportProvider used with a TypeCatalog, for example.  What I would expect from the UnityExportProvider is that if the import can't be satisfied then that instance "passes" and allows another export provider to (possibly) provide the import, which is what the CatalogExportProvider appears to do.

Here's the unit tests to show the situation:

[TestClass]
public class MultipleExportProviders
{
    abstract class BaseA { }
    [Export(typeof(BaseA))]
    class A : BaseA
    {
    }

    abstract class BaseB { }
    [Export(typeof(BaseB))]
    class B : BaseB
    {
    }

    [Export]
    class C
    {
        [ImportingConstructor]
        public C(BaseA a, BaseB b)
        {
            ThingA = a;
            ThingB = b;
        }
        public BaseA ThingA { get; private set; }
        public BaseB ThingB { get; private set; }
    }

    [TestMethod]
    public void ComposeWithTwoUnityExportContainers()
    {
        var unityContainer1 = new UnityContainer();
        var exportProvider1 = new UnityExportProvider(unityContainer1);

        var a = new A();
        unityContainer1.RegisterInstance<BaseA>(a);

        var unityContainer2 = new UnityContainer();
        var exportProvider2 = new UnityExportProvider(unityContainer2);

        var b = new B();
        unityContainer2.RegisterInstance<BaseB>(b);


        var catalog = new TypeCatalog(typeof(C));
        var compositionContainer = new CompositionContainer(catalog, exportProvider1, exportProvider2);
        var instance = compositionContainer.GetExport<C>();
        Assert.IsNotNull(instance.Value);
        Assert.AreEqual(a, instance.Value.ThingA, "Instance of A is the same as that registered with the DI container.");
        Assert.AreEqual(b, instance.Value.ThingB, "Instance of B is the same as that registered with the DI container.");
    }

    [TestMethod]
    public void ComposeWithTwoTypeCatalogExportContainers()
    {
        var catalog1 = new TypeCatalog(typeof(A));
        var exportProvider1 = new CatalogExportProvider(catalog1);
        exportProvider1.SourceProvider = exportProvider1;
        var catalog2 = new TypeCatalog(typeof(B));
        var exportProvider2 = new CatalogExportProvider(catalog2);
        exportProvider2.SourceProvider = exportProvider2;

        var catalog = new TypeCatalog(typeof(C));
        var compositionContainer = new CompositionContainer(catalog, exportProvider1, exportProvider2);
        var instance = compositionContainer.GetExport<C>();
        Assert.IsNotNull(instance.Value);
    }

}


ComposeWithTwoTypeCatalogExportContainers test passes, whereas ComposeWithTwoUnityExportContainers test fails with an ImportCardinalityMismatchException for the export of C because the import of parameter "b" failed.  If you swap the order of exportProvider1 and exportProvider2 in the constructor to CompositionContainer then it fails because the import of parameter "a" fails.  If you put both types in one export provider the test will pass if this is the first provider and fail if it is the second provider.

Any ideas what causes the behaviour difference in the export providers?

Coordinator
Aug 12, 2010 at 9:18 PM
Hi rich,

Thanks for your question. Unfortunatly Piotr, the author of the unity integration stuff is away for another week but I will make sure he replies as soon as he gets back. You wouldn't want my
reply since my experience with unity is virtually zero =)

Apprechiate that you supplied a test that reproduces the behavior you are experiencing, definitive going to help speed things up once he's back at the keyboard

Let me know if theres anything else I can do in the meantime!

/Andreas

On Thu, Aug 12, 2010 at 4:55 PM, rich257 <notifications@codeplex.com> wrote:

From: rich257

I have a situation where I have two MEF export providers passed to a CompositionContainer (there's also a DirectoryCatalog).  If the imported types are not all in the first MEF export provider then I get an ImportCardinalityMismatchException because the first container does not have the imported type, even though the second one does.

This is different behaviour to the CatalogExportProvider used with a TypeCatalog, for example.  What I would expect from the UnityExportProvider is that if the import can't be satisfied then that instance "passes" and allows another export provider to (possibly) provide the import, which is what the CatalogExportProvider appears to do.

Here's the unit tests to show the situation:

[TestClass]
public class MultipleExportProviders
{
    abstract class BaseA { }
    [Export(typeof(BaseA))]
    class A : BaseA
    {
    }

    abstract class BaseB { }
    [Export(typeof(BaseB))]
    class B : BaseB
    {
    }

    [Export]
    class C
    {
        [ImportingConstructor]
        public C(BaseA a, BaseB b)
        {
            ThingA = a;
            ThingB = b;
        }
        public BaseA ThingA { get; private set; }
        public BaseB ThingB { get; private set; }
    }

    [TestMethod]
    public void ComposeWithTwoUnityExportContainers()
    {
        var unityContainer1 = new UnityContainer();
        var exportProvider1 = new UnityExportProvider(unityContainer1);

        var a = new A();
        unityContainer1.RegisterInstance<BaseA>(a);

        var unityContainer2 = new UnityContainer();
        var exportProvider2 = new UnityExportProvider(unityContainer2);

        var b = new B();
        unityContainer2.RegisterInstance<BaseB>(b);


        var catalog = new TypeCatalog(typeof(C));
        var compositionContainer = new CompositionContainer(catalog, exportProvider1, exportProvider2);
        var instance = compositionContainer.GetExport<C>();
        Assert.IsNotNull(instance.Value);
        Assert.AreEqual(a, instance.Value.ThingA, "Instance of A is the same as that registered with the DI container.");
        Assert.AreEqual(b, instance.Value.ThingB, "Instance of B is the same as that registered with the DI container.");
    }

    [TestMethod]
    public void ComposeWithTwoTypeCatalogExportContainers()
    {
        var catalog1 = new TypeCatalog(typeof(A));
        var exportProvider1 = new CatalogExportProvider(catalog1);
        exportProvider1.SourceProvider = exportProvider1;
        var catalog2 = new TypeCatalog(typeof(B));
        var exportProvider2 = new CatalogExportProvider(catalog2);
        exportProvider2.SourceProvider = exportProvider2;

        var catalog = new TypeCatalog(typeof(C));
        var compositionContainer = new CompositionContainer(catalog, exportProvider1, exportProvider2);
        var instance = compositionContainer.GetExport<C>();
        Assert.IsNotNull(instance.Value);
    }

}


ComposeWithTwoTypeCatalogExportContainers test passes, whereas ComposeWithTwoUnityExportContainers test fails with an ImportCardinalityMismatchException for the export of C because the import of parameter "b" failed.  If you swap the order of exportProvider1 and exportProvider2 in the constructor to CompositionContainer then it fails because the import of parameter "a" fails.  If you put both types in one export provider the test will pass if this is the first provider and fail if it is the second provider.

Any ideas what causes the behaviour difference in the export providers?

Read the full discussion online.

To add a post to this discussion, reply to this email (MEFContrib@discussions.codeplex.com)

To start a new discussion for this project, email MEFContrib@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com


Aug 13, 2010 at 3:00 PM

Having looked at this a bit further, I think UnityExportProvider should be calling TryGetExports() in its override of GetExportsCore().  Certainly the tests will pass in this case.

The reason why I think this is the correct change is that UnityExportProvider is aggregating the ExternalExportProvider,  Using Reflector you can see that the AggregateExportProvider and CompositionContainer (also an ExportProvider containing another export provider) are calling TryGetExports() on the child export provider in their GetExportsCore().  This makes sense to me: the export provider is being asked to meet an import definition and it is legal for it to say "no I can't", which it does by returning an empty list.  It is up to the caller to decide if that represents an error or not --- the caller may have other export providers to try or it may not, that can't be known by this export provider.

Coordinator
Sep 8, 2010 at 6:36 PM

Hi rich,

Thank you for reporting this issue. Indeed there was a problem inside the ContainerExportProvider class, which was not properly handling the aggregated export provider. Now it has been fixed, you can download the latest source code including this fix from my fork at http://github.com/pwlodek/MefContrib. I will also make an effort to push this fix back to main repo, which is found at http://github.com/MefContrib/MefContrib.

Btw, from the class names you use I assume you are running a bit old code. I advise to update :)

 

Regards,

Piotr