如何在autofac中混合装饰器?

我希望能够将装饰器与Autofac混合搭配。

例如,假设我有一个由Repository类实现的IRepository接口。
我可以拥有以下装饰器:RepositoryLocalCache,RepositoryDistributedCache,RepositorySecurity,RepositoryLogging …,你得到了ideea。

基于配置设置,我想用所需的装饰器来装饰基本实现。 那可以是无,一个或多个装饰器。

我熟悉注册一个装饰器的语法,或者按固定顺序注册它们的链,但我怎样才能使这个动态化呢?

正如Steven在上面指出的那样,Autofac中的RegisterDecorator方法并不是真正针对这种情况设计的,而且使用起来相当笨拙。 它们是针对某些使用常规Autofac注册难以实现的情况而构建的 – 这样做的“原生”方式更加清晰。

例如, IFoo是服务, Impl是具体(例如存储库)实现。

 interface IFoo { } class Impl : IFoo { } class DecoratorA : IFoo { public DecoratorA(IFoo decorated) { } } class DecoratorB : IFoo { public DecoratorB(IFoo decorated) { } } 

首先使用其具体类型注册所有组件:

 var builder = new ContainerBuilder(); builder.RegisterType(); builder.RegisterType(); builder.RegisterType(); 

Lambda注册也很好,只要确保它们使用As()

现在是一个包装器,它们将它们链接起来以提供完全配置的服务:

 bool useA = true, useB = false; builder.Register(c => { IFoo result = c.Resolve(); if (useA) result = c.Resolve(TypedParameter.From(result)); if (useB) result = c.Resolve(TypedParameter.From(result)); return result; }).As(); 

useBuseB是您从配置中动态提供的值。

现在解析(或依赖) IFoo将为您提供动态构造的装饰链。

 using (var container = builder.Build()) { var foo = container.Resolve(); 

如果你使用generics的东西比较棘手,因为你没有提到它们我不会进入它,但如果你是,那么请发布另一个问题。

有条件地应用装饰器在Autofac中实际上非常麻烦。 让我们分两步完成。 首先让我们编写无条件应用这些装饰器的代码:

 var builder = new ContainerBuilder(); builder.RegisterType().Named("implementor"); builder.RegisterDecorator( (c, inner) => new RepositoryLocalCache(inner), fromKey: "implementor", toKey: "decorator1"); builder.RegisterDecorator( (c, inner) => new RepositoryDistributedCache(inner), fromKey: "decorator1", toKey: "decorator2"); builder.RegisterDecorator( (c, inner) => new RepositorySecurity(inner), fromKey: "decorator2", toKey: "decorator3"); builder.RegisterDecorator( (c, inner) => new RepositoryLogging(inner), fromKey: "decorator3", toKey: null); 

在Autofac中应用装饰器是通过使用键控注册( toKey )注册具有相同服务类型(在您的情况下为IRepository )的多个组件并使用toKey这些注册指向彼此来完成的。 最外面的装饰器应该是无键的,因为默认情况下,Autofac将始终为您解析无密钥注册。

这些密钥注册是Autofac在这方面最大的弱点,因为装饰器由于这些键而硬连接到下一个。 如果只是将RepositoryDistributedCache包装在if -block中,配置将会中断,因为RepositorySecurity现在将指向不存在的注册。

解决这个问题的方法是动态生成密钥并添加一个额外的“虚拟”无密钥装饰器,它不是有条件地应用的:

 int counter = 0; Func getCurrentKey => () => counter; Func getNextKey => () => ++counter; var builder = new ContainerBuilder(); builder.RegisterType().Named(getCurrentKey()); if (config.UseRepositoryLocalCache) { builder.RegisterDecorator( (c, inner) => new RepositoryLocalCache(inner), fromKey: getCurrentKey(), toKey: getNextKey()); } if (config.UseRepositoryDistributedCache) { builder.RegisterDecorator( (c, inner) => new RepositoryDistributedCache(inner), fromKey: getCurrentKey(), toKey: getNextKey()); } if (config.UseRepositorySecurity) { builder.RegisterDecorator( (c, inner) => new RepositorySecurity(inner), fromKey: getCurrentKey(), toKey: getNextKey()); } if (config.UseRepositoryLogging) { builder.RegisterDecorator( (c, inner) => new RepositoryLogging(inner), fromKey: getCurrentKey(), toKey: getNextKey()); } // The keyless decorator that just passes the call through. builder.RegisterDecorator( (c, inner) => new RepositoryPassThrough(inner), fromKey: getCurrentKey(), toKey: null); 

这里我们使用一个counter变量并创建一个getNextKeygetCurrentKey lambdas,这样可以更容易地进行配置。 再次注意最后一个RepositoryPassThrough装饰器。 这个装饰者应该简单地称它为decoratee而不做任何其他事情。 拥有这个额外的装饰器可以更容易地完成配置; 否则将很难决定最后一个装饰器是什么。

使Autofac变得更加困难的一个原因是缺乏对非通用装饰器的自动布线支持。 据我所知,这是Autofac API中唯一不支持自动连接(即让容器找出要注入的构造函数参数)的部分。 如果可以使用类型而不是委托来完成注册会容易得多,因为在这种情况下我们可以构建要应用的装饰器的初始列表,而不仅仅是迭代列表。 我们仍然需要处理这些关键注册。

我刚刚偶然发现了这个post,并想分享我是如何做到这一点的:

不久前,我写了几个扩展方法来简化这个问题。 这些方法类似于@Steven的答案,因为它们可以动态创建实现的名称。 但是,它们不使用RegisterDecorator ,这意味着不需要“传递”实现。

方法可以这样使用:

 builder.RegisterDecorated(); builder.RegisterDecorator(); builder.RegisterDecorator(); 

这种实现有几个优点:

  • 可以有条件地打开或关闭装饰器
  • 只要至少有一个注册不是装饰器,就可以注册零个,一个或多个装饰器
  • 一旦调用了RegisterDecorator ,您就可以从建立RegisterDecorator的任何地方免费调用RegisterDecorator 。 这意味着您可以在完全独立的Autofac模块中注册装饰器,该模块位于与原始实现不同的assembly或项目中以进行装饰。
  • 您可以通过更改注册顺序来控制装饰器的嵌套顺序。 最外面的装饰器将是最后一个要注册的装饰器。

扩展方法如下所示:

 public static class ContainerBuilderExtensions { private static readonly IDictionary _implementationNames = new ConcurrentDictionary(); public static void RegisterDecorated(this ContainerBuilder builder) where T : TImplements { builder.RegisterType() .As() .Named(GetNameOf()); } public static void RegisterDecorator(this ContainerBuilder builder) where T : TImplements { var nameOfServiceToDecorate = GetOutermostNameOf(); builder.RegisterType(); builder.Register(c => { var impl = c.ResolveNamed(nameOfServiceToDecorate); impl = c.Resolve(TypedParameter.From(impl)); return impl; }) .As() .Named(GetNameOf()); } private static string GetNameOf() { var type = typeof(T); var name = type.FullName + Guid.NewGuid(); _implementationNames[type] = name; return name; } private static string GetOutermostNameOf() { var type = typeof(T); if (!_implementationNames.ContainsKey(type)) { throw new Exception("Cannot call RegisterDecorator for an implementation that is not decorated. Ensure that you have called RegisterDecorated for this type before calling RegisterDecorator."); } return _implementationNames[typeof(T)]; } } 

上面的代码段尽可能简单,但它对我很有帮助。 当然,如果您有更复杂的要求,可以进行更改。

这个概念构成了我对Orchard CMS的贡献的基础,它增加了装饰器function: https : //github.com/OrchardCMS/Orchard/pull/6233 。