Exports and Metadata

Exports and Metadata

Declaring Exports explained the basics of parts exporting services and values. In some cases it’s necessary to associate information with exports for a variety of reasons. Commonly it’s used to explain about the capabilities of an specific implementation of a common contract. This is useful to allow imports to either constraint the export that can satisfy it, or to import all available implementations at the time and check their capabilities in runtime before using the export.

Attaching Metadata to an Export

Consider the IMessageSender service introduced earlier. Suppose we have a few implementations, and they have differences that may be relevant to the consumer of the implementations. For our example the transport of the message and whether is secure are important information for a consumer (importer).

Using ExportMetadataAttribute

All we have to do to attach this information is to use the [System.ComponentModel.Composition.ExportMetadataAttribute]:

public interface IMessageSender
{
    void Send(string message);
}

[Export(typeof(IMessageSender))]
[ExportMetadata("transport", "smtp")]
public class EmailSender : IMessageSender
{
    public void Send(string message)
    {
        Console.WriteLine(message);
    }
}

[Export(typeof(IMessageSender))]
[ExportMetadata("transport", "smtp")]
[ExportMetadata("secure", null)]
public class SecureEmailSender : IMessageSender
{
    public void Send(string message)
    {
        Console.WriteLine(message);
    }
}

[Export(typeof(IMessageSender))]
[ExportMetadata("transport", "phone_network")]
public class SMSSender : IMessageSender
{
    public void Send(string message)
    {
        Console.WriteLine(message);
    }
}
Public Interface IMessageSender
    Sub Send(ByVal message As String) 
End Interface

<Export(GetType(IMessageSender)), ExportMetadata("transport", "smtp")>
Public Class EmailSender
    Implements IMessageSender
    Public Sub Send(ByVal message As String) Implements IMessageSender.Send
        Console.WriteLine(message) 
    End Sub
End Class

<Export(GetType(IMessageSender)), ExportMetadata("transport", "smtp"), ExportMetadata("secure", Nothing)> 
Public Class SecureEmailSender
    Implements IMessageSender
    Public Sub Send(ByVal message As String) Implements IMessageSender.Send
        Console.WriteLine(message) 
    End Sub
End Class

<Export(GetType(IMessageSender)), ExportMetadata("transport", "phone_network")>
Public Class SMSSender
    Implements IMessageSender
    Public Sub Send(ByVal message As String) Implements IMessageSender.Send
        Console.WriteLine(message) 
    End Sub
End Class

Using a Custom Export Attribute

In order to do it more strongly typed than using the ExportMetadataAttribute, you need to create your own attribute and decorate it with [System.ComponentModel.Composition.MetadataAttribute]. In this example we also derive from ExportAttribute, thus creating a custom Export attribute that also specifies metadata. 

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class MessageSenderAttribute : ExportAttribute
{
    public MessageSenderAttribute() : base(typeof(IMessageSender)) { }
    public MessageTransport Transport { get; set; }
    public bool IsSecure { get; set; }
}

public enum MessageTransport
{
    Undefined,
    Smtp,
    PhoneNetwork,
    Other
}
<MetadataAttribute(), AttributeUsage(AttributeTargets.Class, AllowMultiple:=False)> 
Public Class MessageSenderAttribute
    Inherits ExportAttribute
    Public Sub New()
        MyBase.New(GetType(IMessageSender)) 
    End Sub
    Public Property Transport() As MessageTransport
    Public Property IsSecure() As Boolean
End Class

Public Enum MessageTransport
    Undefined
    Smtp
    PhoneNetwork
    Other
End Enum

Above, the MetadataAttribute is applied to our custom export attribute. The next step is to apply the attribute to our IMessageSender implementations:

[MessageSender(Transport=MessageTransport.Smtp)]
public class EmailSender : IMessageSender
{
    public void Send(string message)
    {
        Console.WriteLine(message);
    }
}

[MessageSender(Transport=MessageTransport.Smtp, IsSecure=true)]
public class SecureEmailSender : IMessageSender
{
    public void Send(string message)
    {
        Console.WriteLine(message);
    }
}

[MessageSender(Transport=MessageTransport.PhoneNetwork)]
public class SMSSender : IMessageSender
{
    public void Send(string message)
    {
        Console.WriteLine(message);
    }
}

<MessageSender(Transport:=MessageTransport.Smtp)> 
Public Class EmailSender
    Implements IMessageSender
    Public Sub Send(ByVal message As String) Implements IMessageSender.Send
        Console.WriteLine(message) 
    End Sub
End Class

<MessageSender(Transport:=MessageTransport.Smtp, IsSecure:=True)> 
Public Class SecureEmailSender
    Implements IMessageSender
    Public Sub Send(ByVal message As String) Implements IMessageSender.Send
        Console.WriteLine(message) 
    End Sub
End Class

<MessageSender(Transport:=MessageTransport.PhoneNetwork)> 
Public Class SMSSender
    Implements IMessageSender
    Public Sub Send(ByVal message As String) Implements IMessageSender.Send
        Console.WriteLine(message) 
    End Sub
End Class

That’s all that is required on the export side. Under the hood, MEF is still populating a dictionary, but this fact becomes invisible to you.

Note: You can also create metadata attributes that are not themselves exports, by creating an attribute which is decorated with MetadataAttributeAttribute. In these cases the metadata will be added to Exports on the same member where the custom metadata attribute was applied.

Importing Metadata

Importers can access the metadata attached to the exports.

Using Strongly-typed Metadata

To access metadata in a strongly-typed fashion created a metadata view by definining an interface with matching read only properties (names and types). For our sample it would be an interface like the following:

public interface IMessageSenderCapabilities
{
    MessageTransport Transport { get; }
    bool IsSecure { get; }
}
Public Interface IMessageSenderCapabilities
    ReadOnly Property Transport() As MessageTransport
    ReadOnly Property IsSecure() As Boolean
End Interface

Then you can start importing using the type  System.Lazy<T, TMetadata> where T is the contract type and TMetadata is the interface you’ve created. 

[Export]
public class HttpServerHealthMonitor
{
    [ImportMany]
    public Lazy<IMessageSender, IMessageSenderCapabilities>[] Senders { get; set; }

    public void SendNotification()
    {
        foreach(var sender in Senders)
        {
            if (sender.Metadata.Transport == MessageTransport.Smtp && 
                sender.Metadata.IsSecure)
            {
                var messageSender = sender.Value;
                messageSender.Send("Server is fine");
                
                break;
            }
        }
    }
}
<Export()>
Public Class HttpServerHealthMonitor
    <ImportMany()>
    Public Property Senders() As Lazy(Of IMessageSender, IMessageSenderCapabilities)() 

    Public Sub SendNotification()
        For Each sender In Senders
            If sender.Metadata.Transport = MessageTransport.Smtp AndAlso sender.Metadata.IsSecure Then
                Dim messageSender = sender.Value
                messageSender.Send("Server is fine")

                Exit For
            End If
        Next sender
    End Sub
End Class

Using Weakly-typed metadata

To access metadata in a weakly-typed fashion, you import uisng the type System.Lazy<T, TMetadata> passing IDictionary<string,object> for the metadata. You can then access the metadata through the Metadata property which will be a dictionary.

Note: In general we recommend the strongly-typed method for accessing metadata, however there are systems that need to access the metadata in a dynamic fashion, which this allows.

[Export]
public class HttpServerHealthMonitor
{
    [ImportMany]
    public Lazy<IMessageSender, IDictionary<string,object>>[] Senders { get; set; }

    public void SendNotification()
    {
        foreach(var sender in Senders)
        {
            if (sender.Metadata.ContainsKey("Transport") && sender.Metadata["Transport"] == MessageTransport.Smtp && 
                sender.Metadata.ContainsKey("Issecure") && Metadata["IsSecure"] == true)
            {
                var messageSender = sender.Value;
                messageSender.Send("Server is fine");
                
                break;
            }
        }
    }
}
<Export()>
Public Class HttpServerHealthMonitor
    <ImportMany()>
    Public Property Senders() As Lazy(Of IMessageSender, IDictionary(Of String, Object))() 

    Public Sub SendNotification()
        For Each sender In Senders
            If sender.Metadata.ContainsKey("Transport") AndAlso sender.Metadata("Transport") = MessageTransport.Smtp AndAlso sender.Metadata.ContainsKey("Issecure") AndAlso sender.Metadata("IsSecure") = True Then
                Dim messageSender = sender.Value
                messageSender.Send("Server is fine")

                Exit For
            End If
        Next sender
    End Sub
End Class

Metadata filtering and DefaultValueAttribute

When you specifiy a metadata view, an implicit filtering will occur to match  only those exports which contain the metadata properties defined in the view. You can specify on the metadata view that a property is not required, by using the System.ComponentModel.DefaultValueAttribute. Below you can see where we have specified a default value of false on IsSecure. This means if a part exports IMessageSender, but does not supply IsSecure metadata, then it will still be matched.

public interface IMessageSenderCapabilities
{
    MessageTransport Transport { get; }
    [DefaultValue(false)];
    bool IsSecure { get; }
}
Public Interface IMessageSenderCapabilities
    ReadOnly Property Transport() As MessageTransport
    <DefaultValue(False)> 
    ReadOnly Property IsSecure() As Boolean
End Interface

http://mef.codeplex.com/wikipage?title=Exports%20and%20Metadata&referringTitle=Guide

npm WARN old lockfile npm WARN old lockfile The package-lock.json file was created with an old version of npm, npm WARN old lockfile so supplemental metadata must be fetched from the registry. npm WARN old lockfile npm WARN old lockfile This is a one-time fix-up, please be patient... npm WARN old lockfile npm WARN old lockfile vue-loader-v16: No matching version found for vue-loader-v16@16.8.1. npm WARN old lockfile at module.exports (D:\Nodejs\node_modules\npm\node_modules\npm-pick-manifest\lib\index.js:209:23) npm WARN old lockfile at RegistryFetcher.manifest (D:\Nodejs\node_modules\npm\node_modules\pacote\lib\registry.js:125:22) npm WARN old lockfile at async Array.<anonymous> (D:\Nodejs\node_modules\npm\node_modules\@npmcli\arborist\lib\arborist\build-ideal-tree.js:738:24) npm WARN old lockfile Could not fetch metadata for vue-loader-v16@16.8.1 vue-loader-v16: No matching version found for vue-loader-v16@16.8.1. npm WARN old lockfile at module.exports (D:\Nodejs\node_modules\npm\node_modules\npm-pick-manifest\lib\index.js:209:23) npm WARN old lockfile at RegistryFetcher.manifest (D:\Nodejs\node_modules\npm\node_modules\pacote\lib\registry.js:125:22) npm WARN old lockfile at async Array.<anonymous> (D:\Nodejs\node_modules\npm\node_modules\@npmcli\arborist\lib\arborist\build-ideal-tree.js:738:24) { npm WARN old lockfile code: 'ETARGET', npm WARN old lockfile type: 'version', npm WARN old lockfile wanted: '16.8.1', npm WARN old lockfile versions: [ npm WARN old lockfile '16.0.0-beta.5.4', npm WARN old lockfile '16.0.0-beta.5.3', npm WARN deprecated core-js@2.6.12: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js. npm WARN deprecated core-js@3.8.1: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
06-01
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值