出口和元数据
声明出口解释基本的部件输出服务和值。有些情况由于种种原因需要用出口关联信息。一般情况下它是用来解释一个公共契约具体实现的性能。这可以有效地满足允许出口或者限制出口,或者在那时导入所有可用的实现和在使用出口之前检查它们的运行能力。
附加元数据到出口
想一下我们之前的IMessageSender服务。假如我们有一些实现,而这些实现与消者有关而有所不同。在我们的例子里消息是否运行和是否安全对消费者(importer)是重要的信息。
使用ExportMetadataAttribute
附加这些信息我们只需要做的就是使用[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); } }
使用自定义出口属性
为了使它比ExportMetadataAttribute更严格的类型,你需要创建自己的属性并且用[System.ComponentModel.Composition.MetadataAttribute]修饰.在这个例子里面,我们也从ExportAttribute派生,所以创建一个自定义出口属性也是指定的元数据。
[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应用于自定义的出口属性,下一步是应用属性到IMessageSender实现。
[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); } }
这就是出口方所必须的。在引擎里,MEF仍作为一个字典,但这个事实对你来说是无形的。
注意:你也可以创建元数据属性不是他们自己出口,只需要通过创建使用MetadataAttributeAttribute修饰的属性。
在这些例子里元数据会被添加到出口自定义元数据属性被应用的同一成员里。
进口元数据
进口商可以访问附加到出口的元数据。
使用强类型元数据
若要以强类型的方式访问元数据通过定义接口匹配只读属性 (名称和类型)来创建元数据视图。 对于我们的示例来说将是类似于以下内容一个接口:
public interface IMessageSenderCapabilities { MessageTransport Transport { get; } bool IsSecure { get; } }
于是你可以开始用System.Lazy<T, TMetadata>导入。(T是契约类型,TMetadata是你创建的接口)
[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; } } } }
使用弱类型元数据
为了以弱类型方式访问元数据,你使用System.Lazy<T, TMetadata>类型进口传递IDictionary<string,object>元数据。然后你就可以通过作为字典的元数据属性来访问元数据。
注意:一般我们推荐用强类型方法去访问元数据,然而有些系统需要通过动态方式访问元数据,这也是允许的。
[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; } } } }
元数据过滤和默认属性值
当你选中一个元数据视图,一个隐藏的过滤会触发去匹配那些包含在视图定义的元数据属性的出口。你可以在元数据视图里通过System.ComponentModel.DefaultValueAttribute指定属性不需要。在下面你可以看到,我们已经给IsSecure指定一个默认值false。这意味着一个部件出口IMessageSender,但没有提供IsSecure元数据,但它仍然匹配。
public interface IMessageSenderCapabilities { MessageTransport Transport { get; } [DefaultValue(false)]; bool IsSecure { get; } }