2015-09-07 8 views
2

Есть несколько вопросов о SO, которые похожи, но не совсем то, что я ищу. Я хотел бы сделать привязку Ninject на основе условия выполнения, которое не было известно при запуске. Другие вопросы о SO для динамического связывания вращаются вокруг привязки на основе файла конфигурации или некоторых таких - мне нужно, чтобы это произошло условно на основе значения базы данных при обработке данных для определенного объекта. НАПРИМЕР,Ninject динамически привязывается к реализации

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

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

В другом месте, у меня есть 2 DLLs, которые реализуют 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... 
    } 
} 

Нисходящее связывание;

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, как со статически связанными объектами?

UPDATE: Я обновил свой код с помощью решения 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 контекст просто отлично. Тем не менее, все остальные привязки на моем уровне услуг теперь больше не работают для остальной части системы. Аналогично, я не могу связать уровень экспорта, если я изменяю аргумент переменной/ctor PADBEntities компоненту ISomeEntityService. Кажется, я пропустил последний шаг в настройке привязок, чтобы получить эту работу. Есть предположения?

Ошибка: «Ошибка активация ISomeEntityService Нет соответствующие привязки не доступны, и тип не является самим-Привязываемым.»

Update 2: В конце концов получила эту работу с небольшим количеством проб и ошибок с использованием BatteryBackupUnit «s решения хотя я не слишком доволен обручами, чтобы передумать. Любое другое более сжатое решение приветствуется.

Я изменил первоначальную конвенцию;

 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. Перейдите к рисунку.

+0

ИМО, ответы ниже все реализации той или иной форме фабрика, что является правильным ответом. Внесите завод, позвольте фабрике вернуть правильный IExport. Несмотря на некоторые (проницательные) комментарии в ответах ниже, наличие этого на заводе помогает изолировать вас от конкретных функций. –

+0

@Atoms, ninject здесь несколько требуется, так как реализациям экспорта требуются другие сервисы в их ctors, которые уже связаны через ядро ​​ninject.Имеет смысл повторно использовать их, а не реализовывать конкретную фабрику. – DiskJunky

+1

Использование соглашения из двух частей, в котором отсутствует привязка? Также обратите внимание, что с помощью соглашения из двух частей вы бы связали каждый «IExport» дважды. Для первого соглашения вы должны были исключить все 'IExport'. Я предлагаю вам задать новый вопрос в отношении того, как формировать вопрос. Может быть, другая платформа SE, такая как codereview или программисты, будет лучше. ** Предложение **: Почему бы не иметь соглашение для всех типов, заканчивающихся в «Сервис», и, в частности, связать все остальное? Более того, если есть более конкретные привязки, они могут быть помещены в 'NinjectModule' в конкретных сборках. – BatteryBackupUnit

ответ

3

Важно отметить, что в то время как фактическое «условие соответствия» является условием выполнения, вы на самом деле знаете возможный набор (по крайней мере, при запуске при создании контейнера), что подтверждается использованием конвенций. Это то, что связано с условными/контекстуальными привязками (описано в 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 может быть введен в любой компонент и просто использовать как «требуется»:

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

хорошая реализация с тем, чтобы быть понятнее, чем Стив. Однако привязка не позволяет мне создать экземпляр словаря, когда я начал добавлять другие интерфейсы/объект PADBEntities к кодам реализации экспорта. Есть идеи? – DiskJunky

+0

Это (скорее всего) означает, что не все типы, требуемые некоторым «IExport», связаны. Обратите внимание, что в моем ответе я изменил привязку соглашения к типам 'PartnerAdapter. *. Dll'. Возможно, вы захотите изменить это на то, что вы изначально имели, или сделать более подходящее соглашение/привязку. Я сделал это изменение, потому что я не рекомендую такого рода очень широкое соглашение. Скорее, у меня будет dll-реализация некоторого 'NinjectModule' и загрузите их с помощью' kernel.Load'. Если мое подозрение неверно, отправьте все исключение (тип, сообщение, stacktrace, потенциальные внутренние исключения ..) – BatteryBackupUnit

+0

Я понял что-то такое же, но выбор привязки, который у вас есть в вашем примере, нарушает существующее связывание, которое у меня было в OP - он больше не может видеть ссылки IService в ctors, которые я уже установил. Ошибка: 'Ошибка активации ISomeEntityService. Нет соответствующих привязок, и тип не является самопереключаемым'. Это связывание работает, как указано в исходном сообщении, но не при добавлении привязки для IExport – DiskJunky

2

I would like to do Ninject binding based on a runtime condition, that isn't pre-known on startup.

Предотвращение принятия решений во время построения графиков объектов. Это усложняет вашу конфигурацию и затрудняет проверку конфигурации. В идеале, ваши графические объекты должны быть фиксированными и не должны изменять форму во время выполнения.

Вместо этого сделайте решение времени выполнения в ... runtime, переместив его в прокси-класс для 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))); 
+0

Эффективный, но проблематичный - потенциально большое количество партнеров использует неизвестное окончательное количество реализаций экспорта. Для вашего решения потребуется новый аргумент конструктора и обновление 'if' или' switch' каждый раз, когда мы хотим добавить новую реализацию. Тем не менее, я не рассматривал этот подход – DiskJunky

+0

@DiskJunky: Мое решение не требует нового аргумента ctor; это просто простой пример. Адаптируйте решение к вашему конкретному случаю. Если у вас много сборок, вы можете захотеть вместо этого ввести словарь. – Steven

+0

По какой-то причине мы, как разработчики (в том числе и я), испытываем соблазн в попытке решить все проблемы, используя инструменты, которые мы используем, в то время как некоторые проблемы не для инструментов для решения. Если мы сделаем шаг назад и рассмотрим проблему, как будто мы вообще не используем инструмент, это часто приводит к более чистым и более практичным решениям. – Steven

 Смежные вопросы

  • Нет связанных вопросов^_^