#dependency-injection #controller #asp.net-mvc-5 #repository-pattern #mef2
#внедрение зависимостей #контроллер #asp.net-mvc-5 #репозиторий-шаблон #mef2
Вопрос:
Я начинаю новый проект MVC и (почти) решил использовать шаблон репозитория и внедрение зависимостей. Потребовалось некоторое время, чтобы просмотреть варианты, но я придумал следующую структуру для своего приложения:
- Уровень представления: ASP.Net Интерфейс MVC (представления / контроллеры и т.д.)
- Уровень служб (бизнес-уровень, если хотите): интерфейсы и DTO.
- Уровень данных: реализации интерфейса и классы Entity Framework.
В моем решении это 3 отдельных проекта. Уровень представления имеет только ссылку на уровень сервисов. Уровень данных также имеет только ссылку на уровень сервисов — так что это в основном соответствует дизайну, управляемому доменом.
Смысл структурирования вещей таким образом заключается в разделении проблем, слабой связи и тестируемости. Я буду рад получить совет по улучшению, если что-то из этого неразумно?
Часть, с которой я сталкиваюсь, — это внедрение объекта, реализующего интерфейс, из уровня данных в уровень представления, который знает только об интерфейсах на уровне сервисов. Похоже, это именно то, для чего нужен DI, а фреймворки IoC (предположительно!) упрощают это, поэтому я решил попробовать MEF2. Но из десятков статей, вопросов и ответов, которые я прочитал за последние несколько дней, похоже, ничто не решает эту проблему таким образом, который соответствует моей структуре. Почти все они устарели и / или являются примерами простых консольных приложений, которые имеют все интерфейсы и классы в одной сборке, знают все друг о друге и полностью игнорируют точку свободной связи и DI. Я также видел другие, для которых требуется, чтобы DLL уровня данных помещалась в папку bin уровня представления и настраивала другие классы для просмотра там, что снова мешает идее свободной связи.
Существуют некоторые решения, которые используют регистрацию на основе атрибутов, но, предположительно, она была заменена регистрацией на основе соглашений. Я также вижу много примеров внедрения объекта в конструктор контроллера, что создает собственный набор проблем для решения. Я не уверен, что контроллер должен знать об этом на самом деле, и предпочел бы, чтобы объект был введен в модель, но для этого могут быть причины, поскольку многие примеры, похоже, следуют этому пути. Я еще не слишком углубился в это, поскольку я все еще застрял, пытаясь вообще где-либо перенести объект уровня данных на уровень представления.
Я считаю, что одна из моих главных проблем заключается в том, что я не понимаю, на каком уровне должны использоваться различные вещи MEF2, поскольку в каждом примере, который я нашел, используется только один слой. Существуют контейнеры, регистрации и каталоги, а также экспорт и импорт конфигураций, и я не смог точно определить, куда должен идти весь этот код.
Ирония заключается в том, что современные шаблоны проектирования должны абстрагировать сложность и упрощать нашу задачу, но я бы уже наполовину закончил, если бы просто ссылался на DAL из PL и работал над фактической функциональностью приложения. Я был бы очень признателен, если бы кто-нибудь мог сказать: «Да, я понимаю, что вы делаете, но вам не хватает xyz. Что вам нужно сделать, это abc ‘.
Спасибо.
Ответ №1:
Да, я понимаю, что вы делаете (более или менее), но (насколько я могу судить) вам не хватает а) разделения контрактов и типов реализации на их собственные проекты / сборки и б) концепции настройки DI-контейнера, т.Е. Настройки того, какие реализации должны использоваться дляинтерфейсы.
Существует неограниченное количество способов решения этой проблемы, поэтому то, что я даю вам, — это моя личная лучшая практика. Я работаю над этим уже довольно давно и все еще доволен этим, поэтому считаю, что этим стоит поделиться.
a. Всегда должны быть проекты: 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
оставаясь неизменным. Ни одна из ваших других сборок не ссылается на доступ к данным.dll напрямую, поэтому нет неработающих ссылок, конфликтов версий и т. Д. Если это непонятно, попробуйте нарисовать небольшую диаграмму зависимостей. Вы увидите, что нет стрелок, указывающих на какие-либо сборки, которых нет .Contracts
в их названии.
Имеет ли это смысл для вас? Пожалуйста, спросите, если есть что-то неясное.
б. Выберите способ настройки контейнера
Вы можете выбирать между явной конфигурацией (XML и т. Д.), конфигурацией на основе атрибутов и регистрацией на основе соглашений. Хотя первое является проблемой по понятным причинам, я поклонник второго. Я думаю, что он более удобочитаем и прост в отладке, чем конфигурация на основе соглашений, но это дело вкуса.
Конечно, контейнерный тип объединяет все зависимости, которые вы сохранили в своей архитектуре приложения. Чтобы было понятно, что я имею в виду, рассмотрите конфигурацию XML для вашего случая: она будет содержать «ссылки» на все сборки реализации DataAccess.dll, ...
. Тем не менее, это не подрывает идею развязки. Понятно, что вам нужно изменить конфигурацию при обмене сборкой реализации.
Однако, работая с конфигурациями, основанными на атрибутах или соглашениях, вы обычно работаете с упомянутыми вами механизмами автоматического обнаружения: «Поиск во всех сборках, расположенных в xyz». Для этого требуется разместить все сборки в каталоге applications bin. В этом нет ничего плохого, так как код должен быть где-то, верно?
Что вы получаете? Представьте, что вы развернули свое приложение и решили поменять уровень доступа к данным. Допустим, вы выбрали конфигурацию вашего контейнера DI на основе соглашений. Что вы можете сделать сейчас, так это открыть новый проект в VS, сослаться на существующий DataAccess.Contracts.dll
и реализовать все интерфейсы любым удобным вам способом, если вы следуете соглашениям. Затем вы создаете библиотеку, вызываете ее DataAccess.dll
, копируете и вставляете в папку программы вашего исходного приложения, заменяя старую DataAccess.dll
. Готово, вы поменяли всю реализацию, даже не заметив ни одной из других сборок.
Я думаю, вы поняли идею. Это действительно компромисс, использующий IoC и DI. Я настоятельно рекомендую быть прагматичным в ваших проектных решениях. Не подключайте все, это просто становится беспорядочным. Решите сами, где DI и IoC действительно имеют смысл, и не поддавайтесь слишком влиянию религиозных дискуссий сообщества. Тем не менее, при разумном использовании IoC и DI действительно, действительно, действительно мощные!
Комментарии:
1. Спасибо за ваш отзыв, Марк. Ваш первый пункт — это то, что я действительно рассмотрел, хотя и немного по-другому для вас. Мои интерфейсы (контракты) находятся в BL, отдельно от реализации в DAL. Единственные зависимости связаны с интерфейсами (контрактами). Однако ваш второй пункт совершенно верен — мне не хватает четкого представления о том, как настроить контейнер DI. Зная, что мне нужно поместить data.dll в корзине приложений мне есть на что опереться. Трудно отфильтровать устаревшие методы (например, фабрики контроллеров) и найти четкие примеры того, что нужно делать. Я буду упорствовать!
Ответ №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);