Ninject 动态绑定到实现
Ninject dynamically bind to implementation
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
variable/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 的项目。看图。
I would like to do Ninject binding based on a runtime condition, that isn't pre-known on startup.
防止在构建对象图期间做出运行时决策。这会使您的配置复杂化,并使您的配置难以验证。理想情况下,您的对象图应该是固定的,并且不应在运行时改变形状。
相反,通过将其移动到 IExport
的代理 class 中,在...运行时做出运行时决策。这种代理到底是什么样子,取决于你的具体情况,但这里有一个例子:
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
实现,则可以为 ExportProxy
执行以下简单注册
kernel.Bind<IExport>().ToInstance(new ExportProxy(
assembly => kernel.Get<IExport>(assembly.Name)));
重要的是要注意,虽然实际的 "condition match" 是运行时条件,但您实际上提前知道了可能的匹配集(至少在构建容器时启动时是这样)——使用证明了这一点的公约。这就是条件/上下文绑定的意义所在(在 Ninject WIKI 中有描述,并在几个问题中有所涉及)。所以你实际上不需要在任意运行时进行绑定,而你只需要在任意时间进行 resolution/selection (解决方案实际上可以提前完成 => 提前失败)。
这是一个可能的解决方案,其特点是:
- 启动时创建所有绑定
- 提前失败:启动时验证绑定(通过实例化所有绑定
IExport
s)
- 在任意运行时选择
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));
}
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
variable/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 的项目。看图。
I would like to do Ninject binding based on a runtime condition, that isn't pre-known on startup.
防止在构建对象图期间做出运行时决策。这会使您的配置复杂化,并使您的配置难以验证。理想情况下,您的对象图应该是固定的,并且不应在运行时改变形状。
相反,通过将其移动到 IExport
的代理 class 中,在...运行时做出运行时决策。这种代理到底是什么样子,取决于你的具体情况,但这里有一个例子:
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
实现,则可以为 ExportProxy
kernel.Bind<IExport>().ToInstance(new ExportProxy(
assembly => kernel.Get<IExport>(assembly.Name)));
重要的是要注意,虽然实际的 "condition match" 是运行时条件,但您实际上提前知道了可能的匹配集(至少在构建容器时启动时是这样)——使用证明了这一点的公约。这就是条件/上下文绑定的意义所在(在 Ninject WIKI 中有描述,并在几个问题中有所涉及)。所以你实际上不需要在任意运行时进行绑定,而你只需要在任意时间进行 resolution/selection (解决方案实际上可以提前完成 => 提前失败)。
这是一个可能的解决方案,其特点是:
- 启动时创建所有绑定
- 提前失败:启动时验证绑定(通过实例化所有绑定
IExport
s) - 在任意运行时选择
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));
}