簡體   English   中英

如何在autofac中混合裝飾器?

[英]How to mix decorators in 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<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應用裝飾用(注冊多個部件使用相同的服務類型進行IRepository使用密鑰注冊(在你的情況) toKey ),並在彼此使用指向這些注冊fromKey )。 最外面的裝飾器應該是無鍵的,因為默認情況下,Autofac將始終為您解析無密鑰注冊。

這些密鑰注冊是Autofac在這方面最大的弱點,因為裝飾器由於這些密鑰而硬連接到下一個。 如果只是將RepositoryDistributedCache包裝在if -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);    

這里我們使用一個counter變量並創建一個getNextKeygetCurrentKey lambdas,這樣可以更容易地進行配置。 再次注意最后一個RepositoryPassThrough裝飾器。 這個裝飾者應該簡單地稱它為decoratee而不做任何其他事情。 擁有這個額外的裝飾器可以更容易地完成配置; 否則將很難決定最后一個裝飾器是什么。

使Autofac變得更加困難的一個原因是缺乏對非通用裝飾器的自動布線支持。 據我所知,這是Autofac API中唯一不支持自動連接(即讓容器找出要注入的構造函數參數)的部分。 如果可以使用類型而不是委托來完成注冊會容易得多,因為在這種情況下我們可以構建要應用的裝飾器的初始列表,而不僅僅是迭代列表。 我們仍然需要處理這些關鍵注冊。

我剛剛偶然發現了這個帖子,並想分享我是如何做到這一點的:

不久前,我寫了幾個擴展方法來簡化這個問題。 這些方法類似於@Steven的答案,因為它們可以動態地為實現創建名稱。 但是,它們不使用RegisterDecorator ,這意味着不需要“傳遞”實現。

方法可以這樣使用:

builder.RegisterDecorated<EnquiryService, IEnquiryService>();

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

這種實現有幾個優點:

  • 可以有條件地打開或關閉裝飾器
  • 只要至少有一個注冊不是裝飾器,就可以注冊零個,一個或多個裝飾器
  • 一旦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的貢獻的基礎,它增加了裝飾器功能: https//github.com/OrchardCMS/Orchard/pull/6233

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM