如何在autofac中混合装饰器

本文关键字:混合 autofac | 更新日期: 2023-09-27 18:01:00

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

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

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

我熟悉注册一个装饰器或按固定顺序注册它们的链的语法,但如何使其动态?

如何在autofac中混合装饰器

正如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<Impl>();
builder.RegisterType<DecoratorA>();
builder.RegisterType<DecoratorB>();

Lambda注册也很好,只需确保它们使用As<IFoo>()即可。

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

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

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

现在,解析(或依赖)IFoo将获得一个动态构建的装饰器链。

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

如果你使用泛型,事情会更棘手,因为你没有提到它们,我不会深入讨论,但如果你是,请再提出一个问题。

在Autofac中有条件地应用装饰符实际上相当麻烦。让我们分两步来做。首先,让我们编写无条件应用这些装饰器的代码:

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

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

这些密钥注册是Autofac在这方面最大的弱点,因为这些密钥使装饰器与下一个绑定在一起。如果简单地将RepositoryDistributedCache封装在if-块中,则配置将中断,因为RepositorySecurity现在将指向不存在的注册。

这个问题的解决方案是动态生成密钥,并添加一个没有条件应用的额外"伪"无钥匙装饰器:

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

在这里,我们使用了counter变量,并创建了getNextKeygetCurrentKey lambdas,使配置更容易。再次注意最后一个RepositoryPassThrough装饰器。这个decorator应该简单地调用它的decoratee,而不做任何其他事情。有了这个额外的装饰器,完成配置就容易多了;否则就很难决定最后一个装饰师是谁

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

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

不久前,我写了几个扩展方法来简化这个问题。这些方法与@Steven的答案相似,因为它们为动态实现创建名称。然而,它们不使用RegisterDecorator,这意味着不需要"直通"实现。

方法可以这样使用:

builder.RegisterDecorated<EnquiryService, IEnquiryService>();
builder.RegisterDecorator<ServiceBusEnquiryServiceDecorator, IEnquiryService>();
builder.RegisterDecorator<EmailNotificationDecorator, IEnquiryService>();

这种实现有几个优点:

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

扩展方法如下所示:

public static class ContainerBuilderExtensions
{
    private static readonly IDictionary<Type, string> _implementationNames = new ConcurrentDictionary<Type, string>();
    public static void RegisterDecorated<T, TImplements>(this ContainerBuilder builder) where T : TImplements
    {
        builder.RegisterType<T>()
            .As<TImplements>()
            .Named<TImplements>(GetNameOf<TImplements>());
    }
    public static void RegisterDecorator<T, TImplements>(this ContainerBuilder builder) where T : TImplements
    {
        var nameOfServiceToDecorate = GetOutermostNameOf<TImplements>();
        builder.RegisterType<T>();
        builder.Register(c =>
        {
            var impl = c.ResolveNamed<TImplements>(nameOfServiceToDecorate);
            impl = c.Resolve<T>(TypedParameter.From(impl));
            return impl;
        })
            .As<TImplements>()
            .Named<TImplements>(GetNameOf<TImplements>());
    }
    private static string GetNameOf<T>()
    {
        var type = typeof(T);
        var name = type.FullName + Guid.NewGuid();
        _implementationNames[type] = name;
        return name;
    }
    private static string GetOutermostNameOf<T>()
    {
        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的贡献的基础,该CMS增加了装饰器功能:https://github.com/OrchardCMS/Orchard/pull/6233.