簡體   English   中英

使用 Autofac 將多種類型中的一種注冊到接口

[英]Register one of many types to an interface using Autofac

Autofac 有大量的注冊技術,其中大部分我都很難理解。 閱讀了一段時間的文檔,我沒有看到任何可以讓我這樣做的東西。

我有一個不受我控制的系統,它根據一些命令行參數實例化某個 class。 這個 class 是所有衍生自公共基礎 class 的眾多產品之一。 例如:

abstract class BaseCommand {}
class CommandOne : BaseCommand {}
class CommandTwo : BaseCommand {}

直到組件注冊之后,我才會知道這個庫將構建哪個派生 class ( CommandOneCommandTwo )。 以下是代碼外觀的粗略輪廓:

static void Main() {
  // Do all the autofac registrations
  IContainer container = CompositionRoot.Setup();

  // Parse command line arguments
  ParseCommandLine(type => container.Resolve(type))
}

ParseCommandLine()方法是構造我之前提到的兩個類中的任何一個的“黑盒”代碼。 它通過調用我傳遞給它的 lambda 來實現。 永遠不會實例化這兩個類,而只會實例化其中一個。

我需要 Autofac 允許我“延遲注冊” BaseCommand作為服務。 也就是說,在注冊時,我們不知道會選擇哪個特定的上下文,但我們知道它總是從BaseCommand派生的。 所以大致是這樣的:

var builder = new ContainerBuilder();
builder.Register(ctx => /*Access the type passed to Resolve() and instantiate that*/).As<BaseCommand>().AsSelf().SingleInstance();

所以基本上我想要的是:

  1. 我們只能為BaseCommand注冊 1 個具體類型
  2. 在解決發生之前,我們不知道該實現是什么
  3. 我們使用提供給Resolve()的類型來決定
  4. 我們將永遠只有一個該類型的實例。
  5. 嘗試解析任何其他具體類型應該失敗(例如,一旦我解析類型CommandOne ,解析CommandTwo應該拋出異常)

我怎樣才能在 Autofac 中做到這一點?

我不確定你是否能夠 100% 地獲得你想要的,但我認為你可以通過一些間接的方式獲得非常接近的結果。

首先,您需要一種可以在 lambda 表達式中使用的中介物,這樣您就可以延遲實際的注冊內容。 它有幾個工作:

  • 存儲黑盒選取的選定類型。 通過這種方式,它可以在 lambda 中使用,以“推遲”確定選擇了哪些命令。
  • 注冊可用的命令,但使用隨機的“密鑰”,這樣它們就不會意外得到解決。 該容器是不可變的,因此您以后不能“取消注冊”事物,並且您需要解決(或可能解決)的任何事情都需要注冊。
  • 解析可用的命令,但使用過濾器,以便獲得選定的命令。

它看起來像這樣:

public static class CommandFilter
{
  private static readonly Guid _id = Guid.NewGuid();

  public static Type SelectedCommand { get; set; }

  public static void Register(ContainerBuilder builder, Type type)
  {
    // The "secret key" will be required to resolve these,
    // so container.Resolve<T>() won't work - it'd have
    // to be container.ResolveKeyed<T>(_id) to get it.
    builder.RegisterType(type).Keyed(type, _id);
  }

  public static BaseCommand Resolve(IComponentContext context)
  {
    // Only resolve the selected implementation, everything else
    // stays "hidden."
    return context.ResolveKeyed(_id, CommandFilter.SelectedCommand) as BaseCommand;
  }
}

現在您需要注冊所有BaseCommand實現,但使用此過濾器。 這意味着為您進行一些手動裝配掃描。

// WARNING: I can never remember the right direction for
// IsAssignableFrom so check this by testing.
var asm = typeof(BaseCommand).Assembly;
foreach(var type in asm.GetTypes().Where(t => typeof(BaseCommand).IsAssignableFrom(t)))
{
  // Register your "secret" keyed registrations.
  CommandFilter.Register(builder, type);
}

// Register the BaseCommand itself.
builder.Register(ctx => CommandFilter.Resolve(ctx))
  .As<BaseCommand>()
  .SingleInstance();

在您的黑匣子中,關鍵是首先設置過濾器類型,然后解析。

ParseCommandLine(type => {
  CommandFilter.SelectedCommand = type;
  container.Resolve<BaseCommand>();
});

注冊中的 lambda 允許您推遲選擇; 傳遞給ParseCommandLine的 lambda 可以進行選擇; 只有你想要的那個會得到解決。 其他所有內容都已注冊但實際上無法訪問,並且對container.Resolve<NotTheSelectedCommand>()的全權委托調用將失敗並出現解析異常,因為它們已注冊為鍵控並且需要鍵來解析。

我實際上並沒有通過編譯器運行它,但似乎這應該可以解決問題。 根據“真實”系統,您可能需要在設置SelectedCommand的位置加鎖,因為雖然container.Resolve<T>()是線程安全的,但您的選擇機制在其外部運行。 對於那些在設置SelectedCommand之前嘗試從命令過濾器中解決的人來說,可能還需要一些錯誤處理,諸如此類。

暫無
暫無

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

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