Ninject динамически привязывается к реализации
Есть несколько вопросов по 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
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>();
Создание реализаций экспорта в двух тестовых модулях работает и создает 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.
2 ответа
Важно отметить, что хотя фактическое "совпадение условий" является условием времени выполнения, вы на самом деле заранее знаете возможный набор совпадений (по крайней мере, при запуске при сборке контейнера), о чем свидетельствует использование соглашений. Вот что такое условные / контекстные привязки (описаны в Ninject WIKI и описаны в нескольких вопросах). Таким образом, вам на самом деле не нужно выполнять привязку в произвольное время выполнения, скорее, вам просто нужно выполнить разрешение / выборку в произвольное время (на самом деле разрешение может быть выполнено заранее => потерпеть неудачу рано).
Вот возможное решение, которое включает в себя:
- создание всех привязок при запуске
- преждевременный сбой: проверка привязок при запуске (через создание всех связанных
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
может быть вставлен в любой компонент и просто использован как "требуется":
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
реализации с использованием ключа, вы можете сделать следующую простую регистрацию для ExportProxy
kernel.Bind<IExport>().ToInstance(new ExportProxy(
assembly => kernel.Get<IExport>(assembly.Name)));