MEF and exporting based on Metadata

Ingó Vals picture Ingó Vals · Oct 5, 2011 · Viewed 13.9k times · Source

OK I'm pretty sure this is something blindingly obvious but I'm not finding it.

I'm trying to export a object from MEF container based on it's Metadata.

I've seen this done in tutorials like this one:

http://blog.maartenballiauw.be/post/2009/04/21/ASPNET-MVC-and-the-Managed-Extensibility-Framework-%28MEF%29.aspx

However my export doesn't have the Metadata property that is necessary for this to work. What could be the problem there?

[Export(typeof(IController))]
[ExportMetadata("controllerName","Home")]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class HomeController : Controller{}

and again

public class MyControllerFactory : IControllerFactory
{
    private readonly CompositionContainer _container;

    public MyControllerFactory(CompositionContainer container)
    {
        _container = container;
    }

    public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
    {
        var controllerExport = _container.GetExports<IController>().
                Where(exp => exp.Metadata) //Here it doesn't have the Metadata property.
    }
}

I understand that the GetExports returns a collection of Lazy that do of course not have the Metadata property but it's assumed in most tutorials I look through.

How do I do this correctly?

EDIT

This is what I did:

 public interface IControllerMetaData
{
    string Name { get; }
    string Subdomain { get; }
}

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class ControllerMetadataAttribute : ExportAttribute
{
    public ControllerMetadataAttribute(string name, string subdomain)
        : base(typeof(IControllerMetaData))
    {
        Name = name;
        Subdomain = subdomain;
    }

    public string Name { get; set; }
    public string Subdomain { get; set; }
}

Then in each controller

[Export(typeof(IController))]
[ControllerMetadata("Home", "")]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class HomeController : SubdomainManagedController

and in the factory

var controllerExport = _container.GetExports<IController, ControllerMetadataAttribute>().
                                    Where(exp => exp.Metadata.Name.Equals(controllerName) && exp.Metadata.Subdomain.Equals(subdomain)).
                                    FirstOrDefault();

and I'm getting

The Type 'ControllerMetadataAttribute' supplied is not a valid Metadata View.`

How is it not valid. It has the MetaDataAttribute and all?

Answer

Matthew Abbott picture Matthew Abbott · Oct 5, 2011

In your example, you're using GetExports<T>, instead of GetExports<T,TMetadata>. In a simple example, you can use GetExports<IController, IDictionary<string, object>>, which would allow you to query, but a nicer way of doing it is to create a custom metadata contract:

public interface INameMetadata
{
    string Name { get; }
}

Which you can then use as:

[Export(typeof(IController))]
[ExportMetadata("Name", "Home")]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class HomeController : Controller { }

And then change your import to:

var controller = _container.GetExports<IController, INameMetadata>()
                           .Where(e => e.Metadata.Name.Equals(controllerName))
                           .Select(e => e.Value)
                           .FirstOrDefault();

Going one step further, you could combine your Export and ExportMetadata attributes into a single attribute:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false), MetadataAttribute]
public class ExportControllerAttribute : ExportAttribute, INameMetadata
{
    public ExportControllerAttribute(string name)
        : base(typeof(IController))
    {
        Name = name;
    }

    public string Name { get; private set; }
}

Now, you can use that with your export:

[ExportController("Home"), PartCreationPolicy(CreationPolicy.NonShared)]
public class HomeController : Controller { }