如何在autofac中混合装饰器?

Att*_*asz 4 .net c# dependency-injection ioc-container autofac

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

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

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

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

Nic*_*rdt 12

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

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

interface IFoo { }

class Impl : IFoo { }

class DecoratorA : IFoo
{
    public DecoratorA(IFoo decorated) { }
}

class DecoratorB : IFoo
{
    public DecoratorB(IFoo decorated) { }
}
Run Code Online (Sandbox Code Playgroud)

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

var builder = new ContainerBuilder();

builder.RegisterType<Impl>();
builder.RegisterType<DecoratorA>();
builder.RegisterType<DecoratorB>();
Run Code Online (Sandbox Code Playgroud)

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>();
Run Code Online (Sandbox Code Playgroud)

useA并且useB是您从配置中动态提供的值.

现在解析(或依赖)IFoo会得到一个动态构造的装饰链.

using (var container = builder.Build())
{
    var foo = container.Resolve<IFoo>();
Run Code Online (Sandbox Code Playgroud)

如果你正在使用泛型,那么事情就比较棘手,因为你没有提到它们我不会进入它但是如果你那么请发表另一个问题.


Ste*_*ven 6

在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);
Run Code Online (Sandbox Code Playgroud)

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

这些密钥注册是Autofac在这方面最大的弱点,因为装饰器由于这些密钥而硬连接到下一个.如果您只是将其包装RepositoryDistributedCacheif-block中,则配置将中断,因为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);    
Run Code Online (Sandbox Code Playgroud)

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

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