Как быстро стартовать в asp net mvc
Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7
Последнее обновление: 16.11.2019
Чтобы у нас в базе данных уже были начальные данные при запуске приложения, нам нужен некий класс. который бы выполнял роль инициализатора базы данных. Для этого добавим в проект новый класс, который назовем SampleData и который будет иметь следующий код:
using System.Linq; using MobileStore.Models; namespace MobileStore < public static class SampleData < public static void Initialize(MobileContext context) < if (!context.Phones.Any()) < context.Phones.AddRange( new Phone < Name = "iPhone X", Company = "Apple", Price = 600 >, new Phone < Name = "Samsung Galaxy Edge", Company = "Samsung", Price = 550 >, new Phone < Name = "Pixel 3", Company = "Google", Price = 500 >); context.SaveChanges(); > > > >
Данный класс определяет один статический метод Initialize() , в котором происходит добавление трех начальных элементов — объектов Phone. Для добавления объектов в бд в метод Initialize передается контекст данных. И если данные в таблице Phones в бд отсутствуют ( if (!context.Phones.Any()) ), то добавляются три объекта.
Чтобы инициализатор базы данных вызывался при старте приложения, изменим класс Program следующим образом:
using System; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using MobileStore.Models; namespace MobileStore < public class Program < public static void Main(string[] args) < var host = CreateHostBuilder(args).Build(); using (var scope = host.Services.CreateScope()) < var services = scope.ServiceProvider; try < var context = services.GetRequiredService(); SampleData.Initialize(context); > catch (Exception ex) < var logger = services.GetRequiredService>(); logger.LogError(ex, "An error occurred seeding the DB."); > > host.Run(); > public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => < webBuilder.UseStartup(); >); > >
В данном случае получаем контекст из сервисов и вызываем инициализатор. В случае ошибки осуществляем логгирования через соответствующий сервис.
Итак, начальные данные определены, и теперь мы хотим их выводить на веб-страницу, чтобы пользователи видели набор товаров и смогли бы один из них выбрать.
Для этого перейдем к папке Controllers, где находится единственный контроллер HomeController. Изменим его содержимое следующим образом:
using System.Linq; using Microsoft.AspNetCore.Mvc; using MobileStore.Models; namespace MobileStore.Controllers < public class HomeController : Controller < MobileContext db; public HomeController(MobileContext context) < db = context; >public IActionResult Index() < return View(db.Phones.ToList()); >> >
Во-первых, здесь удалены все ненужные нам методы — все кроме метода Index, который будет использоваться для передачи пользователю данных о товарах.
Во-вторых, добавлен конструктор, в котором получаем контекст данных. Здесь применяется встроенный механизм внедрения зависимостей. Так как, в методе ConfigureServices() контекст данных был добавлен в качестве сервиса:
services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
То через конструктор мы можем получить эту зависимость и использовать ее.
Затем вызывается метод View() , который генерирует представление. И в этот метод передаются все объекты из таблицы Phones в базе данных. Для передачи данных нам достаточно использовать такую конструкцию: db.Phones.ToList() .
За построение визуального интерфейса в MVC отвечают представления. И теперь создадим само представление для вывода списка смартфонов. Для представлений в проекте предназначена папка Views. По умолчанию в этой папке уже есть подкаталог для представлений контроллера Home, в котором три представления: About.cshtml, Contact.cshtml и Index.chtml.
Каждый метод контроллера по умоланию использует одноименное представление. То есть метод Index будет использовать представление Index.cshtml. Откроем это представление и изменим его код следующим образом:
@model IEnumerable @ < //ViewData["Title"] = "Список смартфонов"; Layout = null; >Магазин смартфонов Смартфоны
| Модель | Производитель | Цена | |
| @phone.Name | @phone.Company | @phone.Price | Купить |
Представления напоминают html-страницы, так как могут содержать и часто содержат очень много кода html. Но кроме собственно html они также содержат специальные инструкции, которые предваряются символом @. Это инструкции синтаксиса Razor — специального движка представлений, который позволяет использовать вместе с html и код на языке c#. Далее мы подробнее разберем синтаксис движка Razor, а пока достаточно знать, что после символа @ идут выражения на языке C#.
Первая строка устанавливает модель представления — та сущность, которая будет доступна в представлении через объект Model. В данном случае это объект IEnumerable , так как в контроллере в методе Index мы передаем список смартфонов, а список — объект List представляет интерфейс IEnumerable.
Далее идет блок кода, в котором выражение Layout = null указывает, что мастер-страница не будет применяться к этому представлению. Далее мы добавим к нему мастер-страницу и узнаем, зачем она нужна, а пока обойдемся без нее.
Практически весь остальной код представляет собой стандартный код веб-страницы на языке html: создание обычной таблицы, которая выводит информацию о продаваемых смартфонах. Здесь также используется конструкция @foreach (var phone in Model) . То есть тут мы создаем цикл. В нем мы пробегаемся по всем элементам в объекте Model, который представляет переданный в методе Index список смартфонов. И затем получаем значение свойства каждого элемента с помощью синтаксиса Razor: @phone.Name и помещаем его в ячейку таблицы.
В последнюю колонку таблицы для каждого элемента добавляется ссылка Купить . При нажатии на эту ссылку методу Buy контроллера HomeController будет отправляться запрос, в котором вместо @phone.Id будет указан id смартфона. Пока у нас, правда, отсутствует метод Buy, но скоро мы его создадим.
Основы маршрутизации
В методе Configure() класса Startup устанавливаются настройки маршрутизации приложения:
app.UseEndpoints(endpoints => < endpoints.MapControllerRoute( name: "default", pattern: "//"); >);
Здесь добавляется маршрут с именем default . Поле pattern указывает, что запрос к приложению должен иметь двух-трехсегментную структуру. Вначале идет имя контроллера, потом имя метода и потом может идти необязательный параметр id.
То есть чтобы обратиться к контроллеру HomeController или отправить ему запрос, нам надо указать в строке запроса его имя — Home и далее через слеш указать метод контроллера, к которому отправляется запрос, например, Home/Index . По умолчанию при запуске проекта или при обращении к сайту система mvc будет вызывать действие Index контроллера HomeController.
Теперь запустим приложение на выполнение. По умолчанию, как и определено выше маршрутом default, сработает обращение к методу Index контроллера Home, который возвратит пользователю страницу с данными:

А через интерфейс Visual Studio в окне SQL Server Object Explorer мы можем увидеть созданную базу данных:
Как быстро стартовать в asp net mvc?
Посоветуйте материалы для быстрого старта с сим фреймворком.
Владею С#, MS/my SQL, немного знаю html, css, английский.
- Вопрос задан более трёх лет назад
- 20008 просмотров
Комментировать
Решения вопроса 5
Создайте сайт из книги ASP.NET MVC 4/5 Адама Фримана — бесплатно естьтут. Потом на основе этого придумайте свой сайт или усовершенствуйте сайт из примера.
Так же есть краткие и хорошие уроки для новичков от chernikov’a на Хабре.
И больше писать и пробовать решать задачи, придумайте проект или возьмите реальный, и попробуйте реализовать.
Ответ написан более трёх лет назад
Комментировать
Нравится 4 Комментировать
В дополнение к вышесказанному могу добавить следующие источники: онлайн-книги Изучаем ASP.NET MVC 4 и Руководство по ASP.NET MVC 5
Ответ написан более трёх лет назад
Комментировать
Нравится 4 Комментировать
Пройдите официальные туториалы на сайте asp.net/mvc
Ответ написан более трёх лет назад
Комментировать
Нравится 3 Комментировать
Николай Турнавиотов @foxmuldercp
Системный администратор, программист, фотограф
Если нет знаний в шарпе вообще, как у меня год назад — возьмите Троелсена, почитайте.
Шилдта можно.
Потом мануалы на оф сайте mvc.
ну я сейчас изучаю Фреймворк написанием своего варианта домашней веб бухгалтерии, заодно и html, css. jquery, twitter bootstrap
Ответ написан более трёх лет назад
Комментировать
Нравится 1 Комментировать
Гайдар Магдануров asp.net mvc погуглите
Ответ написан более трёх лет назад
Комментировать
Нравится Комментировать
Ответы на вопрос 0
Ваш ответ на вопрос
Войдите, чтобы написать ответ

- Python
- +3 ещё
Как сделать так, чтобы при регистрации пользователя в телеграмм боте его ID сохранялся в файле только один раз?
- 1 подписчик
- 24 минуты назад
- 18 просмотров
Простейший багтрекер на ASP .NET MVC
![]()
Технология ASP .NET уже давно перестала быть неповоротливым монстром и уделом унылых корпоративных приложений. Заряженная MVC фреймворком, платформа ASP .NET превращается в грозное оружие, нацеленное на хайлоад и действительно большие проекты.
Что мне понравилось в ASP .NET MVC
Моя основная работа никогда не была напрямую связана с web-разработкой. Все проекты создавались в свободное время и на технологиях, которые были мне симпатичны. Изначально мой выбор (как и у многих) пал в сторону PHP. Я долго на нем писал код, постоянно закрывая глаза на его странности и проблемы. Мне нравились многие PHP-фреймворки (Kohana, CodeIgniter, FuelPHP и т.д.) и с удовольствием применял их в своих проектах. Однако, несмотря на плюсы и многообразие готовых каркасов мне всегда хотелось переметнуться в другой лагерь и посмотреть, как происходит процесс разработки аналогичных вещей там. Сначала я присматривался к популярному Ruby с его рельсами, но потом все же решил остановиться на .NET. Перечислять плюсы данной платформы можно долго, но наиболее значимыми для меня все же стали:
Если код пахнет откровенной тухлятиной, то компилятор обязательно сообщит об этом и разработчик сможет предпринять необходимые действия. Разрабатывая приложение под ASP .NET я попадаю в ту же самую среду, где в моем распоряжении предсказуемый и строго типизированный язык (C#) с подушкой безопасности в лице компилятора.
Model View Controller
На страницах нашего журнала я уже несколько раз рассматривал Архитектурный паттерн MVC (модель, представление, контроллер). В одном из номеров я даже приводил пример разработки простейшего MVC фреймворка на PHP. Паттерн MVC условно делит архитектуру приложения на три компонента:
Главная цель MVC – обеспечить разделение ответственности между основными компонентами приложения. Контроллер ничего не должен знать о нюансах формирования верстки или хранению данных в БД, а выполнять лишь роль проводника. Пользователь попросил, а контроль нашел правильный путь, не задумываясь о том, что там может произойти.
Багтрекер
Типичный пример при знакомстве с подобными фреймворками – создание еще одного движка для блога. Я сначала хотел пойти тем же путем, но в итоге решил придумать более полезное приложение. Так родилась идея проверить фреймворка на создании проекта «Багтрекер». Такие приложения наиболее востребованы в компаниях, где более-менее налажен процесс разработки и совсем скоро ты убедишься, что сотворить нечто подобное на ASP .NET MVC пуще простого.
Теперь давай определимся с функционалом будущего проекта. Будущий багтрекер должен:
![]()
С первыми двумя пунктами все ясно – немного кода на C# и все готово, но как быть с интерфейсом? Мы воспользуемся фреймворком TwitterBootstrap, который позволит нам состряпать симпатичный интерфейс для приложения за несколько минут. Я не стану приводить портянку из пары десятков килобайт HTML кода, а просто дам ссылку на готовую заготовку http://goo.gl/Xvhsmh. Качай и повторяй действия вместе со мной.
Делаем проект
У меня нет профессиональной версии студии, поэтому я воспользовался экспресс версией редакции «Для web». Запускай студию и создавай новый проект «Веб-приложение ASP .NET 4». С гордым названием BugTrackerForX.
В окне мастера создания нового проекта тебе будет предложено выбрать шаблон для приложения. Шаблоны позволяют сразу же снабдить будущее творение определенным функционалом. Например, выбрав шаблон «Интернет-приложение», ты получишь заготовку с готовой регистрационной формой и механизмом аутентификации.
![]()
Для своего проекта мы выберем вариант «Простой». Такой прожект не будет включать в себя ничего лишнего и это будет в самый раз для первого знакомства с миром ASP .NET MVC. В окне выбора шаблона для приложения также обрати внимание на пункт «View Engine». Здесь выбирается шаблонизатор для представлений. Тебе доступно два варианта: Razor и ASPX.
Во второй версии фреймворка MVC в качестве шаблонизатора использовался ASPX, пришедший из классического ASP .NET. После PHP’ного многообразия движков для рендеринга представлений начинаешь испытывать приступы тошноты от его неуклюжести. Лучше сразу выбирать Razor, максимально приближенный к аналогичным PHP-решениям (Smarty, Twig и т.д.).
Больше никаких галок ставить не нужно, жми «OK» и студия сгенерирует болванку приложения.
Структура MVC приложения
Открой окно «Solution Explorer» по шире и мотай теорию на ус.
![]()
Выжигаем модели
Начинать разработку приложения будем с проектирования моделей. Для создания баг трекера нам потребуются описать несколько моделей:
Каждая модель в ASP .NET MVC фреймворке описывается в виде отдельного класса в папке Model (можно в любом месте). Добавление новые моделей выполняется в контекстном меню с помощью пункта «Add» -> «Class». Создай все выше озвученные модели (см. соответствующие листинги) и возвращайся к тексту статьи.
Листинг 1. Описание модели Category
public class Category < Public int CategoryId < get; set; >public string Title < get; set; >>
Листинг 2. Описание модели Status
public class StatusModel < public int StatusId < get; set; >public string Title < get; set; >>
Листинг 3. Описание модели Ticket
public class Ticket < public int TicketId < get; set; >public string Title < get; set; >public string Description < get; set; >public DateTime Date < get; set; >public int? CategoryId < get; set; >public virtual Category Category < get; set; >public int? StatusId < get; set; >public virtual Status Status < get; set; >public int? UserId < get; set; >public virtual User User
Листинг 4. Описание модели User
public class User < public int UserId < get; set; >public string FirstName < get; set; >public string LastName < get; set; >public string Email < get; set; >>
Подключаем Entity Framework
Платформа .NET предоставляет нам несколько способов доступа к данным. Мы воспользуемся наиболее актуальным из них – Entity Framework. Помимо типичных задач, возлагающих на ORM (Object relation mapping) фреймворк, в EF реализована поддержка методики CodeFirst, позволяющая девелоперу на этапе разработки не задумываться о дизайне схемы БД. Достаточно лишь описать модели, а вопросы создания базы и таблиц на себя возьмет сам фреймворк. Нельзя сказать, что такая схема будет готова к использованию в продакшене, однако, при разработке приложения с нуля такой подход позволит существенно сэкономить время.
Чтобы воспользоваться плюсами этого фреймворка, нам необходимо подключить его к своему проекту. Проще всего это сделать при помощи консоли управления пакетами NuGet. Сразу после инсталяции Visual Studio for Web расширение Nuget Package Manager недоступно. Его требуется установить самостоятельно, воспользовавшись пунктом «Extensions and Update» в меню «Service». После установки запускай «Nuget Package Manager Console» и вводи в ней команду для установки Entity Framework: «Install-Package EntityFramework».
Установив Entity Framework, мы можем создать контекст данных для наших моделей и начать производить первые манипуляции с добавленными в БД записями. Под страшным словом «контекст» подразумевается создание класса наследника от DbContext (System.Data.Entity), который свяжет модели с таблицами базы данных. Создавай новый класс для контекста данных в директории «Models» и переписывай в него код листинга 5.
![]()
Листинг 5. Реализация класса контекста
public class BugTrackerContext : DbContext < public DbSetCategories < get; set; >public DbSet Statuses < get; set; >public DbSet Tickets < get; set; >public DbSet Users < get; set; >>
После создания контекста мы можем воспользоваться технологией CodeFirst, т.е. доверить EntityFramework создание таблиц и полей. В своем примере я воспользуюсь именно этим способом, т.к. статья не резиновая и тратить время на описание процесса создания схемы БД нет смысла. К тому же ничего не мешает тебе самостоятельно создать БД (ради эксперимента) и познакомиться с подходом DatabaseFirst (сначала база, потом код).
В принципе, уже на данном этапе мы можем создать контроллер с представлениями, и EF любезно сгенерирует базу данных. Однако, мы лучше сразу внесем небольшой твик в конфигурационный файл (Web.config), тем самым принудительно зададим имя базы. Найди в этом файле описание секции «ConnectionStrings» и удали из нее (если есть) все разделы. После добавь одну строку:
В этой строке я определил, что фреймворк должен использовать BugTrackerContext для связи с БД, а саму базу будет хранить в файле ourBase.mdf, расположенный в директории данных (App_Data) проекта.
CRUD приложение в пару кликов
Фактически наше приложение научилось работать с базой данных, но в реале мы этого еще не увидели т.к. ни разу не запускали наш баг трекер. Надо срочно исправлять положение дел, но у нас еще нет ни одного контроллера и представлений. Следовательно, запустив проект сейчас, хорошего мы ничего не увидим. Я предлагаю пока не заморачиваться на верстке представления с подготовленным нами красивым интерфейсом. Лучше быстренько протестируем то, что есть.
Создадим для этого простейший CRUD (Create read update delete) функционал без единой строчки кода. Переходи в «Solution Explorer» и добавь в папку «Controllers» новый контроллер с именем «CrudController». В окне создания нового контроллера не торопись кликать на пимпу с кнопкой «OK». В выпадающем поле «Template» выбери «MVC controller with read/write actions and views, using Entity Framework». После этого станут доступны поля для выбора классов, описывающих модель и контекст данных. Выбирай Ticket (в качестве модели) и BugTrackerContext (в качестве контекста). Остальные поля можешь оставить со значениями по умолчанию.
![]()
Нажимай на пимпу «Ok» и восхищайся, как студия создала за нас код действий с необходимыми представлениями. Нам остается только запустить приложение и проверить результат его выполнения. Сразу предупреждаю, после запуска ты увидишь не результат работы CRUD, а ошибку «Ресурс не найден». Мы не прописали маршрут по умолчанию, поэтому чтобы добраться до сгенерированного контроллера тебе потребуется вбить в адресной строке полный путь к нему: http://localhost:53532/crud. Альтернативным решением будет внесение изменений в конфигурацию маршрута (файл RouteConfig.cs):
defaults: new
В этой строке я устанавливаю, что контроллером по умолчанию является crud (а не Home, как было изначально). Внеся изменения в маршрут – перезапусти приложение, и ты сразу попадешь в сгенерированный CRUD интерфейс.
Во время создания тикета ты увидишь, что фреймворк сгенерировал нам выпадающие списки для полей «Category», «Status» и «User», но в этих списках нет ни одного элемента для выбора. Чтобы в них что-то появилось нам нужно добавить данные в соответствующие таблицы. Прерви выполнение аппликации и через окно «DatabaseExplorer» открой соответствующие таблицы для внесения данных. Я создал пару статусов в таблице «Statuses» и парочку разделов в «Categories», после этого выпадающие поля в представлении заполнились добавленными данными.
CRUD-приложение получилось вполне рабочим, но для постоянного использования оно не годится. Уж больно смотрится «топорно» и не функционально. У нас уже есть забутстрапенная заготовка верстки, и теперь остается ее натянуть на существующее приложение, а заодно познакомиться с самостоятельным созданием контроллеров и представлений.
Добавляй новый пустой контроллер к своему проекту и назови его «HomeController». Visual Studio сгенерирует каркас будущего контроллера и создаст действие «Index». Это действие будет выполняться во время обращение к имени контроллера. Например, чтобы обратится к нашему контроллеру «Home», нам требуется перейти по адресу: http://localhost/home.
Пока ты переписываешь код контроллера, я расскажу тебе о реализации контроллеров в ASP .NET MVC. Как ты уже понял из листинга, контроллер – это не что иное, как обычный класс, унаследованный от System.Web.Mvc.Controller. При создании нового контроллера ты должен следовать некоторым соглашениям. Главным, из которых будет обязательное наличие постфикса «Controller». Например, если ты хочешь создать контроллер «Adminka», то в этом случае полное имя должно быть «AdminkaController».
![]()
Чтобы обратится к созданному контроллеру (из браузера) необходимо написать в адресной строке полное имя контроллера и через слэш имя действия. Если требуется передать в контроллер какие-нибудь дополнительные параметры (например, методом Get), то их также следует указывать через слеш. Такой подход справедлив для маршрута, определенного по умолчанию. Например, для передачи параметра «1» контроллеру «adminka» следует пройти по пути: «http://localhost/adminka/1». Обрати внимание, что в браузере указать постфикс «Controller» не требуется.
Листинг 6. Код контроллера
private BugTrackerContext db = new BugTrackerContext(); public ActionResult Index() < var tickets = db.Tickets.Include(p =>p.Category).Include(p => p.Status).Include(p => p.User); return View(tickets.ToList()); >
У тебя может возникнуть резонный вопрос: «Возможно ли, как-то повлиять на установленные правила роутинга?». Шаблон маршрута определен в классе RouteConfig и ты волен им рулить как хочешь. Например: » ru// » заставит добавлять к имени контроллера префикс «ru».
Хочется еще рассказать про маршруты, но, увы, объем статьи ограничен. Пора переходить к расмсотрению небольшого примера кода, демонстрирующего обработку внешних параметров в контроллере. Думаю, комментарии излишни:
int myParam1 = Int32.Parse(Request.Params["myParam"]);
Дизайним представление
Контроллер успешно принимает пользовательские запросы, и теперь пора сотворить для него представление. Добавить для контроллера новое представление. Проще всего это сделать посредством пункта «Add View» контекстного меню, вызываемого при правом клике по имени действия контроллера.
![]()
В ASP .NET MVC принято выделять несколько типов представлений:
Ok, теперь попробуем создать наше первое представление. Поскольку в нашем приложении будет несколько повторяющихся элементов (например, общая разметка, сайдбар и т.д.). Мы можем пихать этот код в каждую вьюшку, но выгодней создать одну мастер страницу, которая впоследствии будет использоваться при формировании других представлений.
Мастер страницы принято помещать в директорию «Shared». Для своего примера я определил одно представление и назвал его _bugTrackerMasterPage.cshtml (содержит основную часть верстки). Приводить код вьюшки я не буду причине ее объема, а лучше сразу рассмотрю используемые управляющие конструкции:
Разобравшись с используемыми управляющими конструкциями, становится ясно, что весь код представлений, созданный на основе мастер-страницы будет включен на место вызова RenderBody().
Чтобы остальные представления знали на основе какой мастер-страницы им рендерится мы должны указать на нее ссылку:
Во время разбора кода мастер-страницы ты наверняка обратил внимание на символ «собачка», расположенный возле каждой управляющей конструкции. Все что идет после этого символа воспринимается как код на языке C#. Если требуется выполнить несколько строк управляемого кода, то для этого надо воспользоваться открывающими и закрывающими скобками.
Теперь посмотрим на код представления Index. В вьюшке формируется список имеющихся в базе тикетов. Выборка самих данных происходит в контроллере и список с ними передается в представление:
BugTrackerContext db = new BugTrackerContext(); var tickets = db.Tickets.Include(p => p.Category).Include(p => p.Status).Include(p => p.User); return View(tickets.ToList());
Запрос к таблицам БД выполняется не напрямую, а через Entity Framework. Поскольку модель «Ticket» содержит ссылки на другие модели, то мы должны включить их в запрос (метод Include()). Сформированная выборка передается в качестве параметра соответствующему представлению.
Вывод данных из модели в самом представлении осуществляется в стандартном цикле:
@foreach (var c in Model)
Здесь я перебираю записи из Model, которую предварительно объявили в самом начале файла представления:
@model IEnumerable.
На этом код первого index() можно считать разобранным, самое время осуществить тестовый запуск проекта и посмотреть результат. Полный список тикетов выводится и теперь остается лишь добавить код добавления новых записей, ну это будет твоим домашним заданием.
Dispose
Разрабатывать WEB-приложения под платформу ASP.NET благодаря фреймворку ASP .NET стало значительно проще. Рассмотренный в статье пример лишнее тому подтверждение. Я не хочу сказать, что ASP .NET – это золотой костыль или серебряная пуля web-строителя. Это очередная технология, которую нужно уметь применять и получать выгоду. Мир на PHP, пусть даже с его многообразием самых разных фреймворков однозначно не заканчивается. Не стесняйся пробовать альтернативные технологии, не засиживайся в одной, пусть даже самой комфортной среде. Сравнивай технологии и выбирай из них наиболее оптимальную для конкретной задачи. На этом у меня все.
Стоит ли изучать классический ASP .NET?
Простота MVC фреймворка для ASP .NET может запросто создать ошибочное мнение. Мол, на фиг этот «неповоротливый» ASP .NET с его «странной» разметкой, мне хватит и ASP .NET MVC. Не стоит забывать, что MVC это всего лишь архитектурный паттерн и его можно реализовать самостоятельно на любом языке. Тут ситуация аналогична миру PHP. С момента появления mvc-фреймворков язык получил второе дыхание и сумел привлечь новых поклонников. Почему? Порог вхождения и в без того простой язык программирования снизился. Многие вещи стали доступны из коробки.
В мире ASP .NET произошла аналогичная ситуация. MVC фреймворк от Microsoft существенно понизил барьер вхождения в технологию ASP .NET,. Правда это не говорит, что без фреймворка платформа никуда не годится. Просто запомни, что фреймворк это лишь очередной слой, скрывающий от твоего взора ряд нюансов классического ASP .NET. Имея достаточный уровень подготовки, ты без проблем можешь сделать свою реализацию MVC фреймворк под платформу ASP .NET. Он тоже сможет в три пятнадцать делать красивые урлы и осуществлять контроль на генерируемой разметкой. К чему это я все говорю? А к тому, что технология ASP .NET намного шире, чем просто ASP .NET MVC. Ее глубокому изучения однозначно стоит уделить время и только тогда ты сможешь по-настоящему прочувствовать всю платформы от Microsoft, которая так полюбилась в корпоративном сегменте.
Топ 5 мифов об ASP .NET/ASP .NET MVC
— «А под unix-like это не заведется!». Еще как заведется. Достаточно немного почитать о проекте Mono (http://goo.gl/dS3v), а потом статью «Установка ASP .NET на Linux (nginx+mono+xsp) с Хабра (http://goo.gl/rPJbX) и миф сразу развеется в дымке;
— «Это же жутко дорого, надо много $$$». Опять неправда. Можно воспользоваться Express версии Visual Studio for Web (именно ее я использовал при написании статьи) или какой-нибудь альтернативной средой (все верно, здесь сплошная демократия). Остальные компоненты (например, SQL Server) также доступны в бесплатных вариантах. Кроме того, Microsoft регулярно проводит всяческие акции/скидки, позволяющие получить полноценный версии продуктов либо совсем бесплатно, либо с существенной скидкой.
— «Microsoft делает дырявый софт». Ну да, и это не мешает им богатеть и завоевывать новые рынки. Если серьезно, продукты Microsoft в большинстве случаев намного безопасней, чем OpenSource альтернативы. Проверить это не трудно. Заходим на баг трекер и смотрим количество ошибок под IIS (как пример) и под Apache. Результаты приятно удивят.
— «PHP приложения работают быстрей». Точно, работают. Не забывай, что код под ASP .NET будет сначала компилироваться, а только потом исполнятся. Причем именно выполнятся, а не стремиться умереть как аналогичный на PHP. Поэтому PHP если немного и побеждает, то только на фальш старте. Производительность ASP .NET хорошо показывает себя на больших проектах.
— «На PHP программировать проще». Порог вхождения действительно ниже, но не настолько как об этом твердят на каждом шагу. Время, вложенное в изучение полностью объектно-ориентированный C# окупиться на первом же более сложном проекте.
Навигация

В предыдущих статьях была построена основная инфраструктура приложения GameStore. Теперь мы воспользуемся этой инфраструктурой для добавления функциональных средств к приложению и увидим первые результаты проделанной работы. И начнем мы с добавления навигационных элементов управления — приложение GameStore станет намного удобнее, если будет предоставлять пользователям возможность навигации по товарам с учетом категории. Эта работа состоит из трех частей:
- Расширение метода действия List() в классе GameController, чтобы он получил возможность фильтрации объектов Game в хранилище.
- Расширение схемы URL и пересмотр стратегии маршрутизации.
- Создание списка категорий, размещенного в боковой панели сайта, который будет подсвечивать текущую категорию, а также поддерживать ссылки на другие категории.
Фильтрация списка товаров
Мы начнем с расширения класса модели представления GamesListViewModel, который был добавлен к проекту GameStore.WebUI. Нам нужно обеспечить взаимодействие текущей категории с представлением, чтобы визуализировать боковую панель, и это хорошее место для старта. В примере ниже показаны изменения, внесенные в файл GamesListView.cs:
using System.Collections.Generic; using GameStore.Domain.Entities; namespace GameStore.WebUI.Models < public class GamesListViewModel < public IEnumerableGames < get; set; >public PagingInfo PagingInfo < get; set; >public string CurrentCategory < get; set; >> >
В класс GamesListViewModel добавлено свойство по имени CurrentCategory. Следующий шаг заключается в обновлении класса GameController, чтобы метод действия List() фильтровал объекты Game по категории и использовал только что добавленное в модель представления свойство для указания категории, выбранной в текущий момент. Соответствующие изменения приведены в примере ниже:
// . namespace GameStore.WebUI.Controllers < public class GameController : Controller < // . public ViewResult List(string category, int page = 1) < GamesListViewModel model = new GamesListViewModel < Games = repository.Games .Where(p =>category == null || p.Category == category) .OrderBy(game => game.GameId) .Skip((page - 1)*pageSize) .Take(pageSize), PagingInfo = new PagingInfo < CurrentPage = page, ItemsPerPage = pageSize, TotalItems = repository.Games.Count() >, CurrentCategory = category >; return View(model); > > >
В этот метод действия внесены три изменения. Первое — добавлен новый параметр по имени category. Он используется вторым изменением, которое представляет собой расширение запроса LINQ. Если значение category не равно null, значит, выбраны только те объекты Game, которые соответствуют значению свойства Category. Последнее, третье, изменение касается установки значения свойства CurrentCategory, которое было добавлено в класс GamesListViewModel. Однако в результате этих изменений значение PagingInfo.TotalItems будет вычисляться некорректно. Со временем мы все исправим.
Модульное тестирование: обновление существующих модульных тестов
Мы изменили сигнатуру метода действия List(), поэтому некоторые существующие методы модульного тестирования перестали компилироваться. Для решения этой проблемы в модульных тестах, которые работают с контроллером, методу действия List() необходимо передавать в первом параметре значение null. Например, в тестовом методе Can_Paginate() раздел действия должен выглядеть следующим образом:
[TestMethod] public void Can_Paginate() < // . // Действие (act) GamesListViewModel result = (GamesListViewModel)controller.List(null, 2).Model; // . >
За счет использования null мы получаем все объекты Game, которые контроллер извлекает из хранилища, что воспроизводит ситуацию, существовавшую перед добавлением нового параметра. Такого же рода изменение понадобится внести и в тестовый метод Can_Send_Pagination_View_Model():
[TestMethod] public void Can_Send_Pagination_View_Model() < // . // Act GamesListViewModel result = (GamesListViewModel)controller.List(null, 2).Model; // . >
Когда вы примете образ мышления, ориентированный на тестирование, обеспечение синхронизации модульных тестов с внесением изменений в код очень быстро станет вашей второй натурой.
Даже при таких небольших изменениях результат фильтрации категорий хорошо заметен. Запустите приложение и выберите категорию с помощью показанной ниже строки запроса, заменив номер порта тем, который был назначен проекту средой Visual Studio:
http://localhost:53985/?category=Симулятор
Вы увидите только товары из категории «Симулятор»:

Очевидно, что пользователи не должны переходить по категориям с применением URL, но здесь показано, что совсем незначительные изменения в приложении MVC Framework могут оказывать существенное влияние, если базовая структура на месте.
Модульное тестирование: фильтрация по категории
Нам необходим модульный тест для проверки функциональности фильтрации по категории, чтобы удостовериться в том, что фильтр может корректно генерировать сведения о товарах указанной категории. Тестовый метод выглядит следующим образом:
[TestMethod] public void Can_Filter_Games() < // Организация (arrange) Mockmock = new Mock(); mock.Setup(m => m.Games).Returns(new List < new Game < GameId = 1, Name = "Игра1", Category="Cat1">, new Game < GameId = 2, Name = "Игра2", Category="Cat2">, new Game < GameId = 3, Name = "Игра3", Category="Cat1">, new Game < GameId = 4, Name = "Игра4", Category="Cat2">, new Game < GameId = 5, Name = "Игра5", Category="Cat3">>); GameController controller = new GameController(mock.Object); controller.pageSize = 3; // Action List result = ((GamesListViewModel)controller.List("Cat2", 1).Model) .Games.ToList(); // Assert Assert.AreEqual(result.Count(), 2); Assert.IsTrue(result[0].Name == "Игра2" && result[0].Category == "Cat2"); Assert.IsTrue(result[1].Name == "Игра4" && result[1].Category == "Cat2"); >
Этот тест создает имитированное хранилище, содержащее объекты Game, которые относятся к определенному диапазону категорий. С использованием метода действия List() запрашивается одна специфическая категория, а результаты проверяются на предмет содержания корректных объектов в правильном порядке.
Улучшение схемы URL
Мало кто желает видеть или пользоваться неуклюжими URL вроде «/?category=Симулятор». Для решения этой проблемы мы намерены пересмотреть схему маршрутизации, чтобы создать подход к URL, который в большей степени удовлетворяет как нашим потребностям, так и потребностям конечных пользователей. Для реализации новой схемы модифицируйте метод RegisterRoutes() в файле App_Start/RouteConfig.cs, как показано в примере ниже:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace GameStore.WebUI < public class RouteConfig < public static void RegisterRoutes(RouteCollection routes) < routes.IgnoreRoute(".axd/"); routes.MapRoute(null, "", new < controller = "Game", action = "List", category = (string)null, page = 1 >); routes.MapRoute( name: null, url: "Page", defaults: new < controller = "Game", action = "List", category = (string)null >, constraints: new < page = @"\d+" >); routes.MapRoute(null, "", new < controller = "Game", action = "List", page = 1 >); routes.MapRoute(null, "/Page", new < controller = "Game", action = "List" >, new < page = @"\d+" >); routes.MapRoute(null, "/"); > > >
Новые маршруты важно добавлять в указанном порядке. Маршруты применяются в порядке, в котором они определены, поэтому изменение порядка может привести к нежелательным эффектам.
В таблице ниже описана схема URL, которую представляют эти маршруты.
Выводит первую страницу списка товаров всех категорий
Выводит указанную страницу (в этом случае страницу 2), отображая товары всех категорий
Отображает первую страницу элементов указанной категории (в этом случае игры в разделе «Симуляторы»)
Отображает заданную страницу (в этом случае страницу 2) элементов указанной категории (Симулятор)
Система маршрутизации ASP.NET применяется инфраструктурой MVC для обработки входящих запросов от пользователей, а также генерирует исходящие URL, которые соответствуют схеме URL и поэтому могут быть встроены в веб-страницы. Использование системы маршрутизации для обработки входящих запросов и генерации исходящих URL позволяет гарантировать согласованность всех URL в приложении.
Метод Url.Action() — это наиболее удобный способ генерации исходящих ссылок. Этот метод применялся в представлении List для отображения ссылок на страницы. Теперь, когда добавлена поддержка фильтрации по категориям, этому методу необходимо передать соответствующую информацию, как показано в примере ниже:
@using GameStore.WebUI.Models @using GameStore.WebUI.HtmlHelpers @model GamesListViewModel @ < ViewBag.Title = "Товары"; >@foreach (var p in @Model.Games) < @Html.Partial("GameSummary", p) > @Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new < page = x, category = Model.CurrentCategory >))
До внесения этого изменения генерируемые ссылки на страницы имели следующий вид:
http://:/Page1
Если пользователь щелкнет на страничной ссылке вроде этой, примененный ранее фильтр по категории теряется, и будет выведена страница, содержащая товары всех категорий. За счет добавления текущей категории, получаемой из модели представления, генерируются URL такого вида:
http://:/Шутер/Page1
Когда пользователь щелкает на подобной ссылке, текущая категория передается методу действия List() и фильтрация сохраняется. После этого изменения, при посещении URL вида «/Шутер» или «/Симулятор» страничные ссылки в нижней части будут корректно включать категорию.
Построение меню навигации по категориям
Нам необходимо предоставить пользователям возможность выбора категории, не предусматривающую ввод чего-либо в URL. Это означает, что мы должны показать список доступных категорий с отмеченной текущей категорией, если она есть. После построения приложения этот список категорий будет применяться в более чем одном контроллере, поэтому он должен быть самодостаточным и многократно используемым.
Инфраструктура ASP.NET MVC Framework поддерживает концепцию дочерних действий, которые идеально подходят для создания компонентов, таких как многократно используемый навигационный элемент управления. Дочернее действие полагается на вспомогательный метод HTML по имени Html.Action(), который позволяет включать в текущее представление вывод из произвольного метода действия.
В этом случае мы можем создать новый контроллер (под названием NavController) с методом действия (в данном случае Menu()), который визуализирует навигационное меню. Затем посредством вспомогательного метода Html.Action() вывод из этого метода встраивается в компоновку.
Такой подход позволяет получить реальный контроллер, который может содержать любую необходимую прикладную логику и который можно подвергать модульному тестированию подобно любому другому контроллеру. Это действительно удобный способ построения небольших сегментов приложения при сохранении общего подхода, принятого в MVC Framework.
Создание контроллера навигации
Щелкните правой кнопкой мыши на папке Controllers в проекте GameStore.WebUI и выберите в контекстном меню пункт Add —> Controller (Добавить —> Контроллер). Укажите вариант MVC 5 Controller — Empty (Контроллер MVC 5 — Пустой) в диалоговом окне Add Scaffold (Добавление шаблона) и щелкните на кнопке Add. В диалоговом окне Add Controller (Добавление контроллера) введите для имени контроллера NavController и щелкните на кнопке Add, чтобы создать файл класса NavController.cs. Удалите метод Index(), который Visual Studio по умолчанию добавляет к новым контроллерам, и добавьте метод действия Menu(), приведенный в примере ниже:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace GameStore.WebUI.Controllers < public class NavController : Controller < public string Menu() < return "Тестируем контроллер Nav"; >> >
Данный метод возвращает статическую строку сообщения, но этого достаточно для начала интеграции дочернего действия в остальную часть приложения. Нам нужно, чтобы список категорий отображался на всех страницах, поэтому мы собираемся визуализировать дочернее действие в компоновке, а не в отдельном представлении.
Отредактируйте файл Views/Shared/_Layout.cshtml, добавив в него вызов вспомогательного метода Html.Action(), как показано в примере ниже:
@ViewBag.Title @Html.Action("Menu", "Nav") @RenderBody()
Текст заполнителя заменен вызовом метода Html.Action(). В качестве параметров этому методу передаются имя метода действия для вызова (Menu()) и содержащий его контроллер (Nav). Запустив приложение, вы увидите, что вывод из метода действия Menu() включен в ответ, отправляемый браузеру:

Генерация списков категорий
Теперь можно вернуться к контроллеру Nav и сгенерировать реальный набор категорий. Мы не хотим генерировать URL категорий в контроллере, а собираемся использовать для этого метод действия в представлении. Все, что планируется сделать в методе действия Menu() — это создать список категорий, как показано в примере ниже:
using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using GameStore.Domain.Abstract; namespace GameStore.WebUI.Controllers < public class NavController : Controller < private IGameRepository repository; public NavController(IGameRepository repo) < repository = repo; >public PartialViewResult Menu() < IEnumerablecategories = repository.Games .Select(game => game.Category) .Distinct() .OrderBy(x => x); return PartialView(categories); > > >
Первое изменение связано с добавлением конструктора, который принимает в качестве своего аргумента реализацию IGameRepository. Результатом является объявление зависимости, которую Ninject будет распознавать при создании экземпляров класса NavController. Второе изменение касается метода действия Menu(), который теперь использует запрос LINQ для получения списка категорий из хранилища и передачи их представлению. Обратите внимание, что поскольку работа в этом контроллере производится с частичным представлением, в методе действия вызывается метод PartialView(), а результатом является объект PartialViewResult.
Модульное тестирование: генерация списка категорий
Модульный тест, предназначенный для проверки возможности генерации списка категорий, относительно прост. Цель заключается в создании списка, который отсортирован в алфавитном порядке и не содержит дубликатов. Для этого проще всего построить тестовые данные, которые имеют дублированные категории и не отсортированы должным образом, передать их в NavController и установить утверждение, что данные будут соответствующим образом очищены.
Тестовый метод выглядит следующим образом:
[TestMethod] public void Can_Create_Categories() < // Организация - создание имитированного хранилища Mockmock = new Mock(); mock.Setup(m => m.Games).Returns(new List < new Game < GameId = 1, Name = "Игра1", Category="Симулятор">, new Game < GameId = 2, Name = "Игра2", Category="Симулятор">, new Game < GameId = 3, Name = "Игра3", Category="Шутер">, new Game < GameId = 4, Name = "Игра4", Category="RPG">, >); // Организация - создание контроллера NavController target = new NavController(mock.Object); // Действие - получение набора категорий List results = ((IEnumerable)target.Menu().Model).ToList(); // Утверждение Assert.AreEqual(results.Count(), 3); Assert.AreEqual(results[0], "RPG"); Assert.AreEqual(results[1], "Симулятор"); Assert.AreEqual(results[2], "Шутер"); >
Внутри теста создается имитированная реализация хранилища, которая содержит повторяющиеся категории и категории, не отсортированные в алфавитном порядке. Затем определяется утверждение о том, что дубликаты будут удалены и алфавитный порядок восстановлен.
Создание представления
Чтобы создать представление для метода действия Menu(), щелкните правой кнопкой мыши на папке Views/Nav и выберите в контекстном меню пункт Add —> MVC 5 View Page (Razor) (Добавить —> Страница представления MVC 5 (Razor)). Укажите в качестве имени Menu и щелкните на кнопке OK для создания файла Menu.cshtml. Удалите содержимое, которое среда Visual Studio добавляет к новым представлениям, и приведите представление в соответствие с кодом:
@model IEnumerable @Html.ActionLink("Домой", "List", "Game", null, new < @class = "btn btn-block btn-default btn-lg" >) @foreach (var link in Model) < @Html.RouteLink(link, new < controller = "Game", action = "List", category = link, page = 1 >, new < @class = "btn btn-block btn-default btn-lg" >) >
Мы добавили ссылку под названием «Домой», которая будет отображаться вверху списка категорий и перемещать пользователя на первую страницу списка всех товаров, не отфильтрованного по категории. Это делается посредством вспомогательного метода ActionLink(), который генерирует HTML-элемент с использованием ранее сконфигурированной информации маршрутизации.
Затем осуществляется проход по именам категорий и создание ссылок для каждой категории с применением метода RouteLink(). Он похож на ActionLink(), но позволяет передавать набор пар «имя/значение», который принимается во внимание во время генерирования URL на основе конфигурации маршрутизации.
Ссылки, сгенерированные по умолчанию, выглядят довольно неуклюжими, поэтому вспомогательным методам ActionLink() и RouteLink() необходимо предоставить объекты, в которых указываются значения для атрибутов создаваемых элементов. В этих объектах определяется атрибут class (снабженный префиксом поскольку class является зарезервированным ключевым словом C#) и применяются классы Bootstrap для стилизации ссылок в виде крупных кнопок.
Запустив приложение, вы увидите ссылки на категории. Если щелкнуть на какой-то категории, список элементов обновится, и будет отображать только элементы выбранной категории:

Подсветка текущей категории
В настоящий момент мы никак не указываем пользователям, какую категорию они просматривают. Иногда пользователям удается выяснить категорию по элементам в списке, однако предпочтительнее предоставить более надежный визуальный отклик.
Это можно было бы сделать за счет создания модели представления, содержащей список категорий и выбранную категорию, и фактически именно это обычно делается. Однако ради разнообразия мы продемонстрируем средство ViewBag. Это средство позволяет передавать данные из контроллера представлению без использования модели представления.
В примере ниже показаны изменения, которые понадобится внести в метод действия Menu() контроллера Nav:
using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using GameStore.Domain.Abstract; namespace GameStore.WebUI.Controllers < public class NavController : Controller < // . public PartialViewResult Menu(string category = null) < ViewBag.SelectedCategory = category; IEnumerablecategories = repository.Games .Select(game => game.Category) .Distinct() .OrderBy(x => x); return PartialView(categories); > > >
В метод действия Menu() добавлен параметр по имени category. Значение для этого параметра будет предоставлено автоматически конфигурацией маршрутизации. Внутри тела метода мы динамически создаем свойство SelectedCategory в объекте ViewBag и устанавливаем его значение равным значению параметра category. ViewBag — это динамический объект, и его новые свойства можно создавать, просто устанавливая для них значения.
Модульное тестирование: сообщение о выбранной категории
Для выполнения проверки того, что метод действия Menu() корректно добавил детали о выбранной категории, в модульном тесте можно прочитать значение свойства ViewBag, которое доступно через класс ViewResult. Ниже показан тестовый метод:
[TestMethod] public void Indicates_Selected_Category() < // Организация - создание имитированного хранилища Mockmock = new Mock(); mock.Setup(m => m.Games).Returns(new Game[] < new Game < GameId = 1, Name = "Игра1", Category="Симулятор">, new Game < GameId = 2, Name = "Игра2", Category="Шутер">>); // Организация - создание контроллера NavController target = new NavController(mock.Object); // Организация - определение выбранной категории string categoryToSelect = "Шутер"; // Действие string result = target.Menu(categoryToSelect).ViewBag.SelectedCategory; // Утверждение Assert.AreEqual(categoryToSelect, result); >
Теперь, когда предоставляется информация о том, какая категория выбрана, можно соответствующим образом обновить представление и добавить класс CSS к HTML-элементу , который воспроизводит выбранную категорию. Изменения в частичном представлении Menu.cshtml показаны в примере ниже:
@model IEnumerable @Html.ActionLink("Домой", "List", "Game", null, new < @class = "btn btn-block btn-default btn-lg" >) @foreach (var link in Model) < @Html.RouteLink(link, new < controller = "Game", action = "List", category = link, page = 1 >, new < @class = "btn btn-block btn-default btn-lg" + (link == ViewBag.SelectedCategory ? " btn-primary" : "") >) >
Изменение выглядит просто. Если текущее значение link совпадает со значением SelectedCategory, созданный элемент добавляется к другому классу Bootstrap, который обеспечивает подсветку кнопки. Запустив приложение, можно увидеть результат подсвечивания выбранной категории:

Корректировка счетчика страниц
Понадобится также скорректировать ссылки на страницы, чтобы они правильно работали, когда выбрана какая-то категория. В настоящий момент количество ссылок на страницы определяется общим числом товаров, а не количеством товаров выбранной категории. Это значит, что пользователь может щелкнуть на ссылке для страницы 2 категории «Шутер» и получить пустую страницу, поскольку товаров данной категории не хватает для заполнения второй страницы.
Проблема продемонстрирована на рисунке ниже:

Это можно исправить, модифицировав метод действия List() в контроллере Game так, чтобы при разбиении на страницы категории принимались во внимание. Необходимые изменения показаны в примере ниже:
// . public ViewResult List(string category, int page = 1) < GamesListViewModel model = new GamesListViewModel < Games = repository.Games .Where(p =>category == null || p.Category == category) .OrderBy(game => game.GameId) .Skip((page - 1)*pageSize) .Take(pageSize), PagingInfo = new PagingInfo < CurrentPage = page, ItemsPerPage = pageSize, TotalItems = category == null ? repository.Games.Count() : repository.Games.Where(game =>game.Category == category).Count() >, CurrentCategory = category >; return View(model); >
При наличии выбранной категории возвращается количество элементов в этой категории, а в противном случае — общее количество товаров. Теперь во время просмотра товаров какой-либо категории ссылки в нижней части страницы корректно отражают количество товаров в этой категории:

Модульное тестирование: счетчик товаров определенной категории
Протестировать возможность генерации корректных счетчиков товаров для различных категорий можно очень просто — необходимо создать имитированное хранилище, которое содержит известные данные в диапазоне категорий, и затем вызывать метод действия List(), запрашивая каждую категорию по очереди. Модульный тест выглядит следующим образом:
[TestMethod] public void Generate_Category_Specific_Game_Count() < /// Организация (arrange) Mockmock = new Mock(); mock.Setup(m => m.Games).Returns(new List < new Game < GameId = 1, Name = "Игра1", Category="Cat1">, new Game < GameId = 2, Name = "Игра2", Category="Cat2">, new Game < GameId = 3, Name = "Игра3", Category="Cat1">, new Game < GameId = 4, Name = "Игра4", Category="Cat2">, new Game < GameId = 5, Name = "Игра5", Category="Cat3">>); GameController controller = new GameController(mock.Object); controller.pageSize = 3; // Действие - тестирование счетчиков товаров для различных категорий int res1 = ((GamesListViewModel)controller.List("Cat1").Model).PagingInfo.TotalItems; int res2 = ((GamesListViewModel)controller.List("Cat2").Model).PagingInfo.TotalItems; int res3 = ((GamesListViewModel)controller.List("Cat3").Model).PagingInfo.TotalItems; int resAll = ((GamesListViewModel)controller.List(null).Model).PagingInfo.TotalItems; // Утверждение Assert.AreEqual(res1, 2); Assert.AreEqual(res2, 2); Assert.AreEqual(res3, 1); Assert.AreEqual(resAll, 5); >
Обратите внимание, что в модульном тесте также вызывается метод List() без указания категории, чтобы удостовериться в правильности подсчета общего количества товаров.