Reputation: 697
Given an exported class with an associated metadata view, is it possible to export the same metadata view multiple times into effectively a single metadata object? Below is an example of why I would want to do this, i.e. the metadata being exported is multiple lists of strings, which logically makes more sense as multiple attributes:
[ExportHandledNamespace("System", "Data")]
[ExportHandledNamespace("System", "Core")]
public class NamespaceHandler : INamespaceHandler { }
public interface INamespaceHandlerMetadata
{
string[] HandledNamespaces { get; }
}
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class)]
public class ExportHandledNamespaceAttribute : ExportAttribute, INamespaceHandlerMetadata
{
// In my use case, NamespaceHandler would be in a plugin assembly
// and I don't want them using this delimiter themselves
private string _namespaceDelimiter = ".";
public string[] HandledNamespaces { get; }
public ExportHandledNamespaceAttribute(params string[] namespaceIdentifiers)
: base(typeof(INamespaceHandler))
{
string namespace = String.Join(_namespaceDelimiter, namespaceIdentifiers);
// Somehow add this to an existing metadata view's HandledNamespaces
}
}
This is how I would want to use such an export:
public void ExampleUsageMethod()
{
var handler = mefContainer.GetExports<INamespaceHandler, INamespaceHandlerMetadata>().First();
string[] handledNamespaces = handler.Metadata.HandledNamespaces;
}
Upvotes: 0
Views: 147
Reputation: 697
I solved my issue by splitting ExportHandledNamespaceAttribute
into a single Export
on INamespaceHandler
and a MetadataAttribute
on the namespace identifiers with a custom metadata view, as below. The trick here is to get the contracts between the import expected by INamespaceHandlerMetadata
and the export provided by HandlesNamespaceAttribute
exactly right. Let me know if I can improve/clarify this answer any:
[Export(typeof(INamespaceHandler))]
[HandlesNamespace("System", "Data")]
[HandlesNamespace("System", "Core")]
public class NamespaceHandler : INamespaceHandler { }
[MetadataViewImplementation(typeof(NamespaceHandlerMetadata))]
public interface INamespaceHandlerMetadata
{
string[] HandledNamespaces { get; set; }
}
public class NamespaceHandlerMetadata : INamespaceHandlerMetadata
{
string[] HandledNamespaces { get; set; }
public NamespaceHandlerMetadata(IDictionary<string, object> exportedMetadata)
{
HandledNamespaces = exportedMetadata[nameof(HandledNamespaces)];
}
}
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class HandlesNamespaceAttribute : Attribute
{
private string _namespaceDelimiter = ".";
// Because the attribute is marked AllowMultiple = true, this will get exported
// as a string[], despite it appearing to only be set once in the constructor below
public string HandledNamespaces { get; }
public ExportHandledNamespaceAttribute(params string[] namespaceIdentifiers)
: base(typeof(INamespaceHandler))
{
string namespace = String.Join(_namespaceDelimiter, namespaceIdentifiers);
HandledNamespaces = namespace;
}
}
The example usage is the same as in the question, querying for an export of Lazy<INamespaceHandler, INamespaceHandlerMetadata>
and getting its HandledNamespaces
. But another example use case is below, using ImportMany
public class NamespaceHandlerManager, IPartImportsSatisfiedNotification
{
[ImportMany]
public IEnumerable<Lazy<INamespaceHandler, INamespaceHandlerMetadata>> NamespaceHandlers { get; set; }
public NamespaceHandlerManager() { }
public void OnImportsSatisfied()
{
// NamespaceHandlers will be populated with the exports from any
// loaded assemblies by this point, do what you want with it
}
}
Upvotes: 1