Search code examples
c#-4.0mef

MEF's GetExports<T, TMetadataView>() fails to find composed parts in the CompositionContainer


When attempting to load an instantiated export with GetExports() (using a LINQ query described below), the method returns null. I notice that when I call GetExports without the LINQ query, the return value is Count: 0. This would indicate to me that MEF is failing to find any exports that have been composed in the container. I can see the ExportDefinition, however, when looking at Container.Catalog.Parts.ExportDefinitions. Any ideas on where I am going wrong? Everything up until the query seems to be working fine.

I have the following contract and metadata view declared and implemented:

public interface IMap
{
    void Init();
    int ParseData();
}

public interface IMapMetadata
{
    string MapName { get; }
    string DocumentType { get; }
}

[Export(typeof(IMap))]
[ExportMetadata("MapName", "Map")]
public class Map
{
    public Map()
    {
    }
}

I am using the following code to load a directory that contains DLLs that satisfy this contract with:

    public void LoadByDirectory(string zPath)
    {
        try
        {
            _catalog.Catalogs.Add(new DirectoryCatalog(zPath));
        }
        catch (Exception e)
        {
            String zErrMess = e.Message;
        }

    }

Using a LINQ query to get an export:

public IMap GetMapInstance(string zMapName)
{
        IMap ndeMap;

        _container = new CompositionContainer(_catalog);
        _container.ComposeParts(this);

        try
        {
            ndeMap = _container.GetExports<IMap, IMapMetadata>()
                            .Where(p => p.Metadata.MapName.Equals(zMapName))
                            .Select(p => p.Value)
                            .FirstOrDefault();
        }
        catch (Exception ex) 
        {
            throw new Exception("Failed to load map " + zMapName + ": " + ex.Message, ex);
        }

        return ndeMap;
    }

Calling the above method like this:

IMap map = mapFactory.GetMapInstance("Map");

returns null.

UPDATED

In addition to the answer below, I was forgetting to declare the interface on the map class, this resolves the issue (note I removed the DocumentType property):

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public sealed class MapExportAttribute : ExportAttribute, IMapMetadata
{
    public MapExportAttribute()
        : base(typeof(IMap))
    {
    }

    public string MapName { get; set; }
}

[MapExport(MapName="Map")]
public class Map : IMap
{
    public Map()
    {
    }

    public void  Init()
    {
        throw new NotImplementedException();
    }

    public int  ParseData()
    {
        throw new NotImplementedException();
    }
}

Solution

  • It looks like you're missing the DocumentType meta-data on your export:

    [Export(typeof(IMap))]
    [ExportMetadata("MapName", "Map")]
    [ExportMetadata("DocumentType", "???")]
    public class Map
    {
    }
    

    The simplest way to ensure you specify the correct meta-data is a custom export attribute:

    [MetadataAttribute]
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public sealed class MapExportAttribute : ExportAttribute, IMapMetadata
    {
       public MapExportAttribute() : base(typeof(IMap))
       {
       }
    
       public string MapName { get; set; }
       public string DocumentType { get; set; }
    }
    
    [MapExport(MapName = "Map")]
    public class Map
    {
    }