我有许多实现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;
}
}
Run Code Online (Sandbox Code Playgroud)
ITimeSpanFilterSettings
但是,我的具体实现需要settingsKey
从数据库中检索设置.但是我无法ITimeSpanFilterSettings
用静态注册我的实现
settingsKey
.
是否有可能解决所有IFilter
实现并指定settingsKey
应该用于实例化ITimeSpanFilterSettings
实现的实现?
在我看来,有几个因素在起作用.这可能是我对这个问题的误解,或者可能是一些问题"缩写",所以请耐心等待.
首先让我们谈谈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>();
Run Code Online (Sandbox Code Playgroud)
即使您有多个过滤器,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>>();
Run Code Online (Sandbox Code Playgroud)
现在让我们谈谈过滤器设置对象的参数化.听起来你需要得到一些配置,所以让我们说(为了方便)配置来自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>();
Run Code Online (Sandbox Code Playgroud)
如果您只为设置对象提供一个传入参数,那么这种事情非常方便.如果有多个参数,您可以使用lambda中的传入上下文参数来动态执行某些分辨率:
builder.Register(
ctx =>
{
var config = ConfigurationManager.AppSettings["my-key"];
var other = ctx.Resolve<OtherDependency>();
return new TimeSpanFilterSettings(config, other);
}).As<ITimeSpanFilterSettings>();
Run Code Online (Sandbox Code Playgroud)
但是,如果您有太多参数可能会有点混乱,那么您也可以使用参数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"];
});
Run Code Online (Sandbox Code Playgroud)
这些都可以工作,这取决于你想要怎么做.
其他复杂性:您在此答案的评论中提到,您根据用户选择的视图获取设置信息.您需要更新系统,将该设置密钥放在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();
Run Code Online (Sandbox Code Playgroud)
设置属性后的替代方法,或者在设置值后从控制器内部IHttpModule
调用.我不喜欢服务地点,很多人认为它是一种"反模式",所以如果可以,请避免使用它.DependencyResolver.Current.GetService<IFilter>()
HttpContext.Items
关于缓存的说明:听起来您的配置值实际上来自某个地方的数据库 - 与阅读相比,这是一个更昂贵的调用AppSettings
.您可以将数据库调用权限置于注册中,但如果您解决了大量这些问题,则可能会遇到一些有趣的性能挑战.两种情况下的lambda都会在每次分辨率发生时执行 - 参数值不会为您缓存,除非您使用InstancePerDependency
生命周期以外的其他内容(默认值)注册对象,否则Autofac不会缓存创建的对象.这可能意味着很多非预期的数据库调用.找出缓存(根据需要)是留给读者的练习.
(请注意,我用作InstancePerHttpRequest
范围的最后一个示例- 这意味着您将获得一个Web请求的缓存.)
关于设计的一件事:这是一种观点,但通常我会尽量避免使用"参数化分辨率".也就是说,"我想要一名将军IFilter
,但需要完全适应这种特殊情况." 听起来这就是你在这里所拥有的.在这些情况下,我发现尽管我可能需要使用通用的基本级接口IFilter
,但我也会尝试使用特定于我需要的接口.
public interface ICustomSituationFilter : IFilter
Run Code Online (Sandbox Code Playgroud)
然后,我将使用这些自定义接口作为我的依赖项,而不是试图将所有内容都推向通用.它允许我更容易地将"配置"概念从控制器中分离出来(它不应该必须配置传入的依赖项)并将其推送到我的注册中 - 我不需要将内容放入HttpContext.Items
或任何类型的共享位置因为唯一了解设置的地方是实际的依赖注册.如果可以,您可能需要考虑更改设计以打破"选择视图"和"使用哪些配置设置"之间的联系.它会让你的生活更轻松.
相关的Autofac维基页面:
归档时间: |
|
查看次数: |
5148 次 |
最近记录: |