Как внедрить Dependency Injection с помощью MVC5 и MEF2 (на основе конвенций) в n-уровневом приложении?

Я начинаю новый проект MVC и (почти) решил попробовать шаблон репозитория и внедрение зависимостей. Потребовалось время, чтобы проанализировать варианты, но я придумал следующую структуру для своего приложения:

  1. Уровень представления: интерфейсная часть ASP.Net MVC (представления / контроллеры и т. Д.)
  2. Сервисный уровень (Business Layer, если хотите): интерфейсы и DTO.
  3. Уровень данных: реализации интерфейса и классы Entity Framework.

Это 3 отдельных проекта в моем решении. Уровень представления имеет только ссылку на уровень служб. Уровень данных также имеет ссылку только на уровень сервисов, так что это в основном соответствует дизайну, управляемому доменом.

Смысл структурирования вещей таким образом заключается в разделении проблем, слабой связи и проверяемости. Я с удовольствием приму совет по улучшению, если что-либо из этого является необоснованным?

Часть, с которой я сталкиваюсь, заключается в том, чтобы внедрить реализующий интерфейс объект из уровня данных в уровень представления, который знает только об интерфейсах на уровне служб. Кажется, это именно то, для чего предназначен DI, и платформы IoC (якобы!) Делают это проще, поэтому я решил попробовать MEF2. Но из десятков статей, вопросов и ответов, которые я прочитал за последние несколько дней, кажется, что на самом деле ничто не решает эту проблему таким образом, чтобы соответствовать моей структуре. Почти все устарели и / или являются простыми примерами консольных приложений, которые имеют все интерфейсы и классы в одной сборке, зная все друг о друге и полностью игнорируя точку слабой связи и DI. Я также видел других, которые требуют, чтобы dll уровня данных помещалась в папку bin уровня представления и настраивалась для просмотра других классов, что опять же мешает идее слабой связи.

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

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

Ирония заключается в том, что современные шаблоны проектирования должны абстрагировать сложность и упростить нашу задачу, но я был бы уже наполовину закончен, если бы я только сослался на DAL из PL и начал бы работать над реальной функциональностью приложения. Я был бы очень признателен, если бы кто-то мог сказать: "Да, я понимаю, что вы делаете, но вам не хватает xyz. Что вам нужно сделать, это abc'.

Благодарю.

2 ответа

Решение

Ну, я потратил еще пару дней на это (что сейчас составляет около недели) и немного продвинулся вперед. Я вполне уверен, что у меня правильно настроен контейнер с моими соглашениями, определяющими правильные части для сопоставления и т. Д., Но я не мог выяснить, что, как мне казалось, было недостающим звеном для активации контроллера DI - я постоянно получал сообщение об ошибке о том, что я не предоставил конструктор без параметров. Итак, я закончил с этим.

Однако мне удалось продвинуться вперед с моей структурой и намерением использовать DI с IoC. Если кто-то попадет в ту же стену, что и я, и захочет альтернативное решение: бросьте MEF 2 и переходите на Unity. Последняя версия (3.5 на момент написания) имеет соглашение об обнаружении и просто работает как из коробки - у нее даже есть довольно подробное руководство с работающими примерами. Существуют и другие платформы IoC, но я выбрал Unity, поскольку он поддерживается MS и хорошо справляется с оценками производительности. Установите пакет начальной загрузки от NuGet, и большая часть работы сделана для вас. В конце мне нужно было написать только одну строку кода для сопоставления всего моего DAL (они даже создали заглушку для вас, чтобы вы знали, где ее вставить):

        container.RegisterTypes(
            AllClasses.FromLoadedAssemblies().Where(t => t.Namespace == "xxx.DAL.Repository"),
            WithMappings.FromMatchingInterface,
            WithName.Default);

Да, я понимаю, что вы делаете (более или менее), но (насколько я могу судить) вам не хватает a) разделения контрактов и типов реализации на их собственные проекты / сборки и b) концепции для настройки DI-контейнер, т. Е. Настроить, какие реализации должны использоваться для интерфейсов.

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

а. Всегда должны проекты: MyNamespace.Something а также MyNamespace.Something.Contracts

В целом, для DI у меня есть две сборки: одна для контрактов, которая содержит только интерфейсы, и одна для реализации этих интерфейсов. В вашем случае у меня, вероятно, было бы пять сборок: Presentation.dll, Services.dll, Services.Contracts.dll, DataAccess.dll а также DataAccess.Contracts.dll,

(Другой допустимый вариант - поместить все контракты в одну сборку, назовем это Commons.dll)

Очевидно, что DataAccess.dll Рекомендации DataAccess.Contracts.dll как классы внутри DataAccess.dll реализовать интерфейсы внутри DataAccess.Contracts.dll, То же самое для Services.dll а также Services.Contracts.dll,

Нет, развязывающая часть: Presentation Рекомендации Services.Contracts а также Data.Contracts, Услуги ссылки Data.Contracts, Как видите, нет зависимости от конкретных реализаций. Это то, о чем все дело в DI. Если вы решили обменять свой слой доступа к данным, вы можете поменять местами DataAccess.dll в то время как DataAccess.Contracts.dll остается такой же. Ни одна из ваших сборок не ссылается на DataAccess.dll напрямую, поэтому нет битых ссылок, конфликтов версий и т. Д. Если это не ясно, попробуйте нарисовать небольшую диаграмму зависимостей. Вы увидите, что нет стрелок, указывающих на какие-либо сборки, которые не имеют .Contracts во имя

Это имеет смысл для вас? Пожалуйста, спросите, если что-то неясно.

б. Выберите, как настроить контейнер

Вы можете выбрать между явной конфигурацией (XML и т. Д.), Конфигурацией на основе атрибутов и регистрацией на основе соглашений. В то время как первое - боль по очевидным причинам, я - поклонник второго. Я думаю, что он более удобен для чтения и отладки, чем обычные конфигурации, но это дело вкуса.

Разумеется, контейнерный вид объединяет все зависимости, которые вы пощадили в своей архитектуре приложения. Чтобы прояснить, что я имею в виду, рассмотрим конфигурацию XML для вашего случая: она будет содержать "ссылки" на все сборки реализации DataAccess.dll, ..., Тем не менее, это не подрывает идею разделения. Понятно, что вам нужно изменить конфигурацию при замене сборки реализации.

Однако, работая с конфигами на основе атрибутов или соглашений, вы обычно работаете с механизмами автообнаружения, о которых вы упомянули: "Поиск во всех сборках, расположенных в xyz". Для этого необходимо поместить все сборки в каталог bin приложений. В этом нет ничего плохого, так как код должен быть где-то, верно?

Что вы получаете? Предположим, вы развернули свое приложение и решили поменять слой DataAccess. Скажем, вы выбрали соглашение на основе конфигурации вашего DI контейнера. Что вы можете сделать сейчас, это открыть новый проект в VS, ссылаясь на существующие DataAccess.Contracts.dll и реализуйте все интерфейсы любым удобным для вас способом, если будете следовать соглашениям. Затем вы создаете библиотеку, назовите ее DataAccess.dll и скопируйте и вставьте его в папку программы вашего исходного приложения, заменив старый DataAccess.dll, Готово, вы поменяли всю реализацию так, что другие сборки даже не заметили.

Я думаю, вы поняли идею. Это действительно компромисс с использованием IoC и DI. Я настоятельно рекомендую быть прагматичным в ваших дизайнерских решениях. Не связывайтесь со всем, это просто становится грязным. Решите сами, где DI и IoC действительно имеют смысл и не слишком подвержены влиянию религиозных дискуссий в сообществе. Тем не менее, при правильном использовании IoC и DI действительно очень мощные!

Другие вопросы по тегам