簡體   English   中英

Ninject動態綁定到實現

[英]Ninject dynamically bind to implementation

關於SO的幾個問題很相似,但不完全是我要尋找的問題。 我想基於運行時條件進行Ninject綁定,這在啟動時並不為人所知。 關於用於動態綁定的SO的其他問題圍繞基於配置文件等的綁定-我需要在處理特定實體的數據時根據數據庫值有條件地進行綁定。 例如,

public class Partner
{
    public int PartnerID { get; set; }
    public string ExportImplementationAssembly { get; set; }
}

public interface IExport
{
    void ExportData(DataTable data);
}

在其他地方,我有2個實現IExport的dll。

public PartnerAExport : IExport
{
    private readonly _db;
    public PartnerAExport(PAEntities db)
    {
        _db = db;
    }
    public void ExportData(DataTable data)
    {
        // export parter A's data...
    }
}

然后是伙伴B;

public PartnerBExport : IExport
{
    private readonly _db;
    public PartnerBExport(PAEntities db)
    {
        _db = db;
    }
    public void ExportData(DataTable data)
    {
        // export parter B's data...
    }
}

當前的Ninject綁定是;

public class NinjectWebBindingsModule : NinjectModule
{
    public override void Load()
    {
        Bind<PADBEntities>().ToSelf();
        Kernel.Bind(s => s.FromAssembliesMatching("PartnerAdapter.*.dll")
                          .SelectAllClasses()
                          .BindDefaultInterfaces()
                   );
    }
}

因此,如何設置綁定以使我可以做到;

foreach (Partner partner in _db.Partners)
{
    // pseudocode...
    IExport exportModule = ninject.Resolve<IExport>(partner.ExportImplementationAssembly);
    exportModule.ExportData(_db.GetPartnerData(partner.PartnerID));
}

這可能嗎? 看起來應該是這樣,但是我不知道該怎么做。 上面的現有綁定配置可用於靜態綁定,但我需要在運行時可以解決的問題。 以上可能嗎,還是我只需要繞過Ninject並使用老式的反射來加載插件? 如果是這樣,我如何使用該方法通過Ninject解析任何構造函數參數,就像靜態綁定的對象一樣?

更新:我已經使用BatteryBackupUnit的解決方案更新了我的代碼,現在我有了以下內容:

Bind<PADBEntities>().ToSelf().InRequestScope();
Kernel.Bind(s => s.FromAssembliesMatching("PartnerAdapter.*.dll")
                    .SelectAllClasses()
                    .BindDefaultInterfaces()
                    .Configure(c => c.InRequestScope())
            );

Kernel.Bind(s => s.FromAssembliesMatching("PartnerAdapter.Modules.*.dll")
                    .SelectAllClasses()
                    .InheritedFrom<IExportService>()
                    .BindSelection((type, baseTypes) => new[] { typeof(IExportService) })
            );
Kernel.Bind<IExportServiceDictionary>().To<ExportServiceDictionary>().InSingletonScope();
ExportServiceDictionary dictionary = KernelInstance.Get<ExportServiceDictionary>();

在2個測試模塊中實例化導出實現可以正常工作,並且實例化PADBEntites上下文也很好。 但是,我的服務層中的所有其他綁定現在不再對系統的其余部分起作用。 同樣,如果將PADBEntities變量/ ctor參數更改為ISomeEntityService組件,則無法綁定導出層。 似乎我缺少配置綁定以完成這項工作的最后一步。 有什么想法嗎?

錯誤:“激活ISomeEntityService時出錯。沒有匹配的綁定可用,並且類型不可自綁定”

更新2 :最終我使用BatteryBackupUnit的解決方案進行了一些反復試驗,盡管我對籃球跳得不太滿意。 任何其他更簡潔的解決方案都歡迎。

我更改了原來的約定綁定;

        Kernel.Bind(s => s.FromAssembliesMatching("PartnerAdapter.*.dll")
                          .SelectAllClasses()
                          .BindDefaultInterfaces()
                   );

更冗長和明確;

Bind<IActionService>().To<ActionService>().InRequestScope();
Bind<IAuditedActionService>().To<AuditedActionService>().InRequestScope();
Bind<ICallService>().To<CallService>().InRequestScope();
Bind<ICompanyService>().To<CompanyService>().InRequestScope();
//...and so on for 30+ lines

不是我最喜歡的解決方案,但它適用於基於顯式和基於約定的綁定,但不適用於兩個約定。 誰能看到我在綁定時出錯了嗎?

更新3 :忽略更新2中綁定的問題。似乎我在Ninject中發現了一個錯誤,該錯誤與在引用的庫中具有多個綁定模塊有關。 即使從未通過斷點命中,對模塊A的更改也將使用其他模塊B明確地中斷項目。

重要的是要注意,雖然實際的“條件匹配”是運行時條件,但實際上您實際上預先知道了可能的匹配集(至少在構建容器時是在啟動時)-這可以通過使用約定來證明。 這就是條件/上下文綁定的含義(在Ninject WIKI中進行了描述,並涵蓋了幾個問題)。 因此,您實際上不需要在任意運行時進行綁定,而只需要在任意時間進行解析/選擇(解析實際上可以提前完成=>盡早失敗)。

這是一個可能的解決方案,其特點是:

  • 在啟動時創建所有綁定
  • 提前失敗:在啟動時驗證綁定(通過實例化所有綁定的IExport
  • 在任意運行時選擇IExport

internal interface IExportDictionary
{
    IExport Get(string key);
}

internal class ExportDictionary : IExportDictionary
{
    private readonly Dictionary<string, IExport> dictionary;

    public ExportDictionary(IEnumerable<IExport> exports)
    {
        dictionary = new Dictionary<string, IExport>();
        foreach (IExport export in exports)
        {
            dictionary.Add(export.GetType().Assembly.FullName, export);
        }
    }

    public IExport Get(string key)
    {
        return dictionary[key];
    }
}

成分根:

// this is just going to bind the IExports.
// If other types need to be bound, go ahead and adapt this or add other bindings.
kernel.Bind(s => s.FromAssembliesMatching("PartnerAdapter.*.dll")
        .SelectAllClasses()
        .InheritedFrom<IExport>()
        .BindSelection((type, baseTypes) => new[] { typeof(IExport) }));

kernel.Bind<IExportDictionary>().To<ExportDictionary>().InSingletonScope();

// create the dictionary immediately after the kernel is initialized.
// do this in the "composition root".
// why? creation of the dictionary will lead to creation of all `IExport`
// that means if one cannot be created because a binding is missing (or such)
// it will fail here (=> fail early).
var exportDictionary = kernel.Get<IExportDictionary>(); 

現在,可以將IExportDictionary注入到任何組件中,並像“ required”一樣使用:

foreach (Partner partner in _db.Partners)
{
    // pseudocode...
    IExport exportModule = exportDictionary.Get(partner.ExportImplementationAssembly);
    exportModule.ExportData(_db.GetPartnerData(partner.PartnerID));
}

我想基於運行時條件進行Ninject綁定,這在啟動時並不為人所知。

防止在構建對象圖時做出運行時決策。 這會使您的配置復雜化,並使您的配置難以驗證。 理想情況下,對象圖應該是固定的,並且在運行時不應改變形狀。

取而代之的是,通過將其移到IExport的代理類中,在...運行時決策運行時。 此類代理的實際外觀取決於您的實際情況,但以下是一個示例:

public sealed class ExportProxy : IExport
{
    private readonly IExport export1;
    private readonly IExport export2;
    public ExportProxy(IExport export1, IExport export2) {
        this.export1 = export1;
        this.export2 = export2;
    }

    void IExport.ExportData(Partner partner) {
        IExport exportModule = GetExportModule(partner.ExportImplementationAssembly);
        exportModule.ExportData(partner);
    }

    private IExport GetExportModule(ImplementationAssembly assembly) {
        if (assembly.Name = "A") return this.export1;
        if (assembly.Name = "B") return this.export2;
        throw new InvalidOperationException(assembly.Name);
    }
}

也許您正在處理一組動態確定的程序集。 在這種情況下,您可以為代理提供導出提供程序委托。 例如:

public sealed class ExportProxy : IExport
{
    private readonly Func<ImplementationAssembly, IExport> exportProvider;
    public ExportProxy(Func<ImplementationAssembly, IExport> exportProvider) {
        this.exportProvider = exportProvider;
    }

    void IExport.ExportData(Partner partner) {
        IExport exportModule = this.exportProvider(partner.ExportImplementationAssembly);
        exportModule.ExportData(partner);
    }
}

通過為代理提供Func<,>您仍然可以在注冊ExportProxy (組合根)的位置進行決策,在該位置可以查詢系統的程序集。 這樣,您可以在容器中IExport注冊IExport實現,從而提高配置的可驗證性。 如果你注冊的所有IExport使用一鍵實現,你可以做了以下簡單的注冊ExportProxy

kernel.Bind<IExport>().ToInstance(new ExportProxy(
    assembly => kernel.Get<IExport>(assembly.Name)));

暫無
暫無

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

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