Где разместить модули Ninject в многоуровневом приложении

Мое приложение включает в себя ряд внутренних сборок (включая уровень репозитория данных Entity Framework), которые используются рядом интерфейсных сборок (включая службу Windows и веб-приложение MVC3).

Насколько я понимаю, процесс привязки Ninject заключается в том, что каждая сборка, содержащая вводимые типы, также должна содержать модуль Ninject, который определяет привязки по умолчанию для этих типов. Затем набор определенных модулей будет загружен в ядро ​​Ninject потребляющих сборок.

Однако я сталкиваюсь с проблемами, поскольку требуемый объем привязки не всегда согласован. Например, мой проект MVC должен быть привязан к контексту данных InRequestScope, тогда как служба Windows привязывается к тому же классу InThreadScope.

Я, очевидно, могу решить эту проблему, переместив все модули во внешние проекты и, таким образом, сохранить отдельные копии каждого модуля для каждого сценария использования, но это кажется хакерским, поскольку он дублирует большую часть содержимого модуля в нескольких проектах.

Есть ли лучший способ размещения модулей в многоуровневом приложении и как я могу согласовать это с моей потребностью в привязке различий между проектами?

Большое спасибо за ваши предложения,

Тим


person Tim Coulter    schedule 16.08.2012    source источник
comment
см. также stackoverflow.com/questions / 1699197 / (IIRC, этот Q - дубли, но это лучшее, что у меня есть на данный момент)   -  person Ruben Bartelink    schedule 18.08.2012
comment
Спасибо, Рубен. Вы правы в том, что между этими двумя вопросами много общего. Мне особенно нравится ваше предложение о передаче параметров времени выполнения в модули, которые находятся во вспомогательных сборках - очень гибко.   -  person Tim Coulter    schedule 19.08.2012
comment
Хм; это было некоторое время назад (никаким образом не пытался обмануть мой ответ). Возможно, в свое время я буквально имел в виду передачу параметров - в общем, я бы старался делать это через интерфейсы, насколько это возможно. Кроме того, это было до manning.com/seemann, что сокращает количество вопросов, которые могут вызвать недоумение в Архитектура DI резко - def run buy it без вопросов.   -  person Ruben Bartelink    schedule 20.08.2012


Ответы (2)


Для решения с одним приложением общий совет - зарегистрировать контейнер в проекте приложения (веб-приложение или проект веб-службы). Для веб-приложения это обычно Global.asax Application_Start. Это место, где вы соединяете все вместе, в терминологии DI называется корнем композиции.

В решении с несколькими приложениями у вас все равно будет один корень композиции для каждого проекта приложения. Так должно быть, поскольку каждое приложение имеет свою уникальную конфигурацию. С другой стороны, дублированный код - это всегда плохо. Вам не нужно менять три места, когда вы вводите новую абстракцию.

Хитрость заключается в том, чтобы переместить все регистрации вниз по иерархии проекта. Например, вы можете определить одну «сборку начальной загрузки», которая зависит от ваших сборок бизнес-уровня (и ниже), и позволить ей иметь все регистрации для тех сборок, которые не меняются. Затем составные корни приложений могут использовать эту сборку для получения регистраций по умолчанию и расширения ее зависимостей, специфичных для приложения.

Такая вещь может выглядеть так:

// MVC Composition root
public static void Bootstrap()
{
    var container = new Container();

    // Default registrations
    BusinessLayerBootstrapper.Bootstrap(container);

    // Application specific registrations
    container.Bind<IUserContext>().To<AspNetUserContext>();

    DependencyResolver.Current = 
        new ContainerDependencyResolver(container);
}

// Windows Service Composition root
public static void Bootstrap()
{
    var container = new Container();

    // Default registrations
    BusinessLayerBootstrapper.Bootstrap(container);

    // Application specific registrations
    container.Bind<IUserContext>().To<SystemUserContext>()
        .SingleScoped();

    // Store somewhere.
    Bootstrapper.Container = container;
}

// In the BL bootstrap assembly
public static class BusinessLayerBootstrapper
{
    public static void Bootstrap(Container container)
    {
        container.Bind<IDepenency>().To<RealThing>();
        // etc
    }
}

Хотя вам не нужно иметь отдельную сборку загрузчика (вы можете разместить этот код в самом BL), это позволяет сохранить сборки бизнес-уровня свободными от каких-либо зависимостей от вашего контейнера.

Также обратите внимание, что я просто вызываю статический метод Bootstrap() вместо использования модулей (Ninject). Я постарался, чтобы мой ответ не зависел от фреймворка, поскольку ваш вопрос является общим, и совет будет одинаковым для всех фреймворков DI. Однако, конечно, вы можете использовать функцию модуля Ninject, если хотите.

person Steven    schedule 16.08.2012
comment
Рекомендуется ли в этом сценарии подход к упаковке функций? - person Ewerton; 30.06.2018

Что касается области видимости, приложение MVC должно иметь другой CompositionRoot, чем служба Windows. Я предлагаю вам попытаться организовать как можно больше по функциональным модулям (для тех частей, которые не зависят от приложения) и всем другим привязкам непосредственно в CompositionRoot проекта MVC или WindowsService.

Еще один очень хороший подход - определить общий набор соглашений, который поможет вам выразить наиболее обязывающие проблемы в нескольких строках. Поэтому ваши приложения могут иметь следующие привязки:

Приложение MVC

Bind(c => c.FromAssemblyContaining<IRepository>()
           .SelectAllClasses()
           .InheritedFrom<IRepository>()
           .Configure(b => b.InRequestScope()));

Ваше приложение-служба Windows

Bind(c => c.FromAssemblyContaining<IRepository>()
           .SelectAllClasses()
           .InheritedFrom<IRepository>()
           .Configure(b => b.InThreadScope()));

С моей точки зрения, в сочетании с функционально-ориентированным структурированием конвенционный подход является наиболее чистым.

person Daniel Marbach    schedule 16.08.2012