![](/img/trans.png)
[英]How to register decorators with Autofac when using RegisterAssemblyTypes?
[英]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>();
useA
和useB
從配置您的動態提供值。
現在解析(或依賴) 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
變量並創建一個getNextKey
和getCurrentKey
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.