Различия в шаблонах 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.