AutoFac解析参数

时间:2012-11-04 11:03:32

标签: autofac

我有许多实现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具有静态settingsKey

是否可以解析所有IFilter实现并指定应该用于实例化settingsKey实现的ITimeSpanFilterSettings

1 个答案:

答案 0 :(得分: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注册一个依赖项,这样你只需要手动注入你指定的一个参数,其余参数将自动完成: / p>

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可以访问的地方。

鉴于你提到了一个&#34;视图,&#34;我假设您的意思是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。不过,我不喜欢服务地点,很多人认为它是一种反模式,#34;如果可以,请避免使用它。

关于缓存的说明:听起来您的配置值实际上来自某个地方的数据库 - 与阅读AppSettings相比,这是一个更昂贵的调用。您可以将数据库调用权限置于注册中,但如果您解决了大量这些问题,则可能会遇到一些有趣的性能挑战。两种情况下的lambda都会在每次分辨率发生时执行 - 参数值不会为您缓存,除非您使用InstancePerDependency生命周期以外的其他内容(默认值)注册对象,否则Autofac不会缓存创建的对象。这可能意味着很多非预期的数据库调用。找出缓存(根据需要)是一个留给读者的练习。

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

关于设计的一件事:这是一个意见,但一般我会尽量避免&#34;参数化分辨率,&#34;原样。也就是说,&#34;我想要一个将军IFilter,但它需要完全适合这种特定情况。&#34;这听起来像你在这里所拥有的。在这些情况下,我发现虽然我可能需要使用像IFilter这样的公共基本级接口,但我也会尝试使用特定于我需要的接口。

public interface ICustomSituationFilter : IFilter
然后我会使用这些自定义接口作为我的依赖项,而不是试图将所有内容都推向通用。它允许我更容易地分离&#34;配置&#34;控制器之外的概念(不应该配置传入的依赖关系)并将其推送到我的注册中 - 我不需要将内容放入HttpContext.Items或任何类型的共享位置,因为只有知道设置的地方才是实际的依赖注册。您可能需要考虑更改您的设计以打破&#34;选择视图&#34;和&#34;使用了哪些配置设置&#34;如果你可以的话。它会让你的生活更轻松。

相关的Autofac维基页面