项目
版本

Autofac 组件元数据/属性元数据

如果你熟悉管理扩展框架 (Managed Extensibility Framework, MEF),你可能已经见过使用组件元数据的例子。

元数据是关于组件的信息,与组件一起存储,可以在不创建组件实例的情况下访问。

在组件注册时添加元数据

描述元数据的值在组件注册时关联到组件。每个元数据项是一个名称/值对:

builder.Register(c => new ScreenAppender())
    .As<ILogAppender>()
    .WithMetadata("AppenderName", "screen");

同样的内容也可以用在 部署时配置 中:

{
  "components": [
    {
      "type": "MyApp.Components.Logging.ScreenAppender, MyApp",
      "services": [
        {
          "type": "MyApp.Services.Logging.ILogAppender, MyApp"
        }
      ],
      "metadata": [
        {
          "key": "AppenderName",
          "value": "screen",
          "type": "System.String, mscorlib"
        }
      ]
    }
  ]
}

消费元数据

与常规属性不同,元数据项独立于组件本身。

这使得它在基于运行时条件选择多个组件或元数据不是组件实现固有属性的情况下很有用。元数据可以代表 ITask 应该运行的时间,或者 ICommand 的按钮标题。

其他组件可以通过 Meta<T> 类型来消费元数据。

public class Log
{
  readonly IEnumerable<Meta<ILogAppender>> _appenders;

  public Log(IEnumerable<Meta<ILogAppender>> appenders)
  {
    _appenders = appenders;
  }

  public void Write(string destination, string message)
  {
    var appender = _appenders.First(a => a.Metadata["AppenderName"].Equals(destination));
    appender.Value.Write(message);
  }
}

要不创建目标组件的情况下消费元数据,可以使用 Meta<Lazy<T>> 或.NET 4 的 Lazy<T, TMetadata> 类型,如下面所示。

强类型元数据

为了避免使用字符串键来描述元数据,可以定义一个包含每个元数据项的公共读写属性的元数据类:

public class AppenderMetadata
{
  public string AppenderName { get; set; }
}

在注册时,可以使用重载的 WithMetadata 方法将值关联起来:

builder.Register(c => new ScreenAppender())
    .As<ILogAppender>()
    .WithMetadata<AppenderMetadata>(m =>
        m.For(am => am.AppenderName, "screen"));

注意使用了强类型的 AppenderName 属性。

注册和消费元数据是分开的,因此强类型元数据可以通过弱类型技术消费,反之亦然。

您还可以通过 DefaultValue 属性提供默认值:

public class AppenderMetadata
{
  [DefaultValue("screen")]
  public string AppenderName { get; set; }
}

如果您的解决方案能够引用 System.ComponentModel.Composition ,可以使用 System.Lazy<T, TMetadata> 类型从强类型元数据类中消费值:

public class Log
{
  readonly IEnumerable<Lazy<ILogAppender, LogAppenderMetadata>> _appenders;

  public Log(IEnumerable<Lazy<ILogAppender, LogAppenderMetadata>> appenders)
  {
    _appenders = appenders;
  }

  public void Write(string destination, string message)
  {
    var appender = _appenders.First(a => a.Metadata.AppenderName == destination);
    appender.Value.Write(message);
  }
}

另一个巧妙的技巧是将元数据字典传递给元数据类的构造函数:

public class AppenderMetadata
{
  public AppenderMetadata(IDictionary<string, object> metadata)
  {
    AppenderName = (string)metadata["AppenderName"];
  }

  public string AppenderName { get; set; }
}

接口为基础的元数据

如果您能访问 System.ComponentModel.Composition 并包含对 Autofac.Mef 包的引用,您可以使用接口而不是类作为元数据。

接口应定义具有每个元数据项的可读属性:

public interface IAppenderMetadata
{
  string AppenderName { get; }
}

在将元数据注册到接口类型之前,还必须调用 ContainerBuilder 上的 RegisterMetadataRegistrationSources 方法。

builder.RegisterMetadataRegistrationSources();

在注册时,可以使用接口和重载的 WithMetadata 方法将值关联起来:

builder.Register(c => new ScreenAppender())
    .As<ILogAppender>()
    .WithMetadata<IAppenderMetadata>(m =>
        m.For(am => am.AppenderName, "screen"));

元数据的获取方式与基于类的元数据相同。

属性为基础的元数据

Autofac.Extras.AttributeMetadata 包使您可以通过属性指定元数据。Autofac 核心支持允许组件根据属性过滤传入的依赖项。

要在解决方案中使带有属性的元数据工作,请按照以下步骤操作:

  1. 创建元数据属性
  2. 应用元数据属性
  3. 在消费中使用元数据过滤
  4. 确保容器使用您的属性

创建元数据属性

元数据属性是一个实现了 System.AttributeSystem.ComponentModel.Composition.MetadataAttributeAttribute 的类。

该属性上的任何公开可读属性将成为名称/值属性对 - 元数据名称将是属性名称,值将是属性值。

在下面的示例中,AgeMetadataAttribute 将提供一个元数据名称/值对,其中名称将是 Age(属性名称),值将是构造期间指定的属性值。

[MetadataAttribute]
public class AgeMetadataAttribute : Attribute
{
  public int Age { get; private set; }

  public AgeMetadataAttribute(int age)
  {
    Age = age;
  }
}

应用元数据属性

一旦有了元数据属性,就可以将其应用于组件类型以提供元数据。

// 不要应用于接口(服务类型)
public interface IArtwork
{
  void Display();
}

// 应用于实现(组件类型)
[AgeMetadata(100)]
public class CenturyArtwork : IArtwork
{
  public void Display() { ... }
}

在消费中使用元数据过滤

除了通过属性提供元数据外,您还可以设置自动过滤组件。这将帮助根据提供的元数据设置构造函数参数。

可以根据 服务键 或注册元数据进行过滤。这种基于属性的过滤无需自定义元数据属性即可完成。

KeyFilterAttributeMetadataFilterAttributeWithAttributeFiltering 扩展方法可以在核心 Autofac 包的 Autofac.Features.AttributeFilters 命名空间中找到。

KeyFilterAttribute

KeyFilterAttribute 允许您选择特定键的服务进行消费。

以下示例显示了一个需要具有特定键的组件的类:

public class ArtDisplay : IDisplay
{
  public ArtDisplay([KeyFilter("Painting")] IArtwork art) { ... }
}

要使用的组件需要注册具有指定名称的关键服务。还需要为容器注册组件,以便容器知道要查找它。

var builder = new ContainerBuilder();

// 注册要消费的关键服务
builder.RegisterType<MyArtwork>().Keyed<IArtwork>("Painting");

// 为消费者指定WithAttributeFiltering
builder.RegisterType<ArtDisplay>().As<IDisplay>().WithAttributeFiltering();

// ...
var container = builder.Build();

MetadataFilterAttribute

MetadataFilterAttribute 允许您根据特定元数据值过滤组件。

以下示例显示了一个需要具有特定元数据值的组件的类:

public class ArtDisplay : IDisplay
{
  public ArtDisplay([MetadataFilter("Age", 100)] IArtwork art) { ... }
}

要使用的组件需要注册具有指定名称/值对的元数据。可以使用前面示例中看到的带属性元数据类,或者在注册时手动指定元数据。还需要为容器注册组件,以便容器知道要查找它。

var builder = new ContainerBuilder();

// 注册带有元数据的服务。
// 因为我们使用了带属性元数据,所以也需要注册AttributedMetadataModule,
// 以便读取元数据属性。
builder.RegisterModule<AttributedMetadataModule>();
builder.RegisterType<CenturyArtwork>().As<IArtwork>();

// 为消费者指定WithAttributeFiltering
builder.RegisterType<ArtDisplay>().As<IDisplay>().WithAttributeFiltering();

// ...
var container = builder.Build();

确保容器使用您的属性

您创建的元数据属性不会默认使用。为了告诉容器您正在使用元数据属性,需要将 AttributedMetadataModule 注册到容器中。

var builder = new ContainerBuilder();

// 注册带有元数据的服务。由于我们使用的是属性元数据,因此还需要注册 `AttributedMetadataModule`,
// 以便读取元数据属性。
builder.RegisterModule<AttributedMetadataModule>();
builder.RegisterType<CenturyArtwork>().As<IArtwork>();

// ...
var container = builder.Build();

如果你在构造函数中使用元数据过滤器(例如 KeyFilterAttributeWithAttributeFiltering),则需要使用 WithAttributeFiltering 扩展来注册这些组件。请注意,如果你仅使用过滤器而不用属性元数据,实际上并不需要 AttributedMetadataModule。元数据过滤器可以独立使用。

var builder = new ContainerBuilder();

// 对消费者指定 WithAttributeFilter
builder.RegisterType<ArtDisplay>().As<IDisplay>().WithAttributeFiltering();
// ...
var container = builder.Build();
在本文档中