我有许多实现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
?
答案 0 :(得分:8)
在我看来,有几个因素在起作用。这可能是我对这个问题的误解,或者可能是一些问题"缩写,"所以请耐心等待。
首先让我们谈谈TimeSpanFilter
获取ITimeSpanFilterSettings
对象的解决方案。我们稍后会参与设置对象的参数化,现在让我们来谈谈将设置带到过滤器。
如果你有所描述的设置,我推断你有一个ISomethingFilterSettings
接口对应于每个IFilter
实现。您有TimeSpanFilter
和ITimeSpanFilterSettings
;如果您有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维基页面