Различия в шаблонах MVC
Мне просто нужно несколько ссылок на статьи, которые я могу прочитать, или некоторые основные объяснения различных шаблонов, используемых в MVC (С#).
В настоящее время я стараюсь создавать свои веб-приложения с использованием шаблона модели представления. Для каждого представления у меня есть одна модель представления. Мне нравится этот подход исключительно потому, что может быть так много мусора, что не требуется от модели, и я могу использовать некоторые основные аннотации данных здесь.
Я также теперь создаю свои модели просмотра в самой модели представления (неуверенный, если это правильно?), чтобы я мог максимально упростить мои контроллеры.
Есть моменты, однако я обнаружил, что добавляю много логики в своем контроллере, я бы предположил, что это тоже хорошо, что и для меня, для чего нужен контроллер.
Теперь, основываясь на вышесказанном, как я уже сказал, я могу с радостью создавать свои приложения без каких-либо серьезных проблем. Однако, выполняя обычный просмотр примеров кода и т.д., Я часто нахожу, что существует так много других способов использования разными разработчиками, чтобы делать то, что я делаю выше, и хотел бы, чтобы они объяснили, что все они подходят друг другу.
Я часто упоминаю, что "используйте ваш репозиторий для выполнения бла-бла". Я иногда использую репозитории, но это в основном для запросов модели, которые, как я знаю, я буду повторно использовать в будущем, и он всегда включается в бит свалки. Что лучше всего здесь?
Я также вижу упомянутые "интерфейсы" и "сервисные слои". Я полностью потерялся здесь. Большинство примеров для меня, похоже, просто добавляют все больше шагов для достижения той же цели. Как/почему они используются?
Ответы
Ответ 1
Я не могу сказать, что это лучшая практика, но это то, что я использую, и почему, и здесь мы идем:
1. Хранилища.
Они структурированы таким образом:
Существует три основных интерфейса: IRead<>
, IReadCreate<>
и IReadCreateDelete<>
.
interface IRead<T>
{
T FindOne(int id);
IQueryable<T> GetOne(int id);
IQueryable<T> FindAll(Expression<Func<T, bool>> predicate);
}
interface IReadCreate<T> : IRead<T>
{
T Create();
void Create(T entity);
}
interface IReadCreateDelete<T> : IReadCreate<T>
{
void Delete(int id);
void Delete(T entity);
void DeleteWhere(Expression<Func<T, bool>> predicate);
}
Все остальные интерфейсы выглядят следующим образом:
interface ICategoriesRepository : IReadCreate<Category>
{
IQueryable<Category> GetAllActive();
}
И все они обеспечивают дополнительную полезную функциональность в источнике данных, от которого они зависят. Это означает, что я не могу связаться с другими типизированными репозиториями в моем репозитории реализации. Это нужно сделать на Сервисы. (Смотрите ниже.)
Основная цель этого подхода - показать код вызова (из другой сборки, поскольку все мои репозитории, службы и другие контракты определены (как интерфейсы) в отдельном проекте DLL), что он может делать (например, чтение и создание элементов ) и что он не может сделать (например, удаление элементов).
2. Услуги
Услуги и лучший способ реализовать вашу бизнес-логику. Они должны реализовать все ваши логические логические методы. Для достижения такой реализации им потребуется некоторое количество репозиториев, и вот оно Dependency Injector
. Я предпочитаю использовать Ninject, потому что он позволяет мне вводить свойства зависимостей следующим образом:
internal class CategoriesService : ICategoryService
{
public ICategoriesRepository CategoriesRepository { get; set; }
public IWorkstationsRepository WorkstationsRepository { get; set; }
// No constructor injection. I am too lazy for that, so the above properties
// are auto-injected with my custom ninject injection heuristic.
public void ActivateCategory(int categoryId)
{
CategoriesRepository.FindOne(categoryId).IsActive = true;
}
}
Целью сервисов является устранение бизнес-логики от контроллеров и репозиториев.
3. ViewModels
Прохладная вещь, как вы сказали, но причина в том, почему вы строите их в себе - это то, чего я не могу получить. Я использую automapper для этого (с его расширяемыми запросами расширениями), что позволяет мне создавать такие виды:
Скажем, у меня есть представление, которому нужна модель IEnumerable<TicketViewModel>
. Что я делаю:
public class FooController : Controller
{
public IMappingEngine Mapping { get; set; } // Thing from automapper.
public ITicketsRepository TicketsRepository { get; set; }
public ViewResult Tickes()
{
return View(TicketsRepository.GetAllForToday().Project(Mapping)
.To<TicketViewModel>().ToArray();
}
}
Что это. Простой вызов в репозиторий, который вызывает вызовы для базового источника данных (другой шаблон. Я не буду писать об этом, потому что его абстракция необходима только для тестирования.), Которая вызывает вызовы в базу данных (или что бы вы ни реализовали IDataSource<T>
). Automapper автоматически отображает Ticket
в TicketViewModel
и формирует базу данных, которую я извлекаю , только необходимый для столбцов ViewModel, включая кросс-таблицу в одном запросе.
Заключение
Есть много, чтобы сказать больше, но я надеюсь, что это даст вам немного еды для размышлений. Все шаблоны и программы, которые я использую:
- Automapper (mapping);
- Ninject (инъекция зависимостей);
- Репозитории (доступ к данным);
- Источник данных (данные считываются из.. ну.. из источника данных);
- Услуги (интерактивность данных);
- ViewModels (объекты передачи данных);
- Возможно, что-то еще я отредактирую, чтобы добавить.
Ответ 2
Когда я начал читать ваш пост, я думал, что, возможно, то, что вы ищете, - это понимание принципов SOLID. И затем вы заканчиваете, упоминая интерфейсы и уровни обслуживания. Интересно.
Есть много статей, посвященных святому Граалу СОВЕРШЕННО и СУХОЙ (Многие не понимают, что действительно предлагают сторонники DRY). Но общая идея в мире .NET - это НЕ идти к автогенерируемой Page_Load в aspx и начинать печатать все willy nilly до тех пор, пока страница не сделает то, что она должна делать. MVC на помощь.
Вы говорите, что у вас есть модель для каждого вида. Я бы назвал этот звук. Даже если две модели идентичны, они равны, но не одинаковы. Например: NewsItem не является EventItem. Если вы хотите расширить его, это не должно влиять на другое.
Затем вы продолжаете говорить о том, что вы производите свои модели в самой модели представления. Это звучит назад. Но вы говорите, что делаете это, чтобы ваш контроллер был чистым. Хорошо! То, что отсутствует в вашем мышлении, - это услуги.
Что вы хотите сделать, так это переместить весь код, который фактически выполняет какую-либо работу в сервисах. Служба может быть основана на аспекте, или на функции, или почему не является элементом управления. Теперь, глядя на один веб-проект, я вижу: VisitorService, NewsfeedService, CalendarService, CachingService, MainMenuService, HeaderService, FooterService и т.д. До бесконечности.
В этом случае контроллер отвечает только за запрос службы (или служб), которая выполняет некоторую работу, для модели. И затем переместите эту модель к виду.
Как только вы получите "бизнес-логику" в сервисах, вы можете легко применить IoC (Inversion of Control) к вашим проектам, если это сделает вас счастливыми. Я еще не проголосовал за IoC. У меня жуткие преимущества не так велики, как рекламируются, и вы можете обойтись без раздувания кода. Но IoC прошу вас подумать, прежде чем вводить код.
Для очень простого учебника по IoC я рекомендую Ninject. Мало того, что в нем есть ниндзя, но самураи, мечи и сюрикены. Это намного круче, чем автомобили и животные.
https://github.com/ninject/ninject/wiki/Dependency-Injection-By-Hand
Ответ 3
Контроллер:
Теоретически ваш контроллер должен обрабатывать только "данные". Перемещение фрагментов информации из одного места в другое.
Маленький пример:
- Контроллер получает запрос "GetMessage" с некоторым параметром.
- Отсылает эти данные на уровень обслуживания. На уровне сервиса, к которому вы обращаетесь
репозитория, возвращающего сообщение.
- Cntroller получает это сообщение (или если оно не было ни одного) и решает, отправит ли оно shoudl полученное сообщение или, возможно, произошла ошибка, и пользователь должен быть уведомлен каким-то образом.
Вся бизнес-логика "в теории" должна быть за некоторым уровнем обслуживания. Таким образом, вы можете легко проверить все. Логика в контроллере затрудняет некоторые тесты.
Интерфейсы:
Дизайн на основе интерфейса очень популярен. Особенно со всеми контейнерами IOC, занимающимися инъекцией зависимостей. Но если вы начинаете с этой концепции, не беспокойтесь об этих ключевых словах. Если вы знаете шаблон репозитория, попробуйте сначала с интерфейсом IRepository и вместо доступа к репозиторию по конкретному классу, используйте IRepository. (Просто измените поле в контроллере из Repository в IRepository).
Общие сведения об интерфейсах
Вы увидите преимущества интерфейсов в более сложных сценариях, но есть один метод, который покажет вам всю славу этого подхода. Тестирование единиц измерения + Mocking.