5

Stack Overflow 上有几个类似但不完全是我要找的问题。我想根据运行时条件进行 Ninject 绑定,这在启动时并不预先知道。Stack Overflow 上用于动态绑定的其他问题围绕基于配置文件或类似文件的绑定展开——我需要在处理特定实体的数据时根据数据库值有条件地发生它。例如,

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:最终使用 's 的解决方案进行了一些试验和错误,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 显式破坏项目。看图。

4

2 回答 2

3

重要的是要注意,虽然实际的“条件匹配”是运行时条件,但您实际上提前知道可能的匹配集(至少在构建容器时启动时)——这可以通过使用约定来证明。这就是条件/上下文绑定的内容(在 Ninject WIKI 中进行了描述,并在几个问题中进行了介绍)。因此,您实际上不需要在任意运行时进行绑定,而只需在任意时间进行解析/选择(实际上可以提前完成解析=>提前失败)。

这是一个可能的解决方案,其特点是:

  • 在启动时创建所有绑定
  • 早期失败:在启动时验证绑定(通过所有 boundIExport的实例化)
  • 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可以注入任何组件并像“必需”一样使用:

foreach (Partner partner in _db.Partners)
{
    // pseudocode...
    IExport exportModule = exportDictionary.Get(partner.ExportImplementationAssembly);
    exportModule.ExportData(_db.GetPartnerData(partner.PartnerID));
}
于 2015-09-07T13:20:25.630 回答
2

我想根据运行时条件进行 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);
    }
}

通过为代理提供 aFunc<,>您仍然可以在您注册ExportProxy(组合根)的位置做出决定,您可以在该位置向系统查询程序集。通过这种方式,您可以IExport在容器中预先注册实现,从而提高配置的可验证性。如果您IExport使用密钥注册了所有实现,您可以为ExportProxy

kernel.Bind<IExport>().ToInstance(new ExportProxy(
    assembly => kernel.Get<IExport>(assembly.Name)));
于 2015-09-07T11:50:37.670 回答