0

我有许多实现IFilter接口的过滤器类(定义过滤器逻辑)。使用构造函数注入,对于每个过滤器实现,我想传递一个定义过滤器设置的接口。考虑以下示例:

interface IFilter
{
  void Filter(DataSource dataSource);
}

interface ITimeSpanFilterSettings
{
  DateTime From {get; set; }
  DateTime To {get; set; }
}

public class TimeSpanFilter : IFilter
{
  private ITimeSpanFilterSettings settings;

  public TimeSpanFilter(ITimeSpanFilterSettings settings)
  {
     this.settings = settings;
  }
}

然而,我的具体实现ITimeSpanFilterSettings需要settingsKey从数据库中检索设置。但是我无法 ITimeSpanFilterSettings使用 static注册我的实现settingsKey

是否可以解决所有IFilter实现并指定一个settingsKey应该用于实例化ITimeSpanFilterSettings实现的方法?

4

1 回答 1

8

在我看来,有几个因素在起作用。可能是我对问题的误解,也可能是一些问题“缩写”,所以请多多包涵。

首先说一下TimeSpanFilter获取ITimeSpanFilterSettings对象的分辨率。稍后我们将讨论设置对象的参数化,现在我们只讨论将设置传递给过滤器

如果你有描述的设置,我推断你有一个ISomethingFilterSettings对应于每个IFilter实现的接口。你有TimeSpanFilterITimeSpanFilterSettings; 如果你有一个DateTimeFilter,你就会有一个IDateTimeFilterSettings

鉴于此,您不需要做任何特别的事情。注册您的各种类型,ContainerBuilder神奇的事情就会发生。

var builder = new ContainerBuilder();
builder.RegisterType<TimeSpanFilter>().As<IFilter>();
builder.RegisterType<TimeSpanFilterSettings>().As<ITimeSpanFilterSettings>();
var container = builder.Build();
// When you resolve, the TimeSpanFilterSettings class gets instantiated
// and injected into the constructor of the TimeSpanFilter.
var filter = container.Resolve<IFilter>();

即使您有多个过滤器,Autofac 也会将所有适当的接口与构造函数参数对齐。你不必做任何事情。

var builder = new ContainerBuilder();
// Look, Ma! Two filters and settings! :)
builder.RegisterType<TimeSpanFilter>().As<IFilter>();
builder.RegisterType<TimeSpanFilterSettings>().As<ITimeSpanFilterSettings>();
builder.RegisterType<DateTimeFilter>().As<IFilter>();
builder.RegisterType<DateTimeFilterSettings>().As<IDateTimeFilterSettings>();
var container = builder.Build();
// You can resolve collections and get all of the registered filters.
var filterEnumerable = container.Resolve<IEnumerable<IFilter>>();

现在让我们谈谈过滤器设置对象的参数化。听起来您需要进行一些配置,因此(为方便起见)假设配置来自AppSettings.

使用 Autofac,您可以将 lambda 表达式注册为依赖项,而不仅仅是具体类型,因此您可以执行以下操作:

var builder = new ContainerBuilder();
builder.RegisterType<TimeSpanFilter>().As<IFilter>();
builder.Register(
  ctx =>
  {
    var config = ConfigurationSettings.AppSettings["my-key"];
    return new TimeSpanFilterSettings(config);
  }).As<ITimeSpanFilterSettings>();
var container = builder.Build();
// When you resolve, the TimeSpanFilterSettings class gets instantiated
// and injected into the constructor of the TimeSpanFilter.
var filter = container.Resolve<IFilter>();

如果您的设置对象只有一个传入参数,那么这种事情非常方便。如果有多个参数,您也可以在 lambda 中使用传入的上下文参数来即时进行一些解析:

builder.Register(
  ctx =>
  {
    var config = ConfigurationManager.AppSettings["my-key"];
    var other = ctx.Resolve<OtherDependency>();
    return new TimeSpanFilterSettings(config, other);
  }).As<ITimeSpanFilterSettings>();

但是,如果您有太多参数可能会有点混乱,那么您也可以使用参数 lambda 注册一个依赖项,这样只有您指定的一个参数将被手动注入,其余的将自动完成:

var builder = new ContainerBuilder();
builder.RegisterType<TimeSpanFilterSettings>().WithParameter(
  // Parameter selector determines which parameter this
  // thing is referring to - here the constructor parameter
  // is called "config" and has to be a System.String.
  (pinfo, ctx) =>
  {
    return
      pinfo.Name == "config" &&
      pinfo.ParameterType == typeof(string);
  },

  // Value provider gets the value that should be injected
  // and returns it.
  (pinfo, ctx) =>
  {
    return ConfigurationManager.AppSettings["my-key"];
  });

这些中的任何一个都可以工作,这取决于你想怎么做。

额外的复杂性:您在对此答案的评论中提到,您正在根据用户选择的视图获取设置信息。您将需要更新您的系统以将该设置密钥放在 Autofac 可以访问的位置。

鉴于您提到了“视图”,我假设您的意思是 ASP.NET MVC 或类似的东西。一个放置请求级值的地方是在HttpContext.Items. 这可能需要在您的系统中进行一些重新设计。

例如,如果依赖项必须作为控制器上的构造函数/属性进入,那么您可能需要有一些机制来在HttpContext控制器实例化之前填充值。也许您的控制器上有一个属性,也许有一个IHttpModule位于管道中并具有 URL 到设置的映射,也许是别的东西。这不是我们在这个问题中可以处理的事情(否则我们只会在这里编写整个产品,对吗?我真的不适合那个......)。

一旦它在那个中心位置,就像HttpContext.Items你可以把它放到 lambda 注册中一样:

// Need to be able to resolve HttpContext, so...
builder.RegisterModule<AutofacWebTypesModule>();
// Then resolve HttpContext in your registration:
builder.Register(
  ctx =>
  {
    var httpCtx = ctx.Resolve<HttpContextBase>();
    var configKey = httpCtx.Items["settings-config-key"];
    var config = ConfigurationManager.AppSettings[configKey];
    return new TimeSpanFilterSettings(config);
  }).As<ITimeSpanFilterSettings>()
  .InstancePerHttpRequest();

创建属性的替代方法是在设置值后从控制器内部IHttpModule调用。不过,我不喜欢服务位置,而且许多人认为它是一种“反模式”,因此请尽可能避免使用它。DependencyResolver.Current.GetService<IFilter>()HttpContext.Items

关于缓存的注意事项:听起来您的配置值实际上来自某个数据库 - 与读取相比,调用成本更高AppSettings。您可以将数据库调用直接放在注册中,但是如果您解决了其中的一堆问题,您可能会遇到一些有趣的性能挑战。两种情况下的 lambdas 都会在每次解析发生时执行 - 参数值不会为您缓存,除非您使用InstancePerDependency生命周期以外的东西(默认值)注册对象,否则 Autofac 不会缓存创建的对象。这可能意味着大量意外的数据库调用。找出缓存(根据需要)是留给读者的练习。

(请注意,在最后一个示例中,我用作InstancePerHttpRequest范围 - 这意味着您将为一个 Web 请求获得缓存。)

关于设计的一件事:这是一种观点,但通常我会尽量避免“参数化分辨率”。也就是说,“我想要一个将军IFilter,但它需要完全适应这种特定情况。” 听起来这就是你在这里所拥有的。在这些情况下,我发现虽然我可能需要使用通用的基础级接口,例如IFilter,但我也会尝试使用特定于我需要的接口。

public interface ICustomSituationFilter : IFilter

然后,我将使用这些自定义接口作为我的依赖项,而不是试图将所有内容都推向通用。它使我可以更轻松地将“配置”概念从控制器中分离出来(它不应该配置传入的依赖项)并将其推送到我的注册中 - 我不需要将东西弹出HttpContext.Items或任何类型的共享位置因为唯一知道设置的地方是实际的依赖注册。如果可以的话,您可能需要考虑更改您的设计以打破“选择视图”和“使用哪些配置设置”之间的联系。它会让你的生活更轻松。

相关 Autofac wiki 页面:

于 2012-11-06T01:13:08.517 回答