/
Author: Фримен А.
Tags: компьютерные технологии программирование язык программирования c#
ISBN: 978-5-9908910-4-3
Year: 2017
Text
ASP.NET CORE MVC с примерами на С# ДЛЯ ПРОФЕССИОНАЛОВ 6-е издание
Pro ASP.NET Core MVC Sixth Edition Adam Freeman
ASP.NET Core MVC с примерами на С# ДЛЯ ПРОФЕССИо·НАЛОВ 6-е издание Адам Фримен Москва· Санкт-Петербург· Киев 2017
ББК 32.973.26-018.2 .75 Ф88 УДК 681.3.07 Компьютерное издательство "Диалектика" Зав. редакцией С.Н. Тригуб Перевод с английского Ю.Н. Артеменко Под редакцией Ю.Н. Артеменко По общим вопросам обращайтесь в издательство "Диалектика" по адресу: info@dialektika.com,http://www.dialektika.com Фримен, Адам. Ф88 ASP.NET Core MVC с примерами на С# для профессионалов , 6-е изд. : Пер. с англ. - СпБ. : ООО "Альфа-книга", 2017. - 992 с. : ил. - Парал. тит. англ. ISBN 978-5-9908910-4-3 (рус .) ББК 32.973.26-018.2.75 Все названия программных продуктов являются зарегистрированными торговыми мар ками соответствующих фирм . Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то ни было форме и какими бы то ни было средствами, будь то электронные или механические, включая фотокопирование и запись на магнитный носит ель, если на это н ет письменного разрешения издательст в а APгess, Berkel ey, СА. Authoriz ed translatioп from the Eпglish laпguage editioп puЬlished Ьу APress , !пс " Copyright © 2016 Ьу Adam Fr ee maп. All rights reserved. No part of this work тау Ье reproduc ed or traпsmitted lп апу form or Ьу апу meaпs, electroпic or mechaпical , lпcludiпg photocopylпg, recordlng, or Ьу any lnformatioп storage or retri eva l system, without the prior wrltteп permissioп of the copyright owner апd the puЬlish e r. 1Тademarked names may appear in this book. Rather th aп use а trademark symbol with every оссurгепсе of а trademarked пате , we use th e п ames опlу iп ап editorial fashioп апd to tl1e beп efit of the trademark owпer. with по iпteпtloп of iпfringemeпt of the trademark. Russiaп laпguage editioп is puЬlished Ьу Dialektika Computer Books PuЬlishiпg according to the Agreemeпt with R&I Eпterprises Iпterпatioпal , Copyright © 2017. Научно-популярное издание Адам Фримен ASP.NET Core МVС с примерами на С# для профессионалов 6-е издание Верстка Художественный редактор Т.Н. Артеменко В.Г. Павлютин Подписано в печать 23.01.2017. Формат 70Х100/ 16. Гарнитура Тimes. Печать офсетная. Усл. печ. л. 79,98. Уч.-изд. л. 58,4. ТИраж 500 энз. Заназ No 648. Отпечатано способом ролевой струйной печати в АО • Первая Образцовая типография • Филиал • Чеховский Печатный Двор » 142300, Московская область, г. Чехов, ул. Полиграфистов, д. l ООО "Альф а-ннига", 195027, Саннт-Петербург, Магнитогорская ул " д. 30 ISBN 978-5 -9908910-4-3 (рус.) ISBN 978-1 -484-20398-9 (англ.) © Компьютерное издательство "Диалентина ", 2017 © Ьу Adam Freeman , 2017
Оглавление Часть 1. Введение в инфраструктуру ASP.NET Core MVC 19 Глава 1. Основы ASP.NET Core MVC 20 Глава 2. Ваше первое приложение MVC 29 Глава 3. Паттерн MVC, проекты и соглашения 68 Глава 4. Важные функциональные возможности языка С# 82 Глава 5. Работа с Razor 115 Глава 6. Работа с Visual Studio 136 Глава 7. Модульное тестирование приложений MVC 170 Глава 8. SportsStore: реальное приложение 201 Глава 9. SportsStore: навигация 244 Глава 10. SportsStore: завершение построения корзины для покупок 276 Глава 11. SportsStore: администрирование 298 Глава 12. SportsStore: защита и развертывание 325 Глава 13. Работа с Visual Studio Code 348 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 373 Глава 14. Конфигурирование приложений 374 Глава 15. Маршрутизация URL 426 Глава 16. Дополнительные возможности маршрутизации 465 Глава 17. Контроллеры и действия 500 Глава 18. Внедрение зависимостей 543 Глава 19. Фильтры 576 Глава 20. Контроллеры API 614 Глава 21. Представления 644 Глава 22. Компоненты представлений 678 Глава 23. Дескрипторные вспомогательные классы 708 Глава 24. Использование дескрипторных вспомогательных классов для форм 741 Глава 25. Использование других встроенных дескрипторных вспомогательных классов Глава 26. Привязка моделей Глава 27. Проверка достоверности моделей Глава 28. Введение в ASP.NET Core ldeпtity Глава 29. Применение ASP.NET Core ldentity Глава 30. Расширенные средства ASP.NET Core ldentity Глава 31. Соглашения по модели и ограничения действий Предметный указатель 766 793 828 861 898 926 958 987
Содержание Об авторе Часть 1. Введение в инфраструктуру ASP.NET Core MVC Глава 1. Основы ASP.NET Core MVC История развития ASP. NЕТ Core МVС ASP.NEТWeb Foгms Первоначальная инфраструктура МVС Fгamewoгk Обзор ASP.NET Core Основные преимущества ASP.NET Core МVС Архитектура МVС Расширяемость Жесткий контроль над HTML и НТТР Тестируемость Мощная система маршрутизации Современный АРI-интерфейс Межплатформенная природа Инфраструктура ASP. NEТ Core МVС имеет открытый код Что необходимо знать? Канава структура книги? Часть I. Введение в инфраструктуру ASP.NEТ Соге МVС Часть П . Подробные сведения об инфраструктуре ASP.NEТ Core МVС Что нового в этом издании? Iде можно получить код примеров? Резюме Глава 2. Ваше первое приложение MVC Установка Visual Studio Создание нового проекта ASP. NЕТ Саге МVС Добавление первого контроллера Понятие маршрутов Визуализация неб - страниц Создание и визуализация представления Добавление динамического вывода Создание простого приложения для ввода данных Предварительная настройка Проектирование модели данных Создание второго действия и строго типизированного представл е ния Ссылка на методы действий Построение формы Получение данных формы Добавление проверки достоверности Стилизация содержимого Резюме 18 19 20 20 21 22 23 24 24 24 25 25 25 26 26 27 27 27 27 28 28 28 28 29 29 31 33 36 37 37 40 42 42 43 44 46 47 49 56 61 67
Содержание 7 Глава З. Паттерн MVC, проекты и соглашения 68 История создания МVС 68 Особенности паттерна МVС 68 Понятие моделей 69 Понятие контроллеров 70 Понятие представлений 70 Реализация MVC в ASP.NEТ Core 70 Сравнение MVC с другими паттернами 71 Паттерн интеллектуального пользовательского интерфейса 71 Проекты ASP. NEТ Core МVС 75 Создание проекта 76 Соглашения в проекте МVС 79 Резюме 81 Глава 4. Важные функциональные возможности языка С# 82 Подготовка проекта для примера 83 Включение ASP. NЕТ Core МVС 84 Создание компонентов приложения МVС 85 Использование nul l-условной операции 87 Связывание в цепочки nu ll-условных операций 88 Комбинирование nul l -условной операции и операции объединения с n u ll 89 Использование автоматически реализуемых свойств 90 Использование инициализаторов автоматически реализуемых свойств 91 Со здание автоматически реализуемых свойств только для чтения 92 Использование интерполяции строк 93 Использование инициализаторов объектов и коллекций 94 Использование инициализатора индексированной коллекции 96 Использование расширяющих методов 97 Применение расширяющих методов к интерфейсу 98 Создание фильтрующих расширяющих методов l00 Использование лямбда- выражений 101 Определени е функций l03 Использование методов и свойств в форме лямбда-выражений 105 Использование автоматического выведения типа и анонимных типов l07 Использование анонимных типов l08 Использование асинхронных методов l09 Работа с задачами напрямую 110 Применение ключевых слов a sync и aw a i t 111 Получение имен 113 Резюме 114 Глава 5. Работа с Razor 115 Подготовка проекта для примера 116 Определение модели 117 Создание контроллера 117 Создание представления 118 Работа с объектом модели 119 Использование файла импортирования представлений 121 Работа с компоновками 123 Создание компоновки 123
8 Содержание Применение компоновки Использование файла запуска представления Использование выражений Razor Вставка значений данных Установка значений атрибутов Использование условных операторов Проход по содержимому массивов и коллекций Резюме Глава 6. Работа с Visual Studio Подготовка проекта для примера Создание модели Создание контроллера и представления Управление программными пакетами Инструмент NuGet Инструмент Bower Итеративная разработка Внесение изменений в представления Razoг Внесение изменений в классы С# Использование средства Вгоwsег Link Подготовка файлов JavaScript и CSS для развертывания Включение доставки статического содержимого Добавление в прое1tт статического содержимого Обновление представления Пакетирование и минификация в приложениях МVС Резюме Глава 7. Модульное тестирование приложений MVC Подготовка проекта для примера Включение встроенных дескрипторных вспомогательных классов Добавление действий к контроллеру Создание формы для ввода данных Обновление представления Index Модульное тестирование приложений МVС Создание проекта модульного тестирования Изолирование компонентов для модульного тестирования Улучшение модульных тестов Параметризация модульного теста Улучшение фиктивных реализаций Резюме Глава 8. SportsStore: реальное приложение Начало работы Создание проекта МVС Создание проекга модульного тестирования Проверка и запуск приложения Начало работы с моделью предметной области Создание хранилища Создание фиктивного хранилища Регистрация службы хранилища 125 126 128 129 130 131 133 135 136 137 137 139 141 141 143 147 147 148 156 161 161 162 164 166 169 170 171 171 172 172 173 174 175 182 190 190 194 200 201 202 202 207 209 209 210 210 211
Отображение списка товаров Добавление контроллера Добавление и конфигурирование представления Установка стандартного маршрута Запуск приложения Подготовка базы данных Установка Entity Fгarnework Core Создание классов базы данных Создание юшсса хранилища Определение строки подключения Конфигурирование приложения Создание и применение миграции базы данных Добавление поддержки разбиения на страницы Отображение ссьmок на страницы Улучшение URL Стилизация содержимого Установка пакета Bootstrap Применение стилей Bootstrap к компоновке Создание частичного представления Резюме Глава 9. SportsStore : навигация Добавление навигационных элементов управления Фильтрация списка товаров Улучшение схемы URL Построение меню навигации по категориям Корректировка счетчика страниц Построение корзины для покупок Определение модели корзины Создание кнопок добавления в корзину Включение поддержки сеансов Реализация контроллера для корзины Отображение содержимого корзины Резюме Глава 1О. SportsStore: завершение построения корзины для покупок Усовершенствование модели корзины с помощью службы Создание класса корзины, осведомленного о хранилище Регистрация службы Упрощение контроллера Cart Завершение функциональности корзины Удаление элементов из корзины Добавление виджета с итоговой информацией по корзине Отправка заказов Создание класса модели Добавление реализации процесса оплаты Реализация обработки заказов Завершение построения контроллера Order Отображение сообщений об ошибках проверки достоверности Отображение итоговой страницы Резюме Содер жа ние 9 212 213 215 216 217 218 219 220 221 222 223 225 226 228 236 237 238 238 241 242 244 244 244 248 252 259 262 262 266 267 269 272 275 276 276 276 277 278 279 279 281 284 284 285 287 291 295 296 297
1О Содержание Глава 11. SportsStore: администрирование Управление заказами Расширение модели Добавление действий и представления Добавление средств управления каталогом Создание контроллера CRUD Реализация представления списка Редактирование сведений о товарах Создание новых товаров Удаление товаров Резюме Глава 12 . SportsStore: защита и развертывание Защита средств администрирования Добавление в проект пакета Jdentity Создание базы данных Identity Применение базовой политики авторизации Создание контроллера Accoun t и представлений Тестирование политики безопасности Развертывание приложения Создание баз данных Подготовка приложения Применение миграций баз данных Процесс развертывания приложения Резюме Глава 13 . Работа с Visual Studio Code Настройка среды разработки Установка Node.js Проверка установки Node Установка Git Проверка установки Git Установка Yeoman, Bower и Gulp Установка .NETCore Проверка установки .NЕТ Core Установr\а Visual Studio Code Проверка установки Visual Studio Code Установка расширения С# для Visual Studio Code Создание проекта ASP. NET Core Подготовка проекта с помощью Visual Studio Code Добавление в проект пакетов NuGet Добавление в проект пакетов клиентской стороны Конфигурирование приложения Построение и запуск проекта Воссоздание приложения Partylnvites Создание модели и хранилища Создание базы данных Создание контроллеров и представлений Модульное тестирование в Visual Studio Code Конфигурирование приложения Создание модульного теста Прогон тестов Резюме 298 298 298 299 302 303 304 306 320 321 324 325 325 325 326 330 332 336 336 337 338 343 343 346 348 348 349 350 350 351 351 351 352 353 353 353 355 356 357 358 359 359 360 360 363 365 369 369 371 371 372
Содержание 11 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 373 Глава 14. Конфигурирование приложений 374 Подготовка проекта для примера 376 Конфигурационные файлы JSON 377 Конфигурирование решения 378 Конфигурирование проекта 380 Класс Prograrn 383 Класс Startup 385 Особенности использования класса Startup 386 Службы ASP.NEТ 387 Промежуточное программное обеспечение ASP.NEТ 390 Особенности вызова метода Configu re () 400 Добавление оставшихся компонентов промежуточного программного обеспечения 408 Использование данных конфигурации 413 Конфигурирование служб МVС 419 Работа со сложными конфигурациями 421 Создание разных внешних конфигурационных файлов 421 Создание разных методов конфигурирования 422 Создание разных классов конфигурирования 424 Резюме 425 Глава 15. Маршрутизация URL 426 Подготовка проекта для примера 428 Создание класса модели 429 Создание контроллеров 430 Создание представления 431 Введение в шаблоны URL 432 Создание и регистрация простого маршрута 434 Определение стандартных значений 435 Определение встроенных стандартных значений 436 Использование статических сегментов URL 438 Определение специальных переменных сегментов 442 Использование специальных переменных в качестве параметров метода действия 445 Определение необязательных сегментов URL 446 Определение маршрутов переменной длины 448 Ограничение маршрутов 451 Ограничение маршрута с использованием регулярного выражения 454 Использование ограничений на основе типов и значений 455 Объ единение ограничений 456 Определение специального ограничения 458 Использование маршрутизации с помощью атрибутов 460 Подготовка для маршрутизации с помощью атрибутов 460 Применение маршрутизации с помощью атрибутов 461 Применение ограничений к маршрутам 463 Резюме 464
12 Содержание Глава 16. Дополнительные возможности маршрутизации Подготовка проекта для примера Генерация исходящих URL в представлениях генерирование исходящих ссылок Генерация URL (без ссылок) генерирование URL в методах действий Настройка системы маршрутизации Изменение конфигурации системы маршрутизации Создание специального класса маршрута Применение специального класса маршрута Маршрутизация на контроллеры МVС Работа с областями Создание области Создание маршрута для области Заполнение области Генерирование ссылок на действия в областях Полезные советы относительно схемы URL Делайте URL чистыми и понятными человеку GET и POST: выбор правильного запроса Резюме Глава 17. Контроллеры и действия Подготовка проекта для примера Подготовка представлений Поняти е контроллеров Создание контроллеров Создание контроллеров РОСО Использование базового класса Controller Получение данных контекста Получение данных из объектов контекста Использование параметров метода действия генерирование ответа Генерирование ответа с использованием объекта контекста Понятие результатов действий Генерирование НТМL-ответа Передача данных из метода действия в представление Выполнение перенаправления Возвращение разных типов содержимого Реагирование с помощью содержимого файлов Возвращение ошибок и кодов Н1ТР Другие классы результатов действий Резюме Глава 18. Внедрение зависимостей Подготовка проекта для прим ера Создание модели и хранилища Создание контроллера и представления Создание проекта модульного тестирования Создание слабо связанных компонентов Исследование сильно связанных компонентов 465 467 468 468 478 478 479 479 481 484 485 491 492 492 493 496 497 497 499 499 500 501 503 505 506 506 508 509 510 514 516 516 517 520 523 528 535 538 540 542 542 543 544 545 547 548 549 550
Содержание 13 Введение в средство внедрения зависимостей ASP. NET 556 Подготовка к внедрению зависимостей 556 I\онфигурирование поставщика служб 558 Модульное тестирование контроллера с зависимостью 560 Использование цепочек зависимостей 560 Использование внедрения зависимостей для конкретных типов 563 Жизненные ЦИIUIЫ служб 565 Использование переходного жизненного цикла 566 Использование жизненного цикла, ограниченного областью действия 570 Использование жизненного цикла одиночки 571 Использование внедрения в действия 572 Использование атрибутов внедрения в свойства 573 Запрашивание объекта реализации вручную 574 Резюме 575 Глава 19. Фильтры 576 Подготовка проекта для примера 577 Включение SSL 579 Создание контроллера и представления 579 Использование фильтров 581 Понятие фильтров 584 Получение данных контекста 585 Использование фильтров авторизации 585 Создание фильтра авторизации 586 Использование фильтров действий 588 Создание фильтра действий 590 Создание асинхронного фильтра действий 591 Использование фильтров результатов 592 Создание фильтра результатов 593 Создание асинхронного фильтра результатов 595 Создание гибридного фильтра действий/результатов 596 Использование фильтров исIUiючений 598 Создание фильтра исключений 599 Использование внедрения зависимостей для фильтров 600 Распознавание зависимостей в фильтрах 601 Управление жизненными циклами фильтров 605 Создание глобальных фильтров 608 Порядок применения фильтров и его изменение 61О Изменения порядка применения фильтров 612 Резюме 613 Глава 20. Контроллеры API 614 Подготовка проекта для примера 615 Создание модели и хранилища 615 Создание контроллера и представлений 617 Конфигурирование приложения 619 Роль контроллеров REST 621 Проблема скорости 621 Проблема эффективности 622 Проблема открытости 622
14 Содержание Введение в RESТ и контроллеры АР! Создание контроллера АР! Тестирование контроллера АР! Использование контроллера АР! в браузере Форматирование содержимого Стандартная политика содержимого Согласование содержимого Указание формата данных для действия Получение формата данных из маршрута или строки запроса Включение полного согласования содержимого Получение разных форматов данных Резюме Глава 21. Представления Подготовка проекта для примера Создание специального механизма визуализации Создание специальной реализации интерфейса I View Создание реализации интерфейса IViewEngine Регистрация специального механизма визуализации Тестирование механизма визуализации Работа с механизмом Razor Подготовка проекта для примера Прояснение представлений Razor Добавление динамического содержимого к представлению Razor Использование разделов компоновки Использование частичных представлений Добавление содержимого JSON в представления Конфигурирование механизма Razor Расширители местоположений представлений Резюме Глава 22. Компоненты представлений Подготовка проекта для примера Создание моделей и хранилиш Создание контроллера и представлений Конфигурирование приложения Понятие компонентов представлений Создание компонента представления Создание компонентов представлений РОСО Наследование от базового класса ViewComp onent Понятие результатов компонентов представлений Получение данных контекста Создание асинхронных компонентов представлений Создание гибридных компонентов контроллеров/представлений Создание гибридных представлений Прим енение гибридного класса Резюме Глава 23. Дескрипторные вспомогательные классы Подготовка проекта для примера Создание модели и хранилища 623 623 628 631 634 635 636 639 639 641 643 643 644 645 647 649 650 651 652 654 654 656 660 661 666 669 671 672 677 678 679 680 682 685 686 686 686 688 690 695 701 703 704 706 707 708 709 710
Содержание 15 Создание контроллера, компоновки и представлений 711 Конфигурирование приложения 713 Создание дескрипторного вспомогательного класса 715 Определение дескрипторного вспомогательного класса 715 Регистрация дескрипторных вспомогательных классов 719 Использование дескрипторного вспомогательного класса 719 Управление областью действия дескрипторного вспомогательного класса 721 Усовершенствованные возможности дескрипторных вспомогательных классов 726 Создание сокращающих элементов 726 Вставка содержимого перед и после элементов 728 Получение данных контекста представления и использование внедрения зависимостей 732 Работа с моделью представления 734 Согласование дескрипторных вспомогательных классов 736 Подавление выходного элемента 738 Резюме 740 Глава 24. Использование дескрипторных вспомогательных классов для форм 7 41 Подготовка проекта для примера 743 Изменение регистрации дескрипторных вспомогательных классов 743 Переустановка представлений и компоновки 743 Работа с элементами f о rrn 745 Установка цели формы 746 Использование средства противодействия подделке 746 Работа с элементами input 749 Конфигурирование элементов input 749 Форматирование значений данных 751 Работа с элементами label 754 Работа с элементами select и option 756 Использование источника данных для заполнения элемента se lect 758 генерирование элементов option из перечисления 758 Работа с элементами textarea 763 Дескрипторные вспомогательные классы для проверки достоверности форм 764 Резюме 765 Глава 25. Использование других встроенных дескрипторных вспомогательных классов Подготовка проекта для примера Использование вспомогательного класса для среды размещения Использование вспомогательных классов для JavaScript и CSS Управление файлами JavaScript Управление таблицами стилей CSS Работа с якорными элементами Работа с элементами i rng Использование кеша данных Установка времени истечения для кеша Использование вариаций кеша Использование URL, относительных к приложению Резюме 766 767 768 769 769 778 781 782 783 786 788 789 792
16 Содержание Глава 26. Привязка моделей 793 Подготов:ка прое:кта для примера 794 Создание модели и хранилища 795 Создание контроллера и представления 796 Конфигурирование приложения 798 Понятие привязки моделей 799 Стандартные значения привязки 80l Привязка простых типов 802 Привязка сложных типов 803 Привязка массивов и коллекций 813 Привязка коллекций сложных типов 817 Указание источника данных привяз:ки моделей 820 Выбор стандартного источника данных привяз:ки 820 Использование заголовков в качестве источников данных привязки 821 Использование тел запросов в качестве источни:ков данных привязки 824 Резюме 827 Глава 27 . Проверка достоверности моделей 828 Подготовка проекта для примера 830 Создание модели 831 Создание контроллера 831 Создание компоновки и представлений 832 Необходимость в проверке достоверности модели 834 Явная проверка достоверности модели 835 Отображение пользователю ошибок проверки достоверности 838 Отображение сообщений об ошибках проверки достоверности 839 Отображение сообщений об ошибках проверки достоверности на уровне свойств 844 Отображение сообщений об ошибках проверки достоверности на уровне модели 845 Указание правил проверки достоверности с помощью метаданных 849 Создание специального атрибута проверки достоверности для свойства 852 Выполнение проверки достоверности на стороне :клиента 854 Выполнение удаленной проверки достоверности 857 Резюме 860 Глава 28. Введение в ASP .NET Core ldentity 861 Подготовка проекта для примера 863 Создание контроллера и представления 864 Настрой:ка ASP. NЕТ Core Identity 866 Добавление пакета Identity в приложение 866 Создание :класса пользователя 867 Создание класса контекста базы данных 869 Конфигурирование настройки строки под:ключения к базе данных 869 Конфигурирование служб и промежуточного программного обеспечения Identity 870 Создание базы данных Identity 872 Использование ASP.NEТ Core Identity 872 Перечисление пользовательских учетных записей 873 Создание пользователей 875 Проверка паролей 879 Проверка деталей, связанных с пользователем 886
Завершение средств администрирования Реализация средства удаления Реализация возможности редантирования Резюме Глава 29. Применение ASP.NET Core ldentity Подготовка проекта для примера Аутентификация пользователей Подготовка к реализации аутентификации Добавление аутентификации пользователей Тестирование аутентификации Авторизация пользователей с помощью ролей Создание и удаление ролей Управление членством в ролях Использование ролей для авторизации Помещение в базу данных начальных данных Резюме Глава 30. Расширенные средства ASP.NET Core ldentity Подготовка проекта для примера Добавление специальных свойств в класс пользователя Подготовка миграции базы данных Тестирование специальных свойств Работа с заявками и политиками Понятие заявок Создание заявок Использование политю< Использование политик для авторизации доступа к ресурсам Использование сторонней аутентификации Регистрация приложения в Google Включение аутентификации Google Резюме Глава 31. Соглашения по модели и ограничения действий Подготовка проекта для примера Создание модели представления, контроллера и представления Использование модели приложения и соглашений по модели Модель приложения Роль соглашений по модели Создание соглашения по модели Порядок вьmолнения соглашений по модели Создание глобальных соглашений по модели Использование ограничений действий Подготовка проекта для примера Ограничения действий Создание ограничения действия Распознавание зависимостей в ограничениях действий Резюме Предметный указатель Содержание 17 890 891 892 897 898 898 899 901 904 907 908 909 914 919 923 925 926 927 927 931 931 932 933 937 940 946 951 951 952 957 958 959 960 962 963 967 968 973 974 976 977 978 979 984 986 987
Посвящается моей прекрасной жене Джеки Гриффиmс. Об авторе Адам Фримев - опытный специалист в области информационных технологий, занимавший ведущие позиции во многих компаниях, последней из которых был гло бальный банк, где он работал на должностях директора по внедрению технологий и руководителя административной службы. После ухода из банка Адам уделяет все свое время писательской деятельности и бегу на длинные дистанции . О техническом рецензенте Фабио Клау.цио Ферраччати - ведущий консультант и главный аналитик/раз работчик, исполь зующий технологии Microsoft. Он работает в компании Brain Force (www . Ыua r anc i o . com). Фабио является сертифицированным Microsoft разработчи ком реш ений для .NET (Microsoft Certified Solution Developer for .NET), сертифициро ванным Microsoft разработчиком приложений для .NET (Microsoft Certified Application Develop er for .NET), сертифицированным Microsoft профессионалом (Microsoft Certifled Professional), а также плодовитым автором и техническим рецензентом. За последние десять лет он написал множество статей для итальянских и международных журна лов и выступал в качестве соавтора в более чем 10 книгах по разнообразны м темам , связанным с 1щмпьютерами. Ждем ваших отзы вов! Вы, читатель этой книги , и есть главный ее критик. Мы ценим ваше мнение и хо тим знать, что было сдел ано нами правильно, что можно было сделать лучше и что еще вы хотели бы увидеть изданным нами. Нам интересны любые ваши замечания в наш адрес. Мы ждем ваших комментариев и надеемся на них . Вы можете прислать нам бу мажное или электронное письмо либо просто посетить наш веб-сайт и оставить свои замечания там. Одним словом, любым удобным для вас способом дайте нам знать , нравится ли вам эта книга, а также выскажите свое мнение о том , как сделать наши книги более интересными для вас. Отпр авляя письмо или сообщение, не забудьте указать название книги и ее авто ров , а та кже свой обратный адрес. Мы внимательно ознакомимся с вашим мнением и обязательно учтем его при отборе и подготовке к изданию новых книг. Наши эл е ктронные адреса: E-mail: WWW: info@dialektika.com http : //www . dialektika . com Наши почтовые адр еса: в России: в Украине: 195027, Санкт-Петербург, Магнитогорская ул., д. 30 , ящик 116 03150, Киев, а/я 152
ЧАСТЬ 1 Введение в инфраструктуру ASP.NET Core МVС д ля разработчиков веб-приложений, использующих платформу Microsoft, инфраструктура ASP.NET Core MVC представляется как радикальное изме нен ие . Особое значение придается чистой архитектуре, паттернам проектирова ния и удобству тестирования , к тому же не предпринимается попытка скрывать, каким образом работает веб-среда. Первая часть этой книги поможет понять в общих чертах основополагающие идеи разработки приложений MVC, включая новые возможности ASP. NЕТ Core MVC, и увидеть на деле, как применяется инфраструктура .
ГЛАВА 1 Основы ASP.NET Core МVС ASP.NEТ Core MVC - это инфраструктура для разработки веб-приложений про изводства Micгosoft , которая сочетает в себе эффективность и аккуратность ар хитектуры "модель-представление-контроллер" (model-view-controller - MVC), идеи и приемы гибкой разработки. а также лучшие части платформы .NET . В настоящей главе вы узнаете, почему компания Micгosoft создала инфраструктуру ASP.NET Соге МVС, увидите, как она соотносится со своими предшественниками и альтернативами, а также ознакомитесь с обзором нововведений ASP.NET Core MVC и с тем, что будет рассматриваться в книге. История развития ASP.NET Core MVC Первая версия платформы ASP.NET появилась в 2002 году, в то время, когда компа ния Micгosoft стремилась сохранить господствующее положение в области разработки традиционных настольных приложений и видела в Интернете угрозу. На рис. 1.1 по казано , как выглядел на то время стек технологий Micгosoft. ASP.NETWeb Forms Набор компонентов пользовательского интерфейса (страниц, кнопок и т.д.) плюс объектно-ориентированная программная модель графического пользовательского интерфейса с сохранением состояния ASP.NET Способ размещения приложений .NET в llS (веб-сервер Microsoft), позволяющий взаимодействовать с запросами и ответами НТТР . NET Мно гоязычная платформа управляемого кода (совершенно новая на то время и по праву ставшая поворотным пунктом в развитии) Рис . 1 . 1. Стек тех нолог ий ASP.NET Web Forms
Глава 1. Основы ASP.NET Core MVC 21 ASP .NET Web Forms В Web Forms разработчики из Microsoft пытались скрыть как протокол НТГР (с при сущим ему отсутствием состояния), так и язык HTML (которым на тот момент не вла дели многие разработчики) за счет моделирования пользовательского интерфейса как иерархии объектов , представляющих серверные элементы управления. Каждый элемент управления отслеживал собственное состояние между запросами, по мере необходимости визуализируя себя в виде НТМL-разметки и автоматически соединяя события клиентской стороны (например, щелчки на кнопках) с соответствующим ко дом их обработки на стороне сервера . Фактически Web Forms - это гигантский уровень абстракции, предн азначенный для доставки классического управляемого событиями графического пользовательского интерфейса через веб-среду. Идея заключалась в том, чтобы разработка веб-приложений выглядела почти так же. как разработка настольных приложений. Разработчики могли оперировать по нятиями пользовательского интерфейса, запоминающего состояние, и не иметь дело с последовательностями независимых запросов и ответов НТГР. У компании Microsoft появилась возможность переместить армию разработчиков настольных Windоws приложений в новый мир веб-приложений. Что было не так с ASP.NET Web Forms? Разработка с использ ованием традиционной технологии ASP.NET Web Forms в при нципе была хорошей, но реальность оказалась более сложной. • Тяжеловесност ь состояния представления (View State). Действительный меха низм поддержки состояния между запросами, известный как View State, вызвал необходимость передачи крупных блоков данных между клиентом и сервером. Объем таких данных мог достигать сотен килобайт даже в скромных веб-прило жениях. Эти данные путешествуют туда и обратно с каждым запросом, приводя к замедлению реакции и увеличивая требование к ширине полосы пропускания сервера. • ){{изненный 4икл страницы. Механизм соединения событий клиентской сто роны с ~юдом обработчиков событий на стороне сервера, являющийся частью жизненного цикла страницы, мог быть сложным и хрупким . Лишь немногим разработчикам удавалось успешно манипулировать иерархией элементов уп равления во время выполнения, не получая ошибки состояния представления или не сталкиваясь с ситуацией, когда некоторые обработчики событий зага дочным образом отказывались запускаться. • Ложное ощущение разделения обязанностей. Модель отделенного кода ASP.NET Web Forms предоставляла способ вынесения кода приложения из НТМL-разметки в специальный файл отделенного кода. Это было сделано для разделения логи ки и представления, но в действительности попустительствовало смешиванию кода представления (например, манипуляций деревом элементов управления серверной стороны) с прикладной логикой (скажем , обработкой информации из базы данных) в гигантских классах отделенного кода. Конечный результат мог быть хрупким и непонятным. • Ограниченный контроль над НТМL-разметкой. Серверные элементы управле ния визуализировали себя в виде НТМL-разметки, но она не обязательно была такой. как хотелось. В ранних версиях Web Forms выходная НТМL-разметка не
22 Часть 1. Введение в инфраструктуру ASP.NET Core MVC соответствовала веб-стандартам или неэффективно применяла каскадные таб лицы стилей (Cascading Style Sheets - CSS), а серверные элементы управления генерировали непредсказуемые и сложные атрибуты идентификаторов, к кото рым было трудно получать доступ с помощью JavaScript. Эти проблемы были значительно смягчены в последних выпусках Web Forms, но получение ожидае мой НТМL- разметки по-прежнему могло быть затруднительным . • Негерметич.ная абстракция. Инфраструктура Web Forms пыталась скрывать де тали, связанные с HTML и НТТР, где только возможно . При попытке реализовать специальное поведение абстракция часто отбрасывалась, поэтому для генериро вания желаемой НТМL-разметки приходилось воссоздавать механизм обратной отправки событий или предпринимать другие сложные действия. • Низкое удобство тестирования. Проектировщики Web Forms даже не пред полагали, что автоматизированное тестирование станет неотъемлемой час тью процесса разработки программного обеспечения . Построенная ими тесно связанная архитектура не была приспособлена для модульного тестирования. Интеграционное тестирование также могло превратиться в сложную задачу. С технологией Web Forms не все было плохо, и в Microsoft приложили немало уси лий по улучшению степени соответствия стандартам, упрощению процесса разра ботки и даже заимствованию ряда возможностей из первоначальной инфраструкту ры ASP.NET MVC Framework для их применения к Web Forms . Технология Web Forms превосходна, когда необходимы быстрые результаты, и с ее помощью вполне реально построить и запустить веб-приложение умеренной сложности всего за один день. Но если не проявлять осторожность во время разработки , то обнаружится, что созданное приложение трудно тестировать и сопровождать. Первоначальная инфраструктура MVC Framework В октябре 2007 года компания Microsoft объявила о выходе новой платформы раз работки, построенной на основе существующей платформы ASP.NET, которая была задумана как прямая реакция на критику Web Forms и популярность конкурирующих платформ наподобие Ruby on Rails. Новая платформа получила название ASP.NET МVС Framework и отражала формирующиеся тенденции в разработке веб - приложе ний, тшше как стандартизация HTML и CSS, веб - службы REST, эффективное модуль ное тестирование, а также идея о том, что разработчики должны принять факт от сутствия состояния у НТТР. Концепции, которые подкрепляли первоначальную инфраструктуру МVС Framework, теперь кажутся естественными и очевидными, но в 2007 году они отсутствовали в мире разработки веб-приложений .NET. Появление ASP.NET МVС Framework возврати ло платформу разрабопш веб-приложений Microsoft в современную эпоху. Инфраструктура МVС Framework также сигнализировала о важном изменении в отношении компании Microsoft, которая ранее пыталась контролировать каждый ком понент в инструментальных средствах, необходимых для веб-приложений . Компания Microsoft встроила в инфраструктуру MVC Framework инструменты с открытым ко дом, такие кaкjQuery, учла проектные соглашения и передовой опыт конкурирующих (и более успешных) платформ, а также выпустила в свет исходный код MVC Framework для изучения разработчиками.
Глава 1. Основы ASP.NET Core MVC 23 Что было не так с первоначальной инфраструктурой MVC Framework? На то время для Microsoft имело смысл создавать инфраструктуру МVС Framework повер х существующей платформы, содержащей большой объем монолитной низ коуровневой функциональности, которая обеспечила преимущество в начале про цесса разработки и которую хорошо знали и понимали разработчики приложений ASP.NET. Компромиссы потребовали основать MVC Framework на платформе, которая из начально предназначалась для Web Forms . Разработчики MVC Framework привыкли использовать конфигурационные параметры и кодовые настройки, отключающие или реконфигурирующие средства, которые не имели ни малейшего отношения к их веб приложениям , но требовались для того, чтобы все заработало. С ростом популярности MVC Framework компания Microsoft приступила к добав лению ряда ее основных возможностей к Web Forms. Результат был все более и более странным, когда индивидуальные особенности проекта, требуемые для поддержки MVC Fra mework, расширялись с целью поддержки Web Forms, и предпринимались до полнительные проектные ухищрения, чтобы подогнать все друг к другу. Одновременно в Microsoft начали расширять ASP.NET новыми инфраструктурами для создания веб служб (Web АР!) и организации коммуникаций в реальном времени (SignalR) . Новые инфраструктуры добавили собственные соглашения по конфигурированию и разра ботке, каждое из которых обладало своими преимуществами и причудами, породив в результате фрагментированную смесь. Обзор ASP .NET Core В 20 15 году Microsoft заявила о новом направлении для ASP.NEТ и MVC Framewor k, которое в итоге привело к появлению инфраструктуры ASP.NET Core МVС, рассматри ваемой в настоящей книге. Платформа ASP.NET Core построена на основе .NET Core, которая представляет со бой межплатформенную версию .NET Framework без интерфейсов программирования приложений (АР!), специфичных для Windows. Господствующей операционной сис темой по-прежнему является Windows, но веб - приложения все чаще размещаются в небольших и простых контейнерах на облачных платформах . За счет принятия меж платформенного подхода компания Microsoft расширила область охвата .NЕТ, сделав возможным развертывание приложений ASP.NET Core на более широком наборе сред размещения, а в качестве бонуса предоставила разработчикам возможность созда вать веб-приложения ASP.NET Core на машинах Linux и OS X/ macOS . ASP.NET Core - это сове р шенно новая инфраструктура. Она проще, с нею легче р а ботать, и она свободна от наследия, сопровождающего Web Forms. Будучи основан ной на .NET Core, она поддерживает разработку веб-приложений для ряда платформ и контейнеров. Инфраструктура ASP.NET Core МVС предоставляет функциональность первона чальной инфраструктуры ASP.NET MVC Framework, построенной поверх новой плат формы ASP.NET Core. Она включает функциональность, которая ранее предлагалась Web АР!, поддерживает более естественный способ генерирования сложного содержи мого и делает основные задачи разработки, такие как модульное тестирование, более простыми и предсказуемыми .
24 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Основные преимущества ASP.NET Core MVC В последующих разделах кратко описано, чем эта новая платформа MVC превос ходит унаследованную инфраструктуру Web Forms и первоначальную инфраструктуру MVC Framework, и что именно позволит вновь вывести ASP.NET на передний край. Архитектура MVC Инфраструктура ASP.NET Core MVC следует паттерну под названием "модель предсmавление-конmроллер" (model-view-controller - MVC), который управляет фор мой веб-приложения ASP.NET и взаимодействиями между содержащимися в нем компонентами. Важно различать архитектурный паттерн МVС и реализацию ASP.NEТ Core МVС. Паттерн МVС далеко не нов (его появление датируется 1978 годом и связано с про ектом Smalltalk в Xerox PARC), но в наши дни он завоевал популярность в качестве паттерна для веб-приложений по перечисленным ниже причинам. • Взаимодействие пользователя с приложением, которое придерживается паттер ном MVC, следует естественному циклу: пользователь предпринимает действие, а в ответ приложение изменяет свою модель данных и доставляет обновленное представление пользователю. Затем цикл повторяется. Это удобно укладывает ся в схему веб-приложений, предоставляемых в виде последовательностей за просов и ответов НТТР. • Веб-приложения, нуждающиеся в сочетании нескольких технологий (например, баз данных, НТМL-разметки и исполняемого кода), обычно разделяются на на бор слоев или уровней. Получ енные в результате таких сочетаний комбинации естественным образом отображаются на концепции в паттерне MVC. Инфраструктура ASP.NET Core МVС реализует паттерн МVС и при этом обеспечи вает гораздо лучшее разделение обязанностей по сравнению с Web Forms. На самом деле в ASP.NET Core MVC внедрена разновидность паттерна MVC, которая особенно хорошо подходит для веб-приложений. Дополнительные сведения по теории и прак тике применения этой архитектуры приведены в главе 3. Расширяемость Инфр аструктуры ASP.NET Core и ASP.NET Core МVС построены в виде последова тельности независимых компонентов, которые имеют четко определенные характе ристики, удовлетворяют интерфейсу .NET или соэданы на основе абстрактного базо вого класса. Основные компоненты можно легко заменять другими компонентами с собственной реализацией. В общем случае для каждого компонента инфраструктура ASP.NET Core MVC предлагает три воэможности. • Использование стандартной реализации компонента в том виде, как есть (чего должно быть достаточно для большинства приложений). • Создание подкласса стандартной реализации с целью корре1пировки существу ющего поведения. • Полная замена компонента новой реализацией интерфейса или абстрактного базового класса. Разнообразные компоненты, а также способы и причины их возможной настройки или замены будут рассматриваться, начиная с главы 14.
Глава 1. Основы ASP.NET Саге MVC 25 Жесткий контроль над HTML и НТТР Инфраструктура ASP.NET Core МVС генерирует ясную и соответствующую стан дартам разметку. Ее встроенные дескрипторные вспомогательные классы (tag helper) производят соответствующий стандартам вывод, но существует также гораздо более значимое философское изменение по сравнению с Web Forms. Вместо генерации гро мадного объема НТМL-разметки, над которой вы имеете очень небольшой контроль, инфраструктура ASP.NET Core MVC поощряет создание простой и элегантной размет ки. стилизованной с помощью CSS. Конечно, если вы действительно хотите добавить готовые виджеты для таких сложных элементов пользовательского интерфейса, как окна выбора даты или кас кадные меню , то подход "никаких специальных требований", принятый в ASP.NET Core МVС, позволяет легко использовать наилучшие клиентские библиотеки. подоб ные jQuery, Aпgular или Bootstrap CSS. Инфраструктура ASP.NET Core MVC настолько тесно сплетена с этими библиотеками, что компания Microsoft включила их подде ржку как встроенных частей стандартного шаблона проектов для веб-приложений. Инфраструктура ASP.NET Core МVС работает в гармонии с НТТР. Вы имеете кон троль над запросами, передаваемыми между браузером и сервером, так что можете точно настраивать пользовательский интерфейс по своему усмотрению. Технология Ajax является легкой в применении, и создание веб-служб для получения браузерных НТТР-запросов представляют собой простой процесс, описанный в главе 20. Тестируемость Естественное разнесение различных обязанностей приложения по независимым друг от друга частям, которое поддерживается архитектурой ASP.NET Core MVC, поз воляет с самого начала делать приложение легко сопровождаемым и удобным для тестирования. Вдобавок каждый фрагмент платформы ASP.NET Core и инфраструк туры ASP.NET Core МVС может быть изолирован и заменен в целях модульного тес тирования , которое допускается выполнять с использованием любой популярной ин фраструктуры тестирования с оп,рытым кодом, такой кан xUnit, рассматриваемой в главе 7. В этой книге вы увидите примеры написания ясных и простых модульных тестов для контроллеров и действий MVC, которые предоставляют фиктивные либо имити рованные реализации компонентов инфраструктуры для эмуляции любого сценария с применением разнообразных стратегий тестирования и имитации. Даже если вам никогда ранее не приходилось создавать модульные тесты, у вас будет все необходи мое для успешного старта. Тестируемость касается не только модульного тестирования . Приложения ASP.NET Core МVС успешно работают также с инструментами тестирования, встроенными в средства автоматизации пользовательского интерфейса. Можно создавать тестовые сценарии, которые имитируют взаимодействие с пользователем, не выдвигая догадки о том , какие структуры НТМL-элементов, классы CSS или идентификаторы сгенери рует инфраструктура, и не беспокоясь о неожиданных изменениях структуры. Мощная система маршрутизации По мере совершенствования технологии построения веб-приложений эволюциони ровал стиль унифицированных указателей ресурсов (uniform resource locator - URL).
26 Часть 1. Введение в инфраструктуру AS P.NE T Core MVC Адреса URL, подобные приведенному ниже: /App_v2/U ser/Page . as p x?act ion = show%2 0p rop&prop id= 8 2742 встречаются все реже, а на смену им приходит более простой и понятный формат следующего вида: / t o -rent/chic a g o/2303 - sil v er -street Существует ряд веских причин для того, чтобы заботиться о структуре URL- aдpecoв . Во-первых, поисковые механизмы придают вес ключевым словам, содержа щимся в URL. Поиск по словосочетанию "rent in Chicago" (аренда в Чикаго) с большей вероятностью обнаружит более простой URL. Во-вторых, многие веб-пользователи достаточно сообразительны, чтобы понять URL, и ценят возможность навигации путем ввода запроса в адресной строке своего браузера. В-третьих, когда структура URL-aдpeca понятна. люди с большей вероятностью пройдут по нему, поделятся им с другими или даже продиктуют его по телефону . В-четвертых, при таком подходе в Интернете не раскрываются технические детали, структура каталогов и имен файлов приложения; следовательно, вы вольны изменять лежащую в основе сайта реализа цию, не нарушая работоспособности всех входящих ссьшок . В более ранних инфраструктурах понятные URL- aдpeca реализовать было трудно, но в ASP.NET Core MVC используется средство, известное как маршрутизация URL, которое обеспечивает предоставление понятных URL- aдpecoв по умолчанию. Это дает контроль над схемой URL и ее взаимосвязью с приложением, обеспечивая свободу создания понятного и удобного для пользователей шаблона URL без необходимости следования какому-то заранее определенному шаблону. И, разумеется. это означает тан:же простоту определения современной схемы URL в стиле REST, если она нужна. Подробное описание маршрутизации URL приведено в главах 15 и 16. Современный АРl-интерфейс Платформа Microsoft .NET развивалась с каждым крупным выпуском, поддержи вая - и даже определяя - многие передовые аспекты современного программирова ния . Инфраструктура ASP.NEТ Core MVC построена для платформы .NET Core, поэто му ее АРI-интерфейс может в полной мере задействовать последние новшества языка и исполняющей среды. знакомые программистам на С# , в том числе ключевое слово aw a i t, расширяющие методы, лямбда-выражения, анонимные и динамические типы, а также язык интегрированных запросов (Language Integrated Query - LINQ) . Многие методы и паттерны кодирования АРI-интерфейса ASP.NET Core MVC сле дуют более четкой и выразительной композиции, чем это было возможно в ранних версиях инфраструктуры . Не переживайте, если вы пока не в курсе последних функ циональных возможностей языка С#: в главе 4 предоставлена сводка по самым важ ным средствам С# для разработки приложений МVС . Межплатформенная природа Предшествующие версии ASP.NET были специфичными для Windows, требуя на стольный компьютер с Windows для написания веб-приложений и сервер Windows для их развертывания и выполнения . Компания Microsoft сделала инфраструктуру ASP.NEТ Core межплатформенной, как в отношении разработки, так и в плане раз вертывания . Продукт .NET Core доступен для различных платформ , включая Linux и OS X/ macOS. и вероятно будет переноситься на другие платформы.
Глава 1. Основы ASP.NET Саге MVC 27 Большая часть разработки приложений ASP.NET Core MVC в ближайшем будущем, скорее всего, будет выполняться с применением Visual Studio, но компания Microsoft также создала межплатформенный инструмент разработки под названием Visual Studio Code , появление которого означает, что разработка ASP.NEТ Core MVC больше не ограничивается Windows. Инфраструктура ASP.NET Core MVC имеет открытый код В отличие от предшествующих платформ для разработки веб-приложений от Microsoft вы можете загрузить исходный код ASP.NEТ Core и ASP.NET Core МVС и даже модифицировать и компилировать его с целью получения собственных версий инфра структур. Это бесценно при отладке кода, обращающегося 1< системному компоненту, когда требуется пошагово вьmолнить его код (и даже почитать исходные комментарии программистов). Это также полезно, если вы создаете усовершенствованный компо нент и хотите посмотреть, какие существуют возможности разработки, или узнать, как действительно работают встроенные компоненты. Исходный код ASP.NET Core и ASP.NET Core МVС доступен для загрузки по адресу: https://github .com/aspnet Что необходимо знать? Чтобы извлечь максимум из этой книги, вы должны быть знакомы с основами раз работки веб-приложений, понимать HTML и CSS, а также иметь практический опыт работы с языком С#. Не беспокойтесь, если детали разработки клиентской стороны, такие как JavaScript. для вас несколько туманны. Основной акцент в книге делается на разработке серверной стороны. и благодаря примерам вы сможете подобрать то, что вам нужно. В главе 4 приводится сводка по наиболее полезным средствам языка С# для разработки MVC, которую вы сочтете удобной, если переходите на последние версии .NET с более раннего выпуска. Какова структура книги? Эта 1\нига разделена на две части, в каждой из которых раскрывается набор свя занных тем. Часть 1. Введение в инфраструктуру ASP.NET Core MVC Книга начинается с помещения ASP.NET Core MVC в контекст разработки. Здесь объяснены преимущества и практическое влияние паттерна МVС, рассмотрен способ, которым инфраструктура ASP.NET Core MVC вписывается в современную разработку веб-приложений, а также описаны инструменты и средства языка С#, необходимые каждому программисту ASP.NEТ Core МVС. В главе 2 мы углубимся в детали и создадим простое веб-приложение, чтобы по лучить представление о том, каковы основные компоненты и строительные блоки, и каким образом они сочетаются друг с другом. Однако большинство материала этой части посвящено разработке проекта под названием SportsStore, посредством которо го демонстрируется реалистичный процесс разработки, начиная с постановки задачи и заканчивая развертыванием, с привлечением основных функциональных возмож ностей ASP. NEТ Core MVC.
28 Часть/ . Введение в инфраструктуру ASP.NET Саге MVC Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC Во второй части объясняется внутренняя работа средств ASP.NET Core MVC, ко торые использовались при построении приложения SportsStore. Будет показано, как работает каждое средство, объяснена его роль и описаны доступные варианты конфи гурирования и настройки. Широкий контекст, представленный в первой части, под робно раскрывается во второй части. Что нового в этом издании? Настоящее издание пересмотрено и расширено с целью описания инфраструкту ры ASP.NET Core МVС, которая отражает полную смену способа поддержки разработ ки веб-приложений компанией Microsoft. Ранние версии МVС Framework были пост роены на основе платформы ASP.NET, которая первоначально создавалась для Web Forms. Это обеспечило преимушество предоставления зрелого фундамента для раз работки MVC, но такими путями, которые допустили просачивание деталей работы Web Forms. Одни средства открывали доступ к внутренним особенностям Web Forms. не имеющим отношения к приложениям MVC, а другие могли порождать непредска зуемые результаты. Кроме того, фундамент ASP.NET предоставлялся с применением сборок, которые были включены в .NET Framework, что означало возможность внесения крупных из менений только при выпуске новой версии .NЕТ. Это стало проблемой, поскольку темп изменения разработки веб-приложений превосходил частоту изменения .NET . Инфраструктура ASP.NET Core MVC полностью переписана с сохранением филосо фии и общего проектного решения, заложенного в ранних версиях, но с обновлени ем АРI-интерфейса для улучшения дизайна и производительности веб-приложений. Инфраструктура ASP.NET Core МVС зависит от платформы ASP.NET Core, которая сама является полной переделкой базового стека веб-технологий: главенствующая роль Web Forms исчезла, а сильная связь с выпусками .NET Framework была разорвана. Вы можете счесть степень изменений тревожной, если имеете опыт работы с МVС 5, но не паникуйте . Лежащие в основе 1юнцепции остались теми же самыми, а многие изменения только выглядят более значительными и сложными, чем есть на самом деле. Во второй части книги приведена сводка по изменениям для каждого крупного средства, которая облегчит переход с МVС 5 на ASP.NET Core MVC. Где можно получить код примеров? Все примеры, рассмотренные в книге, доступны для загрузки на веб-сайте из дательства. Загружаемый файл включает все проекты вместе с их содержимым . Загружать код необязательно, но это самый простой путь для экспериментирования с примерами, а также использования фрагментов кода в собственных проектах. Резюме В этой главе был объяснен контекст, в котором существует инфраструктура ASP.NET Core MVC, и описано ее развитие от Web Forms и первоначальной инфраструктуры ASP.NET MVC Framework. Кроме того, рассматривались преимущества применения ASP.NEТ Core МVС и структура этой книги. В следующей главе вы увидите ASP.NET Core MVC в действии, благодаря простой демонстрации средств, которые проявляют все ее пр еимущества.
ГЛАВА 2 Ваше первое приложение МVС л учший способ оценки инфрастру1<туры, предназначенной для разработки про граммного обеспечения, заключается в том, чтобы приступить непосредственно к ее использованию. В этой главе мы создадим простое приложение ввода данных с применением ASP.NET Core MVC. Мы будем решать эту задачу пошагово, чтобы вы поняли, каким образом строится приложение MVC. Для простоты мы пока опустим некоторые технические подробности . Но не беспокойтесь - если вы только начина ете знакомство с MVC, то узнаете много интересного. Когда что-либо используется без пояснения, то приводится ссылка на главу, в которой находятся все необходимые детали . Установка Visual Studio Настоящая книга опирается на среду Visual Studio 2015, ноторая предлагает все, что понадобится для разработки ASP.NET Core МVС . Мы будем применять бесплат ную редакцию Visual Studio 2015 Community, доступную для загрузки на веб-сайте www . v is ua l st u dio . сот . При установке Visual Studio вы должны удостовериться в том , что отмечен флажок Microsoft Web Developer Tools (Инструменты разработчика веб-приложений от Microsoft). Совет. Среда Visual Studio поддерживает только Windows . Создавать приложения ASP.NET Core MVC можн о и на других платформах, используя продукт Visual Studio Code, но он не предоставляет все инструменты, требуемые для выполнения примеров в этой книге. За подробностями обращайтесь в главу 13. При наличии существующей установленной копии Visual Studio вы должны при менить обновл ение Visual Studio Update 3, которое предоставляет поддержку для ра боты с приложениями ASP.NET Core. В новых установках Visual Studio это обновление применяется автоматически. Оно доступно для загрузки по адресу; ht t p: //go . micr osoft . com/fwl ink/?Linkid=691129 Далее потребуется загрузить и установить платформу .NET Core, устано вочный файл которой находится по адресу h ttps : //go . mic r osoft .c o m / fwli n k /? Link i d = Bl 72 45 . Загружаемый установочный файл .NE T Core обязателен даже для новых установок Visual Studio.
30 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC Финальный шаг предусматривает установку инструмента под названием g i t , до ступного для загрузки по адресу https :/ / git -scm. c om/ downl oad. Продукт Visual Studio включает собственную версию g i t , но она не работает должным образом и приводит к получению непредсказуемых результатов, когда используется другими инструментами. в том числе Bower, который будет описан в главе 6. Во время уста новки g i t удостоверьтесь, что указываете программе установки на необходимость до бавления пути к инструменту в перем енную среды РАТН (рис. 2 . 1). Это позволит ср еде Visual Studio найти новую версию g i t . ~~~-------------~-----------~ ...., Git2.9.0 Setup Adjustin9 your РАТН t!nvironme nt Но•и \\'OIJd you likr to ~ Glt IТom the cOl'f'VМnd line? ------- ------------------- ОUR Glt from GitSash onty 1hlsls lhe sofost choke о. VO<X РАПi ~il notЬe moditied ot <>I. You"111 onlyЬe .ы. tD., .. lhe Git СО111М11d line tools rтom Git В."1. @ Use Gitfrom the Windows Con1m• nd Pro mpt ТNo optfon is ~s.sfe asltonlyadds some minirмl Gitwr~ to YQU" РАТН to avold ciJttering YotX ~t ·дith optiornlf t.ni.X tools . You wil Ье .Ы. tD use Git fтom Ьoth Git в..t1 ш! lhe 1'Г111dом Coomand Prompt . О Use Git ;)hd optiona l UnЬc tools from th e Windows Comm and Pro n1pt Вoth Git .00 lhe optlonal Ur<x tools ~il Ье odded to VO<X РАПi. \Yaming: Th1s w\11 oVl!rrlde Wim!ows tooJs /Jke "fmd" and "юrt•. Only use tl>ls optIOD ifVoU Wld<!t'Stand the impical!on5. C.::_~_JI Next > 1r~~-::J Рис. 2.1 . Добавление пути к g i t в переменную среды РАТН Запустите Visual Studio, выберите в меню Tools (Сервис) пункт Options (Параметры) и перейдите в раздел Projects and SolutionsqExternal Web Tools (Проекты и решения<=> Внешние веб-инструменты), как показано на рис. 2 .2 . Снимите отметку с флажка $ (VS I NSTA LLD I R) \We b \External \ gi t , чтобы блокировать з апуск встроенной в Visual Studio версии g it, и проверьте , отмечен ли флажок$ (РА Т Н ), чтобы мог при м еняться только что установленный инструмент g i t . ! SeatckOpьc.-is(Ctrf•E) .Р lootions of cttrn<tl toot~: ~@;JwG 1 ~?~:~c·:~ons 1 :, 1 :B~~u:~.".~l.d;•:in~dR:~un:;:ng'iА Prci)tcts • nd Solutions G1:nu1I .NЕТ Соц~ P1oje<ts 1 !>tt<Nf\'/ф 1ook' 1 VSOtf;щ lts VC•• Oircclorit.~ VC• ~ P1oj«t Sdtings ,_ •...::.~ P< Oj « ~ L _________ - - - -------, 1 &') S(РАТН] ! 0 S(VSJNSTдt l DIR)\Wt b\Exte 1мf\g il 1 1 i 1 1 1! i 1 '·------------------------........) ! Re5tttoDl!f.щtts j The p•th~ listed obtwewШ Ье s.t11rched when V'кu.гl Studio U1U 3rd·(nrtytcols such IJ G1un~ Gulp, 8owtr, cr npm. -···- -~· -·-~· ---·- ... ------ -· ·- ·- ------- .. -~· ··-··- ~-о_к_~I Г~~~ _J ; Рис. 2.2 . Конфигурирование запуска g i t в Visual Studio
Глава 2. Ваше первое приложение MVC 31 Будущее ASP.NET Core MVC и Visual Studio В Microsoft недооценили, сколько времени потребовалось для создания ASP.NET Core и ASP.NET Core MVC. Первоначально запланированные даты выпусков совпали с выходом Visual Studio 2015, но задержки со стороны ASP.NET означают, что когда писались эти строки, разработка следую щей версии Visual Studio уже началась. Инструментальная поддержка для создания приложений ASP.NET Core MVC может измениться , когда будет выпущена следующая версия Visual Studio . После того как инструменты стабилизируются, я предоставлю обновление с инструкциями, требующими ся для создания примеров приложений. Новости будут доступны на веб-сайте издательства. Создание нового проекта ASP.NET Core MVC Мы собираемся начать с создания нового проекта ASP.NET Core МVС в среде Visual Studio. Выберите в меню File (Файл) пункт NewqProject (СоздатьQ Проект), чтобы открыть диалоговое окно New Project (Новый проект). Перейдя в раздел Templateso::>Visual С#<=:> Web (ШaблoныQVisual С#QВеб) в панели слева, вы увидите шаблон проекта ASP.NET Core Web Applicatioп (.NET Core) (Веб-приложение ASP.NET Core (.NET Core)). Выберите этот тип проекта (рис. 2.3). 1 t> Rtcent i• lnstall<d ! • Template~ ... Visual(# 1 f> Online Name: w.ь .NПCore Android Cloud bll!ns!Ьil1ty .os Reporting S1fvttlight l locatio n: So lut iori ntn,,e: P artylnvitм P.-trty!nvite. ~ ~"!_ЕТ Fr"m~;~ :б~1 - _: . Sort Ьу: \Dtf-;~ij_--_-- -·-~. ") i~ ~ :::: @j A5P.NПWe.bApp1icгtion{.NE1 fr~me.work) VisualC• @ A.SP.NП Core Wtb Applit•tion (.NП fr"mework) Vт~ua/ с~ Click h(r~ 10 по onl•"( ond f1t'!d 1wRIJ.tg. ~мchi~7~tit!d "ТtmplalF:j_Ctrl... E) ~~-!: .- ; Туре: Visua l (# Project ttmplat6 for crtating ASP.N П Core "pplic<1tio ns forWindows, l inux and OS Х щing .NE1 Core. ' AppfКAtion l nslgh ts О Add Appf1c<1t1on lnsights to projкt Optimi:tt ptrformt!11Ct 4nd monitor us<1Qt in vour li<.•e "PPlit"ti on. Help me unde.r~t""d Applka tio n lns19hts P11\'iicySt<11ll!.merit ВloWft". 1 0 Cre.111e. dlltctory fo r.solutiol'\ О Add to Source Contro1 l-------~~,~=~~ ,~~~~~Od-L Сап~ Рис. 2.3 . Шаблон проекта ASP.NET Core Web Applicatioп (.NET Core) в Visual Studio Совет. При выборе шаблона проекта может возникать путаница из-за сильно похожих на званий шаблонов. Шаблон ASP.NET Web Applicatioп (.NET Framework) (Веб-приложение ASP.NET (.NET Framework)) предназначен для создания проектов с использованием унас ледованных версий ASP.NET и MVC Framework, которые предшествовали ASP.NET Core . Остальные два шаблона позволяют создавать приложения ASP.NET Core и отличаются применяемой исполняющей средой, предлагая на выбор .NЕТ Framework или .NET Core. Разница между ними объясняется в главе 6, но повсеместно в книге используется вари ант .NET Core, поэтому для получения идентичных результатов при работе с примерами приложений вы должны выбирать именно его.
32 Часть 1. Введение в инфраструктуру ASP.NET Core MVC В пол е Name (Имя) для нового проекта введите P a r ty i nv i tes и удостоверь тесь в том , что флажок Add Application lnsights to Project (Добавить в проект службу Application Insights) не отмечен (см . рис. 2.3). Для продолжения щелкните на кнопке ОК. Откроется еще одно диалоговое окно (рис. 2.4), предлагающее установить началь ное содержимое проекта . г-·-·--·------·-----·------· · ·-1 1 iMP; CoreTcrn ; 1 . En1 pty ltleb API 1 А proje ct template for crto ting an ASP.NEТ Соге app lication with exll mpfe ASP.NEТ MVC Vi~s and Cont fo!lers. This temp late сап ~lso Ье used for RESТfuf НТТР service:. · 1 · 11 1 J @haпgt'AUt~ 1{ _ Authentication: No Au thentlc~tion ---- -1 1 ------· - ·--·- 1<;;, MicrcкoftAzu re 1 \G О Host iп thecloud j 1 ~1~.,.-г:з uo ~ I L ~ Рис. 2.4 . Выбор начальной конфигурации проекта Для шаблона ASP.NET Core Web Application (.NET Core) доступны три вариант а , каждый из которых приводит к созданию проекта с отличающимся начальным содер жимым. Для целей данной главы выберите вариант Web Application (Веб-приложение), который настроит приложение MVC с заранее определенным содержимым , чтобы не медленно приступить к разработке. На заметку! Это единственная глава, в которой применяется шаблон проекта Web Application. Мне не нравится пользоваться заранее определенными шаблонами , поскольку они пот ворствуют интерпретации ряда важных средств наподобие аутентификации как черны х ящиков . Моя цель в настоящей книге - предоставить вам достаточный объем знаний для понимания и управления каждым аспектом приложения MVC, так что в оставшихся мате риалах книги применяется шаблон Empty (Пустой). Текущая глава посвящена быстрому началу процесса разработки , для чего хорошо подходит шаблон Web Application. Щелкните на кнопке Change Authentication (Изменить аутентификацию) и проверь те, что выбран переключатель No Authentication (Аутентификация отсутствует), 1<ак показано на рис. 2.5 . Данный проект не требует какой-либо аутентификации, а в гла вах 28-30 объясняется , как защитить приложения ASP. NEТ. Щелкните на кнопке ОК, чтобы закрыть диалоговое окно Change Authentication (Изменение аутентификации). Удостоверьтесь в том , что флажок Host in the Cloud (Разместить в облаке) не отмечен и затем щелкните на кнопке ОК для создания про екта Par t yi n v i t e s. После того как среда Visual Studio создала проект, вы увидите в окне Solution Explorer (Проводник решения) множество файлов и папок (рис . 2.6). Это стандартная структура для проекта МVС, созданного с использованием шаблона Web Application , и вскоре вы узнаете на з начение каждого файла и папки , которые были созданы Visual Studio.
Глава 2. Ваше первое приложение MVC 33 For applic•tions that don 't requi re any use r • uthentic• tio". @ No Auth•ntication О lndividual User Accounts О Work And Scl>ool Accou nt s О Windows Authentication г-о~_:] \ Cьncel Рис. 2.5. Выбор настроек аутентификации Теперь можете запустить приложение, выбрав в меню Debug (Отладка) пункт Start Debugging (Запустить отладку); если появит- ся запрос на включение отладки, тогда просто щелкните на кнопке ОК. Среда Visual Studio скомпилирует приложение, с помощью сервера приложений IIS Express запусти т его и откро ет окно веб-браузера для запроса содержимого приложения. Результат показан на рис. 2.7. Когда среда Visual Studio создает проект с при менением шаблона Web Application, она добавля ет базовый код и содержимое, которое вы видите после запуска приложения. В оставшейся части этой главы мы будем заменять такое содержимое, чтобы создать простое приложение МVС. По завершении не забудьте остановить от ладку, закрыв оюю браузера или вернувшись в Visual Studio и выбрав в меню Debug пункт Stop Debugging (Остановить отлад~<у). Как было только что показано, для отображе ния проекта среда Visual Studio открывает окно браузера. Вы можете выбрать любой установ ленный браузер , щелкнув на кнопке со стрелкой Sol uti on Explorer • 1:1Х .~ с' ~ f''"Ф •·4'~·@ Wi 1,,, -- . ..:.,;;,,,·"- ··' ·' .,;:;;;i~.-:. .~: \о...., •·.1, - ._, .se~5~~l u~on ~plorer (Ctrt,:;~ l4J Solution 'Partylnvites' ( 1 project} " ;,,. , . Solution Jtem s /;J global.json ..; ·~1 src ~ ,1- Properties ~ •·• Referenc e.s •·• Depe.ndencies ~ ;.t; Cont roll ers ~ ~ Vieo.vs lJ appsettings.json lJ bundleconfigjso n С" Program.cs ~ lJ project.json .D Project_Readme.html с• Startup.cs у') web. co nfig Р· Рис. 2.6. Начальная структура файлов и папок проекта ASP.NET Саге MVC правее кнопки llS Express в панели инструментов и выбрав нужный вариант из спис ка в меню Web Browser (Веб-браузер), как показано на рис. 2.8. В дальнейшем во всех примерах в книге будет использоваться браузер Google Chrome или Google Chrome Canary, но вы можете применять любой современный бра узер, включая Microsoft Edge и последние версии Internet Explorer. Добавление первого контроллера В рамках паттерна MVC входящие запросы обрабатываются контроллерами. В ASP.NET Core MVC контроллеры - это просто классы С# (обычно унаследованные от класса Microsoft. AspNetCore . Mvc. Controlle r, являющегося встроенным ба зовым классом контроллера МVС).
34 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Appl ication uses How to Overvi ew Run & Dep loy • ~;а 1>о191-? UWЧ ASl'I Nfl CCrtMVC • &.-l!l<m.Ul,}f)IП',j(~.$4dil ...... • T h fm!ng 11~ И«щщр • AddaQ.imrt'Jl!<'or....Ю'/- ' A'1':-."'lapps.;it!l>l)lflщ\/.glil'l<I ......,.."lln~ · M~-...Uwl~ll"l 'lf.l'f.l>I !.l1~ · \.lw~'!/.IU~·~ • -'4. Jpat kog' !'>u Plg tll>O ~ • A(l('ltl<lrllpN~u""'JI~ o ljjf\l('f~fl''"'l.ЦloJ ""JI)( ptOd!,ГЬIК\Wtlr.'JIИ'I( • Глrщ.Ш:.lt.>."4,.,.......iilwl~•t" ASPtlП ~ • Futnr.n1>00!ЬolNil 'Nf J C:.,." ~,,St.!1rup.,11::!~ • .,. · ~-iti~ ·~ 0 (.l~t-"'~•wr.t • ri.,.y,4<\p(">(j..!l<>:nr~f'l>,1'.IO'.~ • r~.t dn"3 f"\ ,"!' l'"° (!t, .'Q ,l/N >~"' tл1 · 11-"'У"'" '""" • Rllft l(ll'll•&ur. ~.\ ~fТtr~ /INl"ll'.r" • l'ld-Jh •<J !~/: .w.n.ww '"" '1l0 1t!l · l'iwfytпrrtн Test. Рис. 2. 7 . Выролнение примера проекта An•lyz• Wi ndow H•lp .-'------->=~. • 1 ~..., L__·_ ......... ll SExp ress llS Express Partyf nvit•s Web Browш (G oog l• Cl11ome) Google Chro me Google Chrome C~n~ry ~ lntern et Exp lorer Microsoft Edge Рис. 2.8. Выбор браузера Каждый открытый метод в контроллере называется методом действия, что оз начает возможность его вызова из веб-среды через некоторый URL для выполнения действия. В соответствии с соглашением MVC контроллеры помещаются в папку Controllers , автоматически создаваемую Visual Studio при настрой1<е проекта. Совет. Вы вовсе не обязаны соблюдать указанное или большинство других соглашений MVC, но рекомендуется его придерживаться - и не в последнюю очередь потому, что это по может уяснить примеры , приведенные в настоящей книге . Среда VisuaJ Studio добавляет в проект класс стандартного контроллера, который вы можете увидеть , раскрыв папку Control le r s в окне Solution Explorer. Файл назы вается Ho meCont r ol l e r. cs . Файлы классов контроллеров имеют имя, завершающе-
Глава 2. Ваше nервое nриложение MVC 35 еся словом Control ler, т. е. в файле Home Controll e r. c s содержится код контролле ра по имени Home - стандартного контроллера. используемого в приложениях МVС. Щелкните на имени файла Ho meCont r o ll er . cs в окне Solution Explorer, чтобы среда Visual Studio открыла е го для редактирования. Вы увидите код С# , приведенный в ли стинге 2.1. Листинг 2.1 . Первоначальное содержимое файла HomeController. cs из папки Controllers using System ; using System.Col lections.Gene ric; using System . Li nq; using S ystem . Thread ing . Tasks ; using Microsoft . AspNe t Core . Mvc ; namespace Pa r tyinvites .C ontrol l e r s puЫic class HomeCon tr olle r : Co ntr o ller puЫic IActionRes ult I ndex() { return View () ; puЫic IAct i onResult About() { ViewData ["Message " ] = " You r appl ication description pag e ."; return View() ; puЫic IActionRes ul t Contact ( ) { ViewData [ "Message"] = " Your c ont act page . "; return View() ; puЫic IActionResu lt Error() { return Vi ew() ; Замените код в файле HomeContro l ler . c s кодом, показанным в листинг е 2.2 . Эде сь были удалены все методы кроме одного , у которого изменен возвращаемый тип и его р еали з ация, а также удалены операторы u s i ng для неиспользуемых про странств имен. Листинг 2.2 . Изменение файла HomeController. cs using Microsof t. AspNetCore .M vc ; n amespace Partyi nvi te s.Cont r o ller s puЫic class HomeControl ler : Cont r oller puЫic stri ng Ind ex() { return "Hello World";
36 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Изменения не приводят к особо впечатляющим результатам, но их вполне доста точно для хорошей демонстрации. Метод по имени I n dex () изменен так. что теперь он возвращает строку "H e llo Wor l d ". Снова запустите проект, выбрав пункт Start Debuggiпg из меню Debug в Visual Studio. Совет. Если вы оставили в функционирующем состоянии приложение из предыдущего разде ла , то выберите в меню Debug пункт Restart (Перезапустить) или при желании пункт Stop Debuggiпg и затем Start Debuggiпg. Браузер сделает НТТР-запрос серверу. Стандартная конфиrурация MVC предусмат ривает, что данный запрос будет обрабатываться с применением метода Index () , на зываемого методом действия или просто действием, а результат, полученный из этого метода , будет отправлен обратно браузеру (рис. 2 .9). 1 l0<a!host:S7628 Х lo World 1 l: _ _?_@l~c~~~~~~~-8- ·~-=-=--~=-=---~-=--= _~-j _ -----------·------- __, Рис. 2.9 . Вывод из метода действия Совет. Обратите внимание, что среда Visual Studio направляет браузер на порт 57628. Внутри URL, который будет запрашивать ваш браузер, почти наверняка будет присутствовать другой номер порта, т. к. Visual Studio выделяет произвольный порт при создании проекта . Если вы заглянете в область уведомлений панели задач Windows, то найдете там значок для llS Express. Этот значок представляет усеченную версию полного сервера приложе ний llS, которая входит в состав Visual Studio и используется для доставки содержимого и служб ASP.NET во время разработки. Развертывание проекта MVC в производственной среде будет описано в главе 12 . Понятие маршруто в В дополнение к моделям , представлениям и контроллерам в приложениях MVC применяется система маршрутизации ASP.NEТ, которая определяет, как URL отоб ражаются на контроллеры и действия. Маршрут - это правило, которо е использу ется для решения о том, как обрабатывать запрос. Когда среда Visual Studio созда ет проект MVC, она добавляет ряд стандартных маршрутов , выступающих в качеств е начальных. Можно запрашивать любой из следующих URL, и они будут направлены на действие Index класса H oтeC ont r o lle r : ./ • /Ноте • / Hoтe/Index Таким образом, когда брауз ер запрашивает http : //ваш- сайт/ или http: //ваш_ сайт/ Н оте , он получает вывод из метода I ndex () класса H oтeController. Можете
Глава 2. Ваше первое приложение MVC 37 опробовать зто самостоятельно, изменив URL в браузере. В настоящий момент он бу дет выглядеть как http: //localhost : 57628/, но представляющая порт часть мо жет быть другой . Если вы добавите к URL порцию /Home или /Home/Index и нажмете клавишу <Enter>. то получите от приложения MVC тот же самый результат - строку "Hello, world". Это хороший пример получения выгоды от соблюдения соглашений, поддержива емых ASP.NET Core MVC. В данном случае соглашение заключается в том, что имеет ся контроллер по имени HomeController. который будет служить стартовой точкой приложения MVC. Стандартная конфигурация. 1<0торую V!sual Stud!o создает для но вого проекта, предполагает. что мы будем следовать этому соглашению. И поскольку мы действительно соблюдаем соглашение, мы автоматически получаем поддержку всех URL из приведенного выше списка. Если не следовать соглашению, то конфигу рацию пришлось бы модифицировать для указания на контроллер, созданный вза мен стандартного. В рассматриваемом простом примере стандартной конфигурации вполне достаточно. Визуализация веб-страниц Выводом предыдущего примера была не НТМL-разметка. а просто строка "Hel lo Wor ld ". Чтобы сгенерировать НТМL-ответ на запрос браузера, понадобится создать представление, которое сообщает MVC. каким образом генерировать ответ для запро са, поступившего из браузера. Создание и визуализация представления Прежде всего, необходимо модифицировать метод действия Index () , как показа но в листинге 2 .3 . Для простоты восприятия в этом и во всех будущих листингах из менения выделяются полужирным. Листинг 2.3. Изменение контроллера для визуализации представления в файле HomeController.cs using Microsoft . AspNetCore . Mvc; namespace Partyinvites.Controllers puЫic class HomeController : Controller puЬlic ViewResult Index () return View("MyView"); Возвращая из метода действия объект ViewResu l t, мы инструктируем МVС о ви зуализации представления. Экземпляр ViewResul t создается посредством вызова метода View () с указанием имени представления. которое должно применяться, т.е. MyView. Запустив приложение, можно заметить, что инфраструктура MVC пытается найти представление, кш< отражено в сообщении об ошибке на рис. 2 . 10. Сообщение об ошибке исключительно полезно. Оно не только объясняет, что ин фраструктура MVC не смогла найти представление, указанное для метода действия, но также показывает, где производился поиск. Представления хранятся в подпапках внутри папки Views .
38 Часть 1. Введение в инфраструктуру ASP.NET Core MVC lrite-rnal Serv~ Erro r Х :1 ---- --··· •1 1 An Linhandled exception оссшгеd while pro cessing the reqL1est . f lnvaliclOpeгatio'lExcep<ion: Т11е viev1 'MyViev/ v1as 110t fou r. d . Th e fo i/01·1ing !ocat ions were searc f1 ed : . /Views/Home/MyView.cshtinl i Niews/Si1ared/MyVie\\1.CSl-1t •nl f Eпiun:-su,cessful 1 В Q\Jery Cookies Heиders 1.- ···---- - . Рис . 2.1 О. Инфраструктура MVC пытается найти представление ii1 i 1 :i Например, представления, которые связаны с контроллером Home , содержатся в папке по имени V i ew s / Ho me . Представления, не являющиеся специфическими для отдельного контроллера, хранятся в папке под названием V ie ws / Sha r ed . Среда Visual Studio создает папки Ho me и Sh a red автоматически, когда используется шаб лон Web Application, и в рамках начальной подготовки проекта помещает в них не сколько представлений-заполнителей. Чтобы создать представление , щелкните правой кнопкой мыши на папке Home внутри V i ews в окне Solution Explorer и выберите в контекстном меню пункт Add~New ltem (Добавить~Новый элемент) . Среда Visual Studio предложит список шаблонов эле ментов. Выберите категорию ASP.NET в панели слева и затем укажите элемент MVC View Page (Страница представления МVС) в центральной панели (рис. 2.11) . ·~ 1nтlle<1 ~J>.NEТ1 Client-side Code f> Onl ine Nomt: MyVi~.csht1n l Sort Ь у: ~•foult i SNrcl1 lnst4JJed Tem~l;tt s ~Ct r!•E} Р· .j MVC (ontroller Clas~ ASP.NEТ ~ Туре: ASP.N H р MVCView Р•9е W!:!b АР\ Contioller Class А5Р.NЕТ MVC Viow Start Page ASP.NEТ ~ ~·. MVC View lmport:; Page ASP.NH ~·~ R.!zor Tag HeJper д5Р.N ЕТ Рис. 2 . 11 . Создание представления Совет. В папке V i e ws уже есть несколько файлов, которые были добавлены Visual Studio с целью предоставления начальн ого содержим ого , показанного на рис. 2.7 . Можете проиг норировать эти файлы .
Глава 2. Ваше первое прило жение MVC 39 Введите в поле Name (Имя) имя MyVi ew. cshtml и щелкните на кнопке Add (Доб авить) для создания представления. Среда VisuaJ Studio создаст файл View s/ Home/MyView . c sh tml и откроет его для редактирования. Начальное содержимое файла представления - это просто ряд комментариев и заполнитель. Замените его содержимым, приведенным в листинге 2 .4 . Совет. Довольно легко создать файл представления не в той папке. Если в итоге вы не по лучили файл по имени MyView . cshtml в папке Views/Home , тогда удалите созданный файл и попробуйте создать заново . Листинг 2.4. Замена содержимого файла МyView. cshtml из папки Views/Horne @{ Layout = nu ll; < ! DOCTYP E html> <html> <head> <meta name= " v iewport " content= " width=device-width" /> <title>Index</title> </head> <body> <div> Hello World (from the view) </div> </body> </html> Теперь файл представления содержит главным образо м НТМL-разметку. Исклю чением является часть, которая имеет следующий вид: @{ Layout null; Такое выражение будет интерпретироваться механизмом визуализации Razor, который обрабатывает содержимое представлений и генерирует НТМL-р азметку, отправляемую браузеру. Показанное выше простое выражение Razor сообщает ме ханизму Razor о том, что компоновка не применяется; это похоже на шаблон для НТМL-разметки, который посылается браузеру (и будет описан в главе 5). Мы пока проигнорируем механизм Razor и возвратимся к нему позже. Чтобы увидеть создан ное представление, выберите в меню Debug пункт Start Debugging для запуска прило жения. Должен получиться результат, приведенный на рис. 2.12 . При первом редактировании метод действия I ndex () возвращал строковое зна чение . Это означало, что инфраструктура MVC всего лишь передавала брауз еру стро ковое значение в том виде, как есть. Теперь. когда метод Index () возвращает объект ViewResul t, инф раструктура MVC визуализирует представление и возвращает сгене рированную НТМL-разметку. Мы сообщили инфраструктуре МVС о том, какое пред ставление должно использоваться, поэтому с помощью соглашения об именовании
40 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC она автоматически выполнила его поиск. Соглашение предполагает, что имя файла представления совпадает с именем метода действия, а файл представления хранится в папке, названной по имени контроллера: /Views/Home/MyView . cshtml . Кроме строк и объектов ViewResul t методы действий могут возвращать другие ре зультаты . Например, если мы возвращаем объект RedirectResul t, то браузер будет перенаправлен на другой URL. Если мы возвращаем объект HttpUnauthorizedResul t , то вынуждаем пользователя войти в систему. Все вместе такие объекты называются результатами действий. Система результатов действий позволяет инкапсулировать и повторно использовать часто встречающиеся ответы в действиях. В главе 17 мы рассмотрим их подробнее и продемонстрируем разные способы их применения. Hello Wo1·id (fl'OШ tl1e vie\v) Рис. 2.12. Тестирование представления Добавление динамического вывода Весь смысл платформы для разработки веб-приложений состоит в конструирова нии и отображении динамического вывода. В рамках МVС работа контролле ра заклю чается в подготовке данных и передаче их представлению, которое отвечает за их визуализацию в виде НТМL-разметки. Один из способов передачи данных из контроллера в представление предусмат ривает использование объекта ViewBag , который является членом базового класса Controller. По существу ViewBag - это динамический объект, в котором можно устанавливать произвольные свойства, делая их значения доступными в любом визу ализируемом далее представлении. В листинге 2.5 демонстрируется передача таким способом простых динамических данных в файле HomeController. cs . Листинг 2.5. Установка данных представления в HomeController. cs using System; using Mic r osoft. AspNetCore .Mv c ; namespace Partyinvites . Contro llers puЫic class HomeCon t roller : Con tro ller puЫic ViewResu l t Index() { int hour = DateTime.Now.Hour; ViewBag. Greeting = hour < 12 ? "Good Morning" return View( "MyView " ) ; "Good Afternoon";
Глава 2. Ваше первое приложение MVC 41 Данные для представления предоставляются во время присваивания значения свойству ViewBag . Greeting. Свойство Greeting не существует вплоть до момента, когда ему присваивается значение - это позволяет передавать данные из контроллера в представление в свободной и гибкой манере, без необходимости в предварительном определении классов. Чтобы получить значение данных, необходимо еще раз сослаться на свойство ViewBag. Greeting, но уже в представлении, как показано в листинге 2.6, содержащем изменение, которое было внесено в файл MyView. cshtml . Листинг 2.6. Извлечение значения данных ViewBag в файле МyView. cshtml @{ Layout = null; <!DOCTYPE html> <html> <head> <meta name =" viewpor t" content="width=device-width" /> <tit l e> I ndex</title> </head> <body> <div> @ViewBag.Greeting World (from the view) </div> </body> </html> Добавленный в листинге фрагмент - это выражение Razor, которое оценивается, когда MVC применяет представление для генерации ответа. Вызов метода View () в методе I ndex ( ) контроллера приводит к тому, что МVС находит файл представления MyView . cshtml и запрашивает у механизма визуализации Razor синтаксический анализ содержимого этого файла. Механизм Razor ищет выражения, подобные до бавленному в листинге 2 .6, и обрабатывает их. В рассматриваемом примере обработ ка выражения означает вставку в представление значения, которое было присвоено свойству ViewBag . Greeting в методе действия. Выбор для свойства имени Greeting не диктуется никакими особыми соображе ниями. Его можно было бы заменить любым другим именем, и все работало бы точно так же при условии, что имя, используемое в контроллере, совпадает с именем, ко торое применяется в представлении. Присваивая значения более чем одному свойс тву, можно пер едавать из контроллера в представление множество значений данных. После запуска проекта можно увидеть результат внесенных изменений (рис. 2.13). Рис. 2.13. Динамический ответ, сгенерированный MVC
42 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Создание простого приложения для ввода данных В оставшихся разделах этой главы будут исследованы другие базовые функцио нальные средства MVC за счет построения простого приложения для ввода данных . В этом разделе мы собираемся несколько увеличить темп изложения. Целью является демонстрация инфраструктуры МVС в действии, поэтому некоторые объяснения того . что происходит "за кулисами", будут пропущены. Однако не беспокойтесь - мы вер немся к подробному обсуждению этих тем в последующих главах. Предварительная настройка Представьте себе. что ваша подруга решила организовать вечеринку в канун ново го года и попросила создать веб-приложение, которое позволяет приглашенным отве тить на приглашение по электронной почте. Она высказала пожелание относительно четырех основных средств, которые перечислены ниже: • домашняя страница. отображающая информацию о вечеринке; • форма , которая может использоваться для ответа на приглаш е ние (repoпdez s'il vous plalt- RSVP); • проверка достоверности для формы RSVP, которая будет отображать страницу с выражением благодарности за внимание; • итоговая страница. которая показывает, кто собирается прийти на вечеринку . В последующих разделах мы достроим проект МVС, созданный в начале главы , и добавим в него перечисленные выше средства. Первый пункт можно убрать из спис ка. применив то, что было показано ранее - добавить НТМL-разметку с подробной информацией о вечеринке в существующее представление. В листинге 2. 7 приведено содержимое файла Views/Home/MyView. cshtml с внесенными дополнениями. Листинг 2.7 . Отображение подробностей о вечеринке в файле МyView. cshtml @{ Layout = null ; <!DOCTYPE html> <html > <head> <meta name="viewport" content= "w i d th=de v ice-w idth" /> <title>Index</title> </head> <body> <div> @ViewBag .Gr eeting World (from the view) <p>We' re going to have an exciting party . <br /> (То do: sell i t better. Add pictures or something.) </р> </div> </body> </html >
Глава 2. Ваше первое приложение MVC 43 Мы двигаемся в верном направлении. Если запустить приложение, выбрав в меню Debug пункт Start Debugging . то отобразятся подробности о вечеринке - точнее, за полнитель для подробностей, но идея должна быть понятной (рис. 2.14). 1 Goocl Moш.i11g \Vorld (fi·oщ tl1e Yie,v) \Ve're goiдg to lшУе а11 exc iti.11g party. l(То do' "11 i1 bon« Add piono" 01 •onwd1iug._ ) ______, Ри с. 2.14 . Добавление информации о вечеринке к НТМL-разметке представления Проектирование модели данных Буква "М" в аббревиатуре MVC обозначает тodel (модель) , и она является самой важной частью приложения. Модель - это представление реальных объектов, про цессов и правил. которые определяют сферу приложения, известную как предметная область. Модель , которую часто называют моделью предметной области, содержит объекты С# (или объекты предметной области), образующие "вселенную" приложе ния, и методы, позволяющие манипулировать ими. Представления и контроллеры открывают доступ клиентам к предметной области в согласованной манере, и любое корректно разработанное приложение МVС начинается с хорошо спроектированной модели, которая затем служит центральным узлом при добавлении контроллеров и представлений . Для про екта Partylnvi tes сложная модель не требуется, поскольку приложение совсем простое, и нужно создать только один класс предметной области, который по лучит имя GuestRespo nse. Этот объект будет отвечать за хранение, проверку досто верности и подтверждение ответа на приглашение (RSVP). По соглашению MVC классы, которые образуют модель. помещаются в папку по имени Models . Чтобы создать эту папку. щелкните правой кнопкой мыши на проекте Partylnvi tes (элемент. содержащий папки Controllers и Views). выберите в кон текстном меню пункт Add~New Folder (ДобавитьqНовая папка) и укажите Models в качестве имени папки. На заметку! Вы не сможете установить имя новой папки , если приложение все еще фун кционирует. Выберите в меню Debug пункт Stop Debugging , щелкните правой кнопкой мыши на элементе NewFolder , который добавился в окно Solution Explorer, выберите в контекстном меню пун кт Rename (Переименовать) и измените имя на Mode ls. Для создания файла масса щелкните правой кнопкой мыши на папке Models в окне Solution Explorer и выберите в контекстном меню пункт AddqClass (ДобавитьqКласс) . Введите GuestResponse . cs для имени нового класса и щелкните на кнопке Add (Добавить). Приведите содержимое нового файла класса к виду , показанному в лис тинге 2.8 .
44 Часть 1. Введение в инфрастру к туру ASP.NET Core MVC Листинг 2.8 . Класс предметной области GuestResponse, определенный в файле Gues tResponse . cs внутри папки Model в namespace Partyinvites . Models { puЫic class GuestResponse { puЫic string Name { get ; set; puЬlic string Email { get; set ; } puЬlic string Phone { get; set; } puЫic bool? WillAttend { get; set; Совет. Вы могли заметить, что свойство WillAttend имеет тип bool, допускающий null, т.е. оно может принимать значение true, false или null . Обоснование этого будет приведено в разделе "Добавление проверки достоверности" далее в главе. Создание второго действия и строго типизированного представления Одной из целей разрабатываемого приложения является включение формы RSVP, а это означает необходимость определения метода действия, который сможет полу чать запросы к ней. В единственном классе контроллера может определяться множес тво методов действий, а по соглашению связанные действия группируются вместе в одном контроллере. В листинге 2.9 иллюстрируется добавление нового метода дейс твия к контроллеру Ноте. Листинг 2.9 . Добавление метода действия в файле HomeController. cshtml using System ; using Microsoft . AspNetCore . Mvc ; namespace Partyinvites . Controllers puЫic class HomeController : Controller puЬlic ViewResult Index() { int hour = DateTime . Now.Hour ; ViewBag. Greeting = hour < 12 ? " Good Morning " return View( "MyView " ); puЫic ViewResul t RsvpForm () return View () ; "G ood Afternoon"; Метод действия RsvpForm () вызывает метод View () без аргументов, что сообща ет инфраструктуре МVС о необходимости визуализации стандартного пр едставления, связанного с этим методом действия, которым будет представление с таким же име нем, как у метода действия (RsvpForm . cshtml в данном случае).
Глава 2. Ваше первое приложение MVC 45 Щелкните правой кнопкой мыши на папке Views внутри Ho me и выберите в кон текстном меню пункт Add<=:> New ltem (Добавить<=:> Новый элемент). Выберите шаб лон MVC View Page (Страница представления MVC) из категории ASP.NET, укажи те RsvpForm . cshtml в к ачестве имени нового файла и щелкните на кнопке Add (Добавить), чтобы создать файл. Приведите содержимое нового файла в соответствие с листингом 2.10. Листинг 2.10. Содержимое файла RsvpForm. cshtml из папки Views/Home @model Partyinvi tes . Mode ls. Gue stRespon se @{ Layout = null; < !DOCTYPE html > <html> <head> <meta name="viewport" cont ent="width=devi ce- wi dth" /> <title>RsvpForm</titl e> </ head> <body> <div> This is the RsvpForm . c s html View </di v> </body> </html> Содержимое состоит в основном из НТМL-разметки , но с добавлением Rаzоr выражения @model , которое используется для создания строго mипизирован.н.ого представления. Строго типизированное представление предназначено для визуали зации специфического типа модели, и если указан жела емый тип (в данном случае класс GuestResponse из пространства имен Par t yinvite s . Models), то MVC может создать ряд удобных сокращений, чтобы сделать его проще. Вскоре мы задействуем преимущество характеристики строгой типизации. Чтобы протестировать новый метод действия и его представление , запустите при ложение, выбрав в меню Debug пункт Start Debugging , и с помощью брауз ера перей дите на URL вида / Home/RsvpForm. Инфраструктура МVС применит описанное ранее соглашение об именовании для направления запроса методу действия RsvpForm () , определенному в контролле ре Ho me . Этот метод действия указывает MVC о том, что должно визуализировать ся стандартное представление, которое посредством еще одного применения того же соглашения об именовании визуализирует Rs vpFo rm. cs hm l из папки Views/ Home . Результат показан на рис. 2.15. 1· RsvpForm х г---·-----"·~-·-- ~ С Lq:> loca l ho~t 5}628/Home/RsvpFo r m.___ [ " ; ;, ;he Rs-;;pi~''" oshtщl Vi_e,_:s .' ---- --- · Рис. 2.15 . Визуализация второго представления
46 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Ссылка на методы действий Нам необходимо создать в представлении MyView ссылку, чтобы гости могли ви деть представление Rsvp Form без обязательного знания URL, который указывает на специфический метод действия (листинг 2.11). Листинг 2.11. Добавление ссылки на форму RSVP в файле МyView. cshtml @{ Layout = null; <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Index</title> </head> <body> <div> @ViewBag.Greeting World (from the view) <p>W e're going to have an exciting party.<br /> (То do: sell it better. Add pictures or something.) </р> <а asp-action="RsvpForm">RSVP Now</a> </div> </body> </html> В листинге 2.11 добавлен элемент а. который имеет атрибут asp - action. Данный атрибут является примером атрибута дескрuпторного вспомогательного класса, т.е . инструкцией Razor, которая будет вьшолнена, когда представление визуализируется . Атрибут asp-action - это инструкция по добавлению к элементу а атрибута href, содержащего URL для метода действия. Работа дескрипторных вспомогательных клас сов объясняется в главах 24, 25 и 26, а пока достаточно знать, что asp-action - про стейший вид атрибута дескрипторного вспомогательного класса для элементов а. Он указывает Razor на необходимость вставки URL для метода действия, определенного в том же контроллере, для которого визуализируется текушее представление. Запустив проект, можно увидеть ссылку, которую создал вспомогательный класс (рис. 2.16). 1 lndex j Rsv pform Х _____ '? i9?~°.~~~~~-;~3___ L Пu $ is 111 ~ RsYpF011n.c slitшl \ ' ie\\· ~--~- j' ~---- Рис. 2.16. Ссылка на метод действия
Глава 2. Ваше первое приложение MVC 47 После запуска приложения наведите курсор мыши на ссылку RSVP Now (Ответить на приглашение) в окне браузера. Вы заметите, что ссылка указывает на следующий URL (возможно , вашему проекту Visual Studio назначит другой номер порта): http://localhost : 57628/Home/R s vpForm Здесь требуется соблюдать один важный принцип: вы должны использовать средс тва , предлагае мые МVС для генерации URL, а не жестко кодировать их в своих пред ставлениях. Когда вспомогательный класс создает атрибут href для элемента а , он инсп е ктирует конфигурацию приложения, чтобы выяснить , к аким должен быть URL. В итоге появляется возможность изменять конфигурацию приложения для поддер жки разных форматов URL без необходимости в обновлении каких-либо пр едставлений . Особенности работы этого рассматриваются в главе 15. Построение формы Теперь, когда строго типизированное представление создано и достижимо из представ ления I ndex, займемся подгонкой содержимого файла RsvpForm . c s html, чтобы превра тить его в НТМL-форму для редактирования объектов Gu e stResponse (листинг 2.12) . Листинг 2.12. Создание представления в виде формы в файле RsvpForm. cshtml @model Pa r tyinvites . Models . GuestRe sponse @{ Layout = null ; < !DOCTYPE html > <html> <head> <meta name= " viewpo r t " content= " width=devi ce - wi dth " /> <title>RsvpForm</title> </head> <body> <form asp-action="RsvpForm" method="post"> <р> <label asp-for="Name">Your name:</label> <input asp-for="Name" /> </р> <р> <laЬel asp-for="Email">Your email:</laЬel> <input asp-for="Email" /> </р> <р> <laЬel asp-for="Phone">Your phone:</label> <input asp-for="Phone" /> </р> <р> <laЬel>Will you attend?</laЬel> <select asp-for="WillAttend"> <option value=" ">Choose an option</option> <option value="true">Yes, I '11 Ье there</option> <option value="false">No, I can•t come</option> </select> </р> <button type="submit">SuЬmit RSVP</button> </form> </body> </html>
48 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Для каждого свойства IUiacca модели GuestResponse определены элементы label и input (или элемент select в случае свойства Wi llAttend}. Каждый элемент ассо циирован со свойством модели с применением еще одного атрибута дескрипторно го вспомогательного IUiacca - asp - for. Атрибуты дескрипторных вспомогательных IUiaccoв конфигурируют элементы, чтобы привязать их к объекту модели. Вот пример НТМL-разметки , которую генерируют дескрипторные вспомогательные IUiaccы для от правки браузеру: <р> <labe l for="Name">Your name : </label> <input type="text" id="Name" name="Name" value=""> </р> Атрибут asp-for в элементе label устанавливает значение атрибута for. Атрибут asp - for в элементе inp ut устанавливает атрибуты id и name . В данный момент это не выглядит особенно полезным , но по мере определения прикладной функциональ ности вы увидите , что ассоциирование элементов со свойством модели предлагает дополнительные преимущества. Более непосредственный результат дает атрибут asp - action в элементе form, который использует конфигурацию маршрутизации URL приложения для установки атрибута action в URL, нацеленный на специфический метод действия, например : <form method= "post " act i on=" /Hom e /RsvpForm " > Как и в случае атрибута дескрипторного вспомогательного класса, примененного к элементу а, преимущество такого подхода заIUiючается в том, что вы можете изме нять систему URL, используемую приложением, и содержимое , которое ге нерируется дескрипторными вспомогательными классами, автоматически отразит изменения. Форму можно увидеть, запустив приложение и щел кнув на ссылке RSVP Now (рис. 2.17). @-------"·--·-·-· ·----- · ·------------·-"". ~ С localhost :57628/ Home/RsvpForm ~ ------------------------ Уош 11щnе: ; [ .""."---·-··----· --· "." --- ·--·- ·-···"·- -·-·- -·-_ J Уонr eшail : L 1 ______ ,_"____ .___, Уонr pl1011e: с--------1 \\ ill уон a"end? rch~~e ;~;; ~ ·---· --- ---- ----- --· Рис. 2.17 . Добавление НТМL-формы к приложени ю
Глава 2. Ваше первое приложение MVC 49 П олучение данных формы Мы пока еще не указали инфрастру~<туре MVC , что должно быть сделано, когда форма отправляется серверу. В нынешнем состоянии приложения щелчок на кноп ке Submit RSVP (Отправить RSVP) лишь очищает любые значения, введенные в фор му . Причина в том , что форма осуществляет обратну~о отправ1<у методу действия RsvpForm () контроллера Home , который толыю сообщает MVC о необходимости пов торной визуализации представления. Чтобы получить и обработать отправленные данные формы, мы собираемся вос пользоваться основной возможностью контроллера. Мы добавим второй метод дейс твия RsvpForm () ,чтобы получить в свое распоряжение следующее. • Метод, который отвечает н.а НТТР-запросы GE T. Каждый раз, 1<огда кто-то щел кает на ссьmке, браузер обычно выдает запрос GET. Эта версия действия будет отвечать за отображение изначально пустой формы , когда ~по-нибудь впервые посещает / Ho me/ RsvpFo rm. • Метод, который отвечает на НТГР-запросы PO ST. По умолчанию формы, ви зуализированные с помощью Ht ml. Be g in Form () ,отправляются браузером как запросы POST. Эта версия действия будет отвечать за получение отправленных данных и принятие решения о том, что с ними делать. Обработка запросов GET и POST в отдельных методах С# способствует обеспечению аккуратности кода контроллера, т.к. эти два метода имеют разные обязанности. Оба метода действий вызываются через тот же самый URL, но в зависимости от вида за проса - GET или POS T - инфраструктура МVС вызьmает подходящий метод. В листин ге 2 . 13 показаны изменения, которые необходимо вне сти в класс HomeCo ntro ller. Листинг 2.13. Добавление метода действия для поддержки запросов POST в файле HomeController. cs using Sys tem ; usi ng Mi crosoft .AspNetCore .Mvc; using Partyinvites.Models; name space Partyinvi tes. Controller s puЫic class HomeC ontroller : Contr olle r puЫi c ViewResul t Index( ) { int hour = DateTime . Now .Hour ; ViewBag.Greeting = hour < 12 ? "Good Morning" ret urn View("MyView" ); [HttpGet] puЫic ViewResul t RsvpForm() { return View(); [HttpPost] "Good Afternoon"; puЬlic ViewResult RsvpForm(GuestResponse guestResponse) //Что сделать: сохранить ответ от гостя return View () ;
50 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Существующий метод действия RsvpFor m ( ) был снабжен атрибутом Htt pGet, который указывает MVC на то, что данный метод должен применяться только для запросов GET . Затем была добавлена перегруженная версия метода Rsvp Form ( ) ,при нимающая объект Gu e s tRe s ponse. К ней был применен атрибут Http Post , который сообщает МVС о том, что этот новый метод будет иметь дело только с запросами POST. Произведенные добавления объясняются в последующих разделах. Кроме того, было импортировано пространство имен Pa r t y i n v i t es. Mo del s . Это сделано для то г о, чтобы на тип модели Gues t Respon se можно было ссылаться без н е обходимости в указании полностью определ е нного имени класса. Использование привязки модели Первая перегруженная версия метода действия Rs v p Form () визуализирует то же самое представление, что и ранее (файл RsvpForm . cs h t ml), для генер ации формы, показанной на рис. 2.17. Вторая перегруженная версия более интересна из -за нали чия параметра. Но с учетом того, что этот метод действия будет вызываться в ответ на НТГР-запрос POST, а тип Gu estRes po nse является классом С# , каким образом они соединяются между собой? Секрет кроется в привязке модели - чрезвычайно полезной функциональной воз можности МVС, посредством которой производится разбор входящих данных и при менение пар "ключ/значение " в НТГР-запросе для заполнения свойств в типах моде лей предметной области. Привязка модели - мощное и настраиваемое средство, которое избавляет от кро потливого и тяжелого труда по взаимодействию с НТГР-запросами напрямую и поз воляет работать с объектами С#, а не иметь дело с индивидуальными значениями данных, отправляемыми браузером. Объект Guest Resp o ns e , который передается этому м етоду действия в качестве параметра, автоматически заполня ется данными из полей формы. Привязка модели, включая ее настройку, подробно рассматривается в главе 26. Одной из целей приложения является предоставление итоговой страницы с дета лями о том, кто придет на вечеринку, что означает необходимость сохранения полу чаемых ответов. Мы собираемся делать это с помощью созданной в памяти коллекции объектов . В реальном приложении такой подход не подойдет, т.к. данные ответов бу дут утрачиваться в результате останова или перезапуска приложения , но он позволя ет сосредоточить внимание на MVC и создать приложение, которое может быть легко сброшено в свое начальное состояние. Совет. В главе 8 будет продемонстрировано использование MVC для постоянного хранения и доступа к данным как часть более реалистичного примера приложения. Мы добавили в проект новый файл, щелкнув правой кнопкой мыши на папке Mo d e l s и выбрав в контекстном меню пункт Add9Class (Добавить9Класс). Файл име ет имя Repo si tory . cs и содержимое, показанное в листинге 2.14 . Листинг 2.14 . Содержимое файла Reposi tory. cs из папки Models u s ing System . Collectio n s .Gene ric; namesp ace Part yinvites.Models { puЫ ic static clas s Repository p r ivate static List<Gu es t Res ponse> responses new Li s t<G ue s tResponse>() ;
Глава 2. Ваше первое прило же ние MVC 51 puЫic static IEnumeraЫe<GuestResponse> Responses { get { return responses; puЫic static void AddResponse(GuestResponse resp onse) { responses.Add(response); Класс Repos i tory и его члены объявлены статическими, чтобы облегчить сохра нение и извлечение данных из разных мест приложения. Инфраструктура МVС пред лагает более сложный подход к определению общей функциональности, называемый внедрением зависимостей, который будет описан в главе 18, но для простого прило ж е ния врод е рассматриваемого вполне достаточно и статического RЛасса. Сохранение ответов Теперь , когда есть куда сохранять данные, можно обновить метод действия, кото рый получает НТГР-запросы POST (листинг 2.15). Листинг 2.15. Обновление метода действия в файле HomeController. cs using System; using Microsoft . AspNetCore . Mvc ; using Partyinvites .M ode l s; namespace Partyinvites . Controlle rs puЫic class HomeContro l ler : Controller puЫic ViewResult Index() { int hour = DateTime .Now . Hour ; ViewBag . Greeting = h our < 12 ? "G ood Morning" return View ( "MyView " ) ; [HttpGet] puЫic ViewResult RsvpForm() { return View () ; [HttpPost] " Good Afternoon "; puЫic ViewResult RsvpForm(GuestResponse g uest Response) { Repository.AddResponse(guestResponse); return View ( "Thanks", guestResponse) ; Все, что необходимо сделать с данными формы , посланными в запросе - это передать методу Repos i tory. AddResponse () в качестве аргумента объе1п GuestResponse , который был передан методу действия, чтобы ответ мог быть сохранен.
52 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Почему привязка модели не похожа на инфраструктуру Web Forms В главе 1 было указано , что одним из недостатков традиционной инфраструктуры ASP.NET Web Forms было сокрытие деталей НТТР и HTML от разработчиков . Вас может интересовать , делает ли то же самое привязка модели MVC, которая применялась для создания объекта GuestResponse из НТТР-запроса POST в листинге 2.15. Нет, не делает. Привязка модели освобождает нас от решения утомительной и подверже нной ошибкам задачи по инспектированию НТТР-запроса и извлечению все х требующихся значений данных, но (что самое важное) если мы хотим обрабатывать запрос вручную, то могли бы это делать, поскольку MVC предоставляет легкий доступ ко всем данных запроса. Ничто не скрыто от разработчика, но есть несколько удобных средств, которые упрощают работу с НТТР и HTML; тем не менее , использовать их вовсе не обязательно. Это может показаться едва заметной разницей, но по мере углубления знаний инфраструктуры MVC вы увидите, что практика разработки в ней полностью отличается от традиционной инфра структуры Web Forms и вы всегда осведомлены о том, как обрабатываются получаемые прило жением запросы . Вызов метода View () внутри метода действия RsvpForm () сообщает МVС о том, что нужно визуализировать представление по имени Thanks и передать ему объект GuestResponse. Чтобы создать это представление, щелкните правой кнопкой мыши на папке Views/Home в окне Solution Explorer и выберите в контекстном меню пункт Addc:::>New ltem (Добавить<=:> Новый элемент). Укажите шаблон MVC View Page (Страница представления MVC) из категории ASP.NET, назначьте ему имя Thanks. cshtml и щелкните на кнопке Add (Добавить) . Среда Visual Studio создаст файл Views/Home/ Thanks . cshtml и от1<роет его для редактирования. Поместите в файл содержимое, приведенно е в листинге 2.16. Листинг 2.16. Содержимое файла Thanks. cshtml из папки Views/Home @model Partyinvites.Models.GuestResponse @{ Layout = null ; <!DOCTYPE html> <html> <head> <meta name="viewport" content= " width=device - width " /> <title>Thanks</title> </head> <body> <р> <hl>Thank you , @Model . Name!</h l > @if (Model.WillAttend == true) { @:It' s great that you ' re coming . The drinks are already in the fridge! else { @: Sorry to hear that you can ' t make it, but thanks for letting us know. } </р> <p>Click <а asp - action= " ListResponses " >here</a> to see who is coming . </р> </body> </html>
Глава 2. Ваше nервое приложение MVC 53 Представление Thanks. csh t ml применяет механизм визуализации Razor для отображения содержимого на основе значения свойства Gu es t Re s p o ns e , которое пе редается методу View () внутри Rsvp Fo r m () . Выражение @mode l синтаксиса Razor указывает тип модели предметной области, с помощью которого представление стро го типизировано. Для доступа к значению свойства в объекте предметной области используется конструкция Model . ИмяСвойст в а . Например, чтобы получить значение свойства Name , применяется Mo d e l. Na me . Не беспокойтесь, если синтаксис Razor пока не по нятен - он более подробно объясняется в главе 5 . Теперь. когда создано представление Th a nks, появился работающий базовый при мер обработки формы посредством МVС. Запустите приложение в Visual Studio, вы брав в меню Debug пункт Start Debugg ing, щелю-rите на ссылке RSVP Now, введите на форме какие-нибудь данные и щелкните на кнопке Submit RS VP. Вы увидите резуль тат, показанный на рис. 2.18 (он может отличаться, если введено другое имя либо указано о невозможности посетить вечеринку). Thank you, Joe! I Yat1r pl1011e: ~5.~:~234===.:._ -=] It's gi·e~ r tlint yat1'r~ co111ing. llie drщks are already iн tl1e fr1dge! 1 1 \\~i l l уа11 ntt e11d? г~;~ ii~i~e~~ ~~-- Click~ !О see \V!IO is сошiнg. ~uьmit R~~-~ L______Г_____________________ _J [___ ---- ------------------ -- --- --- -- --- -- ------ _, Рис. 2.18. Представление Th a n ks Отображение ответов В конце представления Th a nks. c sht ml мы добавили элемент а для создания ссыл ки. которая позволяет отобразить список людей, собирающихся посетить вечеринку . С применением атрибута дескрипторного вспомогательного класса a s p -actio n со здается URL, который нацелен на метод действия по имени Lis t Re s pon ses (): <p>C l ick <а asp-action="ListResponses" >h ere</a > to see who is coming.< /p> Наведя курсор мыши на с сылку, rшторую отображает браузер, вы заметите, что она указывает на URL вида / Ho me/Lis tRe s pon s e s . Это не соответствует ни одно му методу действия в контроллере Н оте , и если вы щелкнете на ссылке, то увиди те пустую страницу. Открыв инструменты разработки браузера и просмотрев от вет, присланный сервером, легко обнаружить , что сервер отправил обратно ошибку 404 - Not Foun d (404 - не найдено) . (Браузер Chrome несколько странен тем, что не отображает сообщение об ошибке для пользователя, но в главе 14 будет показано, как генерировать содержательные сообщения об ошибках.}
54 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Мы устраним проблему путем создания в контроллере Home метода действия, на который нацелен URL (листинг 2 .1 7). Листинг 2.17. Добавление метода действия в файле HomeController. cs using System; using Microsoft.AspNetCore . Mvc ; using Partyinvites.Models; using System.Linq; namespace Partyinvites . Controllers puЫic class HomeCo ntroller : Controller puЫic ViewResult Index() { int hour = DateTime .N ow .H our; ViewBag. Greeting = hour < 12 ? "Go od Morning " return View ( "MyView " ) ; [HttpGet] puЫic ViewResult RsvpForm() { return View(); [HttpPost] "Good Afternoon "; puЫic ViewResult RsvpForm(GuestResponse guestResponse) { Repository . AddResponse(guestResponse); return View ( "Thanks", guestResponse) ; puЫic ViewResult ListResponses() { return View(Repository.Responses.Where(r => r.WillAttend == true)); Новый метод действия называется ListResponses ();он вызывает метод View (), используя свойство Reposi tory. Responses в качестве аргумента. Именно так метод действия предоставляет данные строго типизированному представлению . Коллекция объектов GuestResponse фильтруется с применением LINQ, так что используются только ответы с положительным решением об участии в вечеринке. Метод действия ListResponses не указывает имя представления, которое долж но применяться для отображения коллекции объектов GuestResponse, поэтому бу дет задействовано соглашение об именовании и MVC инициирует поиск представ ления по имени ListResponses. cshtml в папках Views/Home и Views/Shared . Чтобы создать представление, щелкните правой кнопкой мыши на папке Views/ Home в окне Solution Explorer и выберите в контекстном меню пункт AddqNew ltem (ДобавитьQ Новый элемент). Выберите шаблон MVC View Page (Страница представле ния MVC) из категории ASP.NET, назначьте ему имя ListResponses. cshtml и щелк ните на кнопке Add (Добавить). Приведите содержимое нового файла представления в соответствие с листингом 2.18.
Глава 2. Ваше первое прило жение MVC 55 Листинг 2.18. Отображение принятых приглашений в файле ListResponses. cshtrnl из папки Views/Home @model IEnumeraЬle<Partyinvites.Models . GuestResponse> @{ Layout = null; < !DOCTYPE html > <html> <head> <meta name="viewport " content= " width=device-width " /> <title>Responses</title> </head> <body> <h2>Here is the list of people attending the party</h2> <tаЫе> <thead> <tr> <th>Name</th> <th>Email</th> <th>Phone</ th> </tr> </thead> <tbody> @foreach (Partyinvites.Models.GuestResponse r in Model) { <tr> <td>@r .Name </td> <td>@r .Email</td> <td>@r . Phone</td> </tr> </tbody> </tаЫе> </body> </html> Файлы представлений Razor имеют расширение cshtml, потому что содержат смесь кода С# и элементов HTML. Это можно заметить в листинге 2.18, где исполь зуется цикл foreach для обработки всех объектов GuestResponse, которые метод действия передает представлению с применением метода View () . В отличие от нор мального цикла foreach языка С# тело цикла foreach из Razor содержит элементы HTML, добавляемые к ответу, который будет отправлен обратно браузеру. В данном представлении для каждого объекта GuestResponse генерируется элемент tr , кото рый содержит элементы td, заполненные значениями свойств объекта. Чтобы увидеть список в работе, запустите приложение, выбрав в меню Debug пункт Start Debugging , отправьте какие-то данные формы и затем щелкните на ссыл ке для просмотра списка ответов. Отобразится сводка по данным, введенным вами с момента запуска приложения (рис . 2.19). Представление не оформляет данные при влекательным образом, но пока этого вполне достаточно, а стилизацией мы займемся позже в главе.
56 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC 1· ReJ:ponses х Не1·е is the list of people attending the party Eшail Phon t> Joe joe,rg. exiuнp le.co 111 555- 1234 Al ice al i ce@e.xaшple . coш 555 -56 78 1 -----·--------·--------------·---__J Рис. 2. 19. Отображение списка участников вечеринки Добавление проверки досто верности Теперь мы готовы добавить в приложение проверку достоверности вводимых дан ных . В отсутствие проверки достоверности пользователи смогут вводить бессмыслен ные данные или даже отправлять пустую форму . В приложении МVС пров ерка до стоверности обычно прим еня ется к модели предметной области, а не производится в пользовательском интерфейс е. Это значит, что проверка достоверности определя ется в одном месте , но оказывает воздействие в приложении везде, где использует ся класс модели. Инфраструктура MVC поддерживает декларативные правила про верки достоверности, опр еделенные с помощью атрибутов из пространства имен System . Co mponen t Mode l. DataAnnotations , т.е. ограничения провер ки до стовер ности выражаются посредством стандартных атрибутов С#. В листинге 2.19 показа но, как применить эти атрибуты к классу модели Gu estRespon se. Листинг 2.19. Применение проверки достоверности в файле GuestResponse. cs using System.Componentмodel.DataAnnotations; names pace Partyinvit es . Models puЫic class GuestResponse { [Required(ErrorMessage = "Please enter your name")] 11 Пожалуйста, введите свое имя puЫic string Name { get ; set ; } [Required(ErrorMessage = "Please enter your email address")] [RegularExpression(".+\\@.+\\ . . +", ErrorMessage = "Please enter а valid email address")] 11 Пожалуйста , введите свой адрес электрон н ой почты puЫic st ring Email { get; set; } [Required (ErrorMessage = "Please enter your phone numЬer")] 11 Пожалуйста, введите свой номер телефона puЫic string Phone { get; set; J [Required (ErrorMessage = "Please specify whether you' 11 attend")] 11 Пожалуйста , укажите , примете ли участие puЫic bool? WillAttend { get; set; }
Глава 2. Ваше первое приложение MVC 57 Инфраструктура МVС автоматически обнаруживает атрибуты проверки достовер ности и использует их для проверки данных во время процесса привязки модели. Мы импортировали пространство имен, которое содержит атрибуты проверки достовер ности, так что к ним можно обращаться, не указывая полные имена. Совет. Как отмечалось ранее, для свойства WillAttend был выбран булевский тип , допус кающий null. Это было сделано для того, чтобы можно было применить атрибут провер ки Required. Если бы использовался обычный булевский тип, то значением, получаемым посредством привязки модели, могло быть только true или false, и отсутствовала бы возможность определить, выбрал ли пользователь значение. Булевский тип, допускающий null , имеет три разрешенных значения: true, false и null. Браузер отправляет зна чение null, если пользователь не выбрал значение, и тогда атрибут Required сообщит об ошибке проверки достоверности . Это хороший пример того, насколько элегантно ин фраструктура MVC сочетает средства С# с HTML и НТТР. Проверку на наличие проблемы с достоверностью данных можно выполнить с применением свойства ModelState. IsValid в классе контроллера. В листинге 2.20 показано, как это реализовано в методе действия RsvpForm (), поддерживающем за просы POST, внутри класса контроллера Home. Листинг 2.20. Проверка на наличие ошибок проверки достоверности для данных формы в файле HorneController. cs using System; using Microsoft.AspNetCore.Mvc; using Partyinvites.Models; using System.Linq; namespace Partyinvi tes.Controllers puЫic class HomeController : Controller puЫic ViewResult Index() { int hour = DateTime.Now.Hour; ViewBag. Greeting = hour < 12 ? " Good Morning" return View ( "MyView " ) ; "Good Afternoon"; [HttpGet] puЬlic ViewResul t RsvpForm () { return View () ; [HttpPost] puЫic ViewResult RsvpForm(GuestResponse guestResponse) if (ModelState.IsValid) { Repository.AddResponse(guestResponse) ; return View("Thanks", guestResponse) ; else { 11 Обнаружена ошибRа проверки достоверности. return View(); puЬlic ViewResult ListResponses() { return View(Repository .Response s .Where (r => r.WillAttend true));
58 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Базовый класс Controller предоставляет свойство по имени ModelState, ко торое сообщает информацию о преобразовании данных НТГР-запроса в объекты С#. Если свойство ModelState. IsValue возвращает true, то известно, что инфраструк тура МVС сумела удовлетворить ограничения пров ерки достоверности, указанные че рез атрибуты для класса GuestResponse. Когда такое происходит, визуализируется представление Thanks, как делалось до того. Если свойство ModelState . IsValue возвращает false, то известно, что есть ошибки проверки достоверности. Возвращаемый свойством ModelState объект пре доставляет подробные сведения о каждой возникшей проблеме, но нам нет нужды погружаться на такой уровень деталей, поскольку мы можем полагаться на удобное средство, которое автоматизирует процесс указания пользователю на необходимость решения любых проблем, вызывая метод View () без параметров. Когда MVC визуализирует представление, механизм Razor имеет доступ к дета лям любых ошибок проверки достоверности, связанных с запросом, и дескриптор ные вспомогательные классы могут обращаться к этим деталям, чтобы отображать сообщения об ошибках пользователю. В листинге 2 .21 приведено содержимое файла представления RsvpForm . cshtml с добавленными атрибутами дескрипторных вспо м ог ательных классов для проверки дост оверност и. Листинг 2.21. Добавление сводки по проверке достоверности в файл RsvpForm.cshtml @model Partyinvites . Models . GuestResponse @{ Layout = null ; ) < !DOCTYPE html> <html> <head> <meta name= " viewport " content= " width=device - width " /> <title>RsvpForm</ti t le> </head> <body> <form asp-action= " RsvpForm " method="post"> <div asp-validation-summary= 11 All 11 ></div> <р> <label asp-for= " Name " >Your name:</label> <input asp-for="Name " /> </р> <р> <label asp-for= " Email " >Your email : </label> <input asp-for="Email" /> </р> <р> <label asp-for="Phone " >Your phone:</label> <input asp - for= " Phone " /> </р> <р> <label>Will you attend?</label> <select asp-for= " WillAttend " > <option value= "" >Choose an option</option> <option value= " true">Yes , I 'll Ье there</option> <option value= "false">No, I can ' t come</option> </select>
Глава 2. Ваше первое прило же ние MVC 59 </р> <b utton type= " submit " >Submit RSVP</butt on> </form> </body> </html> Атрибут asp - validation - summary применяется к элементу div и отобра жает список ошибок проверки достоверности при визуализации представления. Значение для атрибута asp - validat ion-summar y берется из перечисления по име ни Val idationSummary, которое указывает типы ошибок проверки достоверности, помещаемые в сводку. Мы указали All, что является хорошей отправной точкой для большинства приложений, а в главе 27 будут описаны другие значения. Чтобы взглянуть, как работает сводка по проверке достоверности, запустите при ложение, заполните поле Name и отправьте форму. не вводя другие данные. Вы уви дите сводку с ошибками, показанную на рис. 2.20. R\Vl)Form х 1·~ 1·-·-- · --·---------:i С L ф lo.alhost:S7626/Horпe/Rs11pform '(( 1 -_.----·-~ __" _ --=~·~-:..=--·---=-·::=--::-:--:--- 1 • Рlе ме nller yo11.f ешаi\ acklr~s · • Please enter )'O\lf phone U11J11Ьer 1 • Рlеме specit)· ,, ·he1 /1 o:r yo11'll :11ta1d j Yot1r шuue ; \~~-=--=- __-- · -= - -=] 1 . r- 1 Yo11.f ешмl:l --. . . Уощ pl1011e : с_-=~ · 1 1 \\'1 11 уон att end" : С~оо~е ал opli~? • J 1(Jiiixn;1-RsvEJ [_______:·==--·--·-·----------·-·--·· -----· --·-----J Рис. 2.20. Отображение ошибок проверки достоверности Метод действия RsvpFo r m () не визуализирует представление Than ks до тех пор , пока не будут удовлетворены все ограничения провер1'и достоверности, примененные к классу GuestResponse. Обратите внимание, что введенные в поле Name данные были сохранены и отображены снова при визуализации механизмом Razor представ ления со сводкой проверки достоверности. Это еще одно преимущество привязки мо дели, и оно упрощает работу с данными формы. На заметку! Если вы работали с ASP.NET Web Forms, то знаете, что в Web Forms имеется концепция серверных элементов управления, которые сохраняют состояние, сериализи руя значения в скрытое поле формы по имени VIEWSTATE. Привязка модели MVC не имеет никакого отношения к концепциям серверных элементов управления, обратным от правкам или состоянию представления, принятым в Web Forms. Инфраструктура MVC не внедряет скрытое поле VIEWSTATE в визуализированные НТМL-страницы. Взамен она включает данные, устанавливая атрибуты value элементов управления input.
60 Часть 1. Введение в инфраструк туру ASP.NET Core MVC подсветка полей с недопустимыми значениями Атрибуты дескрипторных вспомогательных классов, которые ассоциируют свойс тва модели с элементами, обладают удобным средством , которое можно использовать в сочетании с привязкой модели. Когда свойство класса модели не проходит проверку достоверности, атрибуты дескрипторных вспомогательных .классов будут генериро вать несколько отличающуюся НТМL-разметку. Вот элемент input , который генери руется для поля Phone при отсутствии ошибок проверки достоверности: <input type= " text " data-val= " true " data - val-req u ired= "Pl ease enter your phone nurnber " id= " Phone " narne=" Phone " value= "" > Для сравнения ниже показан тот же НТМL-элемент после того, .ка.к пользователь отправил форму, не введя данные в текстовое поле (что является ошибкой проверки достоверности, поскольку мы применили к свойству Pho ne класса GuestResponse атрибут проверки Required): <input type= " text " class="input-validation-error" data-val= " true" data-val - required= "Pl ease ente r your phone nurnber" id="Phone " narne= " Phone " va lue="" > Отличие выделено полужирным: атрибут дес.крипторного вспомогательного класса asp-f or добавил .к элементу input .класс по имени input-validation-error . Мы можем воспользоваться этой возможностью, создав таблицу стилей, которая содер жит стили CSS для этого класса и другие стили, применяемые различными атрибу тами дескрипторных вспомогательных классов. По принятому в проектах МVС соглашению статическое содержимое, доставляе мое клиентам, помещается в папку wwwroot , подпапки которой организованы по типу содержимого, так что таблицы стилей CSS находятся в папке wwwroot/css , файлы JavaScrlpt - в папке wwwroot/j s и т.д. Для создания таблицы стилей щелкните правой .кнопкой мыши на папке wwwroot/ css в окне Solution Exp lore r и выберите в контекстном меню пункт AddФNew ltem (ДобавитьФ Новый элемент). В открывшемся диалоговом окне Add New ltem (Добавление нового элемента) перейдите в раздел Client-side (Клиентская сторона) и выберите шаб лон Style Sheet (Таблица стилей) из списка (рис. 2.21). r~. !:;~: .litnt·sidt 1 Code !t> Onli~ 1 1 1 f N.:iu1~ L Sort Ьy. Odault 1 CJ HTML P•ge @j5 TyptS<ript Fi!e. l§J' T~Scrip tJ SX F i lc rJ Type5(t1pt JSON Conf19ur111on f1le rJ Sowc1 Confi9u11tion f 1lc C/1cnt ·side Clttnt ·sidt Clicnt·side Cl1cnt·~ide Clltnt-1idc P·j .а. Туре; C!itnHidt 1 А(.tS(adir19 ~ty!t sh<!d. (CS.S) u~cd for1i<h 1 HTML\tyledtfinot1 oros J Q d"d-)o;,-;; -i j --- - __J Рис . 2.21 . Создание таб лицы стилей CSS
Глава 2. Ваше первое приложение MVC 61 Совет. Среда Visual Studio создает файл style . css в папке wwwroot/css при созда нии проекта с использованием шаблона Web Application. В этой главе данный файл не задействован. Назначьте файлу имя styl es . css, щелкните на кнопке Add (Добавить), чтобы со здать файл таблицы стилей, и приведите его содержимое к виду, показанному в лис тинге 2 .22. Листинг 2.22 . Содержимое файла styles. css . field - validation-error {color : #fOO ; } . field - validation- val id { display : none; . input- validation- error { border : lpx solid #fOO ; background-color : #fee ; } . validation- summary - errors { font-weight : bold ; color : #fOO ; } . validation-summary-valid { display : none; } Чтобы применить эту таблицу стилей, мы добавили элемент link в раздел head представления RsvpForm (ли стинг 2.23). Листинг 2.23. Применение таблицы стилей в файле RsvpForrn.cshtml <head> <meta name="viewport " content= " width=device-width" /> <title>RsvpForm</title> <link rel="stylesheet" href="/css/styles.css" /> </head> Элемент link использует атрибут href для у1<азания местоположения таблицы стилей. Обратите внимание, что папка wwwroot в URL опущена. Стандартная кон фигурация ASP.NET вrшючает поддержr<у обслуживания статического содержимого, такого как изображения, таблицы стилей CSS и файлы JavaScrlpt , которая автомати чески отображает запросы на папку wwwroot. Процесс конфигурации ASP.NEТ и МVС рассматривается в главе 14. Совет. Для работы с таблицами стилей предусмотрен специальный дескрипторный вспомо гательный класс, который может оказаться полезным при наличии многочисленных фай лов, требующих управления . Подробности ищите в главе 25 . Благодаря применению таблицы стилей в случае отправ1ш данных, которые вызы вают ошибки проверки достоверности, отображается визуально боле е ясные сообще ния об ошибках (рис. 2.22) . Стилизация содержимого Все цели приложения , касающиеся функциональности, достигнуты , но его общий вид оставляет желать лучшего. Когда вы создаете проект с использованием шаблона Web Application, как в текущем примере , Visual Studio устанавливает несколько рас пространенных пакетов для разработки на стороне клиента. Хотя я не являюсь сто ронником применения шаблонов проектов, мне нравятся выбранные Microsoft библи-
62 Часть 1. Введение в инфраструктуру ASP.NET Core MVC отеки клиентской стороны. Одна из них называется Bootstrap и представляет собой удобную инфраструктуру CSS, первоначально разработанную в 'I\vitter. которая пос тепенно превратилась в крупный проект с открытым кодом и стала главной опорой разработки веб-приложений. Rsvpioim Х ,-- ---- -· -- - ·------·- -----·- . - - - С LФ locJ!lюst:S7628/НQ111e/RsvpForm . ~- ... --~ - . ___ ._. ._." -; -::- ~- _. . , ·-~~:-·- --···- - - : : ·- :·;::::-.:- - ... ·::--..::.:: 1 • Plta8• 1.>ntft' ~·out· t>m.;iiJ addi'\'и • Р1Е-а~.- e11ftr :;1·ощ· phoot' numbtr • Pl•:a~• \pc.>('if·y " 'htther ~·ou'U atttod --- -" ! Yo\U' eшail : ....1-- --- =:J 1 Your pl1oue : 1 1 \ViU >"• '"'""*"""' "''"''" 1 1 1 [ suьn~li'RsiiP ] 1 L____, ___________.__________J Рис. 2.22. Автоматическая подсветка полей с ошибками проверки достоверности На заметку! На момент написания этой книги текущей версией была Bootstrap 3, но версия 4 находилась на стадии разработки. В Microsoft могут принять решение обновить версию Bootstrap, используемую шаблоном Web Application, в последующих выпусках Visual Studio, что может привести к отображению содержимого по-другому. В других главах кни ги это не должно стать проблемой, т.к. там будет показано, каким образом явно указать версию пакета , чтобы получить ожидаемые результаты . Стилизация начального представления Базовые средства Bootstrap работают за счет применения классов к элементам, которые соответствуют селекторам CSS, определенным внутри добавленных в папку wwwroot/liЬ/bootstrap файлов. Подробную информацию о классах, определенных в библиотеке Bootstrap, можно получить на веб-сайте http: / / getboo ts trap . с от , а в листинге 2.24 демонстрируется использование нескольких базовых стилей в пред ставлении MyView. cshtml. Листинг 2.24 . Добавление классов Bootstrap в файл МyView. cshtml @{ Layout = null; < ! DOCTYPE html>
<html> <head> Глава 2. Ваше первое прило жение MVC 63 <meta name= " vi ewpor t " content=" width=devi ce- width" /> <title>Index</ title> <link rel="stylesheet" href="/lib/Ьootstrap/dist/css/Ьootstrap.css" /> </head> <body> <div class="text-center"> <hЗ>We're going to have an exciting party!</hЗ> <h4>And you are invited</h4> <а class="Ьtn Ьtn-primary" asp-action="RsvpForm">RSVP Now</a> </di v> </body> </html> В разметку был добавлен элемент link, атрибут h r e f которого загружает файл bootstrap . css и з папки wwwroot/liЬ/boot s trap/dist/css . По соглашению пак е ты CSS и JavaScript от независимых поставщиков устанавливаются в папку www roo t / lib, и в главе 6 будет описан инструмент, предназначенный для управления такими пакетами. После импортирования таблиц стилей Bootstrap осталось стилизовать элементы. Рассматрива емый пример прост, поэтому необходимо использовать совсем немного классов CSS из Bootstrap: text - cen ter, Ьtn и Ьtn -p rima r y . Кл асс text - ce n ter центрирует содержимое элемента и его дочерних элементов. Класс Ьtn стилизует эл емент but t on , input или а в виде симпатичной кнопки. а класс Ьtn -primary указывает диапазон цветов для этой кнопки . Запустив приложе ние, можно увидеть результат, показанный на рис. 2.23 . Вам должно быть вполне очевидно , что я - не веб-дизайнер . На самом деле, буду чи еще ребенком , я был освобожден от уроков рисования по причине полного отсутс твия таланта. Это произвело благоприятный эффект в виде того, что я стал уделять больше времени урокам математики, но вместе с тем мои художественные навыки не развивались примерно с десятилетнего возраста. Для реальных проектов я обратился бы к помощи профессионального дизайнера содержимого, но в настоящем примере я собираюсь делать все самостоятельно и применять Bootstrap с максимально возмож ной сде ржанностью и согласованностью, на какую толь ко способен. lndex х С !. ф localhost:57628 We're going to have an exciting party! And you are inv ited 11И1Р!~' ______ _______ __ J Рис. 2.23. Стилизация представления
64 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Стилизация представления RsvpForm В Bootstra p опред ел е ны классы , которые могут использоваться для стил из ации фор м . Я н е планирую вдаваться в особые детали, но в листинге 2.25 показано, как были применены эти классы. Листинг 2.25. Добавление классов Bootstrap в файл RsvpForm. cshtml @mode l Partyinvite s. Mode l s . Gu e stRes ponse @{ Layout = null; < !DOCTYPE html > <html> <head> <meta name= " viewport " content= " width= devic e- width " /> <ti tle>RsvpForm</ti t l e> <li nk rel =" stylesheet" href=" /css/styles.css " /> <link rel="stylesheet" href="/lib/Ьootstrap/dist/css/Ьootstrap.css" /> </ h ead> <bod y> <div class="panel panel-success"> <div class="panel-heading text-center"><h4>RSVP</h4></div> <div class="pan e l-Ьody"> <form class="p-a-1" asp-action="RsvpForm" method="post"> <div asp-va lidation-swnmary="All"></div> <div class="form-group"> <laЬe l asp-for="Name">Your name:</laЬ e l> <input class= "form-control" asp-for= "Name" /> </div> <div cla ss= "form-group"> <laЬel asp-for=" Email">Your email:</laЬel> <input class="form-control" asp-for="Email" /> </div> <div class="form-group"> <laЬel asp-for="Phone">Your phone:</laЬel> <input class="form-control" asp-for="Phone" /> </div> <div class="form-group"> <laЬel>Will you attend?</laЬel> <select class="form-control" asp-for="WillAttend"> <option value="">Choose an option</option> <option value="true">Yes, I'll Ье there</option> <option value="false">No, I can ' t come</option> </select> </div> <div class="text-center"> <Ьutton class="Ьtn Ьtn-primary" type="suЬmit"> SuЬmit RSVP </Ьutton> </div> </form> </div> </div> </body> </html>
Глава 2. Ваше первое прило же ние MVC 65 Классы Bootstrap в этом примере создают заголовок, просто чтобы придать компо новке структурированность. Для стилизации формы используется класс form-group, J{Оторый стилизует элемент, содержащий label и связанный элемент input или select. Результаты стилизации можно видеть на рис. 2.24 . 1 '~1.. YourpJюne: 1 1 W1ll you attend? Choose an opuon ·- !1 j 1 j 1 1 ! 1 1 L. -----~-·-~------·-----·-----··-·-- ·----- ___j Рис. 2.24. Стилизация представления RsvpForm Стилизация представления Тhanks Следующим стилизуемым представлением является T hanks. cshtml; в листин ге 2.26 показано, как это делается с применением классов CSS , подобных тем, которые использовались для других представлений. Чтобы облегчить управление приложени ем, имеет смысл избегать дублирования кода и разметки везде, где только возможно . Инфраструктура MVC предлагает несколько средств, помогающих сократить дубли рование, которые рассматриваются в последующих главах. К таким средствам отно сятся компоновки Razor (глава 5), частичные пр едставления (глава 21) и компоненты представлений (глава 22). Листинг 2.26. Добавление классов Bootstrap в файл Thanks. cshtml @model Partyinvites . Models . GuestResponse @{ Layout = null; <!DOCTYP E html> <h tml> <head > <meta n ame= "viewp ort " content= " width=device - width " /> <title>Thanks</title> <link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" /> </head>
66 Часть 1. Введение в и нфрас тр у ктуру ASP.NET Core MVC <body class="text-center"> <р> <hl>Thank you , @Model . Name!</hl> @if (Mode l. WillAttend == true) { @: It ' s great that you ' re coming. Th e drinks are already in the fridge! else { @: Sorry to hear that you can ' t make it, but thanks for letting us know. } </р> Click <а class="nav-link" asp-action="ListResponses">here</a> to see who is coming . </body> </html> На рис. 2.25 показан результат стилизации. Thank you, Joe! ll's great tha t you're coming. The drink s are already in tl1e fridge ' Click t1 ere to see who is coming. -- ------------------------- --J Рис. 2.25. Стилизация представления Th anks Стилизация представления Lis tResponses Последним мы стилизуем представление ListResponses, которое отображает список участников вечеринки . Стилизация содержимого следует тому же ба зовому подходу, который применялся в отношении всех стилей Bootstrap (листинг 2.27). Листинг 2.27. Добавление классов Bootstrap в файл ListResponses. cshtml @model IEnumeraЬle<Partyinvites.Models . Gue s tResponse> @{ Layout = null ; <!DOCTYPE html> <html> <h ead> <meta name= " viewport " content= " widt h=device-widt h" /> <link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" /> <tit l e>Responses</t itle> </head> <body> <div class="panel-body"> <h2>Here is the l ist of people attending the party</h2>
Глава 2. Ваше nервое nриложение MVC 67 <tаЫе class="taЫe taЫe-sm taЫe-striped taЫe-Ьordered"> <thead> <tr> <th>Name</th> <th>Email</th> <th>Phone</th> </tr> </ the ad> <tbody> @foreach (Partyinvites .Mo dels . GuestResponse r in Mode l) { <tr> <td>@r.Name</td> <td>@r . Email</td> <td>@r . Phone</td> </tr> < /tbody> </tаЫе> </div> </body> </html> На рис . 2.26 показано, как теперь выглядит таблица участников . Добавление сти лей к данному представлению завершает пример приложения , которое теперь достиг ло всех целей разработки и имеет намного более совершенный внешний вид. if- 1[~ Here is the list of people attending the party ! 1 Name 1 Joe 1 Alico i ВоЬ ! Emai l joe@example.com a1ice ~ example .com bob @example.com [______ - ---·-- --- Phone 555-1 234 255-2345 i1 1 1 i - -·- ___ J Рис. 2.26. Стилизация представления ListResponses Резюме В этой главе был создан новый проект МVС , который использовался для построе ния простого приложения ввода данных МVС, что позволило получить первое пред ставление об архитектуре ASP.NET Core MVC и применяемом подходе. Некоторые о сн о вные средства (включая синтаксис Razor, маршрутизацию и тестирование) не рассматривались, но мы вернемся к этим темам в последующих главах. В следующей глав е будет описаны паттерны проектирования MVC, которые формируют основу эф фективной разр аботки с помощью ASP.NET Core МVС .
ГЛАВА 3 Паттерн МVС, проекты и соглашения п режде чем приступить к углубленному изучению деталей инфраструктуры ASP.NEТ Core MVC, необходимо освоить паттерн проектирования МVС, лежащие в его основе концепции и способ, которым они транслируются в проекты ASP.NET Core MVC. Возможно, вы уже знакомы с некоторыми идеями и соглашениями, обсуждае мыми в этой главе, особенно если вам приходилось заниматься разработкой сложных приложений ASP.NET или С#. Если это не так, тогда внимательно читайте настоящую главу. Хорошее понимание того, что положено в основу MVC, может помочь увязать функциональные возможности инфраструктуры с контекстом материала, излагаемо го в оставшихся главах книги. История создания MVC Термин модель-представление-контроллер (model-view-controller) был в употреб лении с конца 1970-х годов и происходит из проекта Smalltalk в Xerox PARC, где он был задуман как способ организации ряда ранних приложений с графическим поль зовательским интерфейсом. Некоторые нюансы первоначального паттерна MVC были связаны с концепциями, специфичными для Smalltalk, такими как экраны и инстру менты, но более широкие понятия по-прежнему применимы к приложениям - и осо бенно хорошо они подходят для веб-приложений. Особенности паттерна MVC Если оперировать высокоуровневыми понятиями, то паттерн MVC означает, что приложение MVC будет разделено, по крайней мере, на три указанные далее части. • Модели, содержащие или представляющие данные, с которыми работают пользователи. • Представления, используемые для визуализации некоторой части модели в виде пользовательского интерфейса. • Контроллеры, которые обрабатывают входящие запросы, выполняют операции с моделью и выбирают представления для визуализации пользователю. Каждая порция архитектуры MVC четко определена и самодостаточна; такое по ложение вещей называют разделением обязанностей. Логика, которая манипулиру-
Глава 3. Паперн MVC, проекты и соглашения 69 ет данными в модели, содержится только в модели. Логика, отображающая данные, присутствует только в представлении. Код, который обрабатывает пользовательские запросы и ввод. находится только в контроллере. Благодаря ясному разделению между порциями приложение будет легче сопровождать и расширять на протяже нии его времени существования вне зависимости от того, насколько большим оно станет. Понятие моделей Модели (Мв МVС) содержат данные, с которыми работают пользователи. Сущест вуют два обширных типа моделей: модели представлений, которые выражают сами данные, передаваемые из контроллера в представление. и модели предметной облас ти, которые содержат данные в предметной области наряду с операциями. транс формациями и правилами для создания, хранения и манипулирования данными, все вместе называемыми логикой моделей. Модели - это определение "вселенной", в которой функционирует приложение. Например. в банковском приложении модель представляет все аспекты банковской деятельности, поддерживаемые приложением, такие как расчетные счета, главная бухгалтерская книга и кредитные лимиты для клиентов. равно как и операции, ко торые могут применяться для манипулирования данными в модели. подобные вне сению денежных средств и списанию их со счетов . Модель отвечает также за сохра нение общего состояния и целостности данных - например, удостоверяясь, что все транзакции внесены в главную книгу, а клиент не снимает со счета больше денеж ных средств, чем имеет на то право, или больше. чем находится в распоряжении самого банка. Для каждого компонента в паттерне MVC будет описано, что он должен включать, а что не должен. Модель в приложении, построенном с использованием паттерна МVС, доЛJ1Сна: • содержать данные предметной области; • содержать логику для создания, управления и модификации данных предмет ной области; • предоставлять чистый АРI-интерфейс, который открывает доступ к данным мо дели и операциям с ними. Модель не должна: • показывать детали того, ка~< осуществляется получение или управление дан ными модели (другими словами, подробности механизма хранения данных не должны быть видны контроллерам и представлениям); • содержать логику. которая трансформирует модель на основе взаимодействия с пользователем (поскольку это работа контроллера): • содержать логику для отображения данных пользователю (т.к. это работа представления). Преимущества обеспечения изоляции модели от контроллера и представлений за ключаются в том, что вы можете гораздо легче тестировать логику (модульное тести рование описано в главе 7) и проще расширять и сопровождать приложение в целом.
70 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Совет. Многих разработчиков, только приступивших к ознакомлению с паттерном MVC, при водит в замешательство идея помещения логики в модель данных из - за их уверенности в том, что целью паттерна MVC является отделение данны х от логики . Это заблуждение: цель паттерна MVC - разделение приложения на три функциональных области, каждая из которых может содержать и логику, и данные. Цель не в том, чтобы устранить логику из модели. Наоборот, цель в том, чтобы гарантировать наличие в модели только логики, предназначенной для создания и управления данными модели. Понятие контроллеров Контроллеры являются "соединительной тканью " паттерна MVC, исполняя роль каналов между моделью данных и представлениями. Контроллеры определяют дейс твия. предоставляющие бизнес-логику, которая оперирует на модели данных и обес печивает представления данными, подлежащими отображению для пользователя. Контроллер, построенный с применением паттерна MVC, должен: • содержать действия. требующиеся для обновления модели на основе взаимо действия с пользователем. Контроллер не должен: • содержать логику, которая управляет внешним видом данных (это работа представления); • содержать логику, которая управляет постоянство м данных (это работа модели). Понятие представлений Представления содержат логику, которая требуется для отображения данных поль зователю или для сбора данных от пользователя. так что они могут быть обработаны каким-то де йствием контроллера. Представления должны: • содержать логику и разметку, необходимые для показа данных поль з ов ателю. Представления не должны: • содержать сложную логику (ее лучше поместить в контролл е р): • содержать логику. которая создает, сохраняет или манипулирует моделью пред метной области. Представления могут содержать логику, но она должна быть простой и использо ваться умеренно. Помещение в представление чего угодно кроме вызовов простейших методов или несложных выражений затрудняет тестирование и сопровождение при ложения в целом. Реализация MVC в ASP.NET Core Как подразумевает само название, реализация ASP.NET Core MVC адаптиру ет абстрактный патте рн MVC к ми ру разработки ASP.NET и С#. В инфраструктуре ASP.NET Core MVC контроллеры - это классы С#, обычно производные от класса Microsoft . AspNetCore . Mvc . Controll er. Каждый открытый метод в производном от Con t rol le r классе является методом действия. который ассоциирован с каким-то URL.
Глава 3. Паттерн MVC, проекты и соглашения 71 Когда запрос посьmается по URL, связанному с методом действия, операторы в данном ме тоде действия выполняются, чтобы провести некоторую операцию над моделью предмет ной области и затем выбрать представление для отображения клиенту. Взаимодействия между контроллером, моделью и представлением проиллюстрированы на рис. 3. 1 . НТТР Запрос ------------~ Ответ Представление .-1- - -- - -- -1 Модель Контроллер ._________. представления .__ ______ . Модель Рис. 3.1 . Взаимодействия в приложении MVC _• Постоянство (обычно с помощью реляционной - - базы данных) В инфраструктуре ASP.NET Core МVС применяется механизм визуализации, из вестный как Razor, который является компонентом, ответственным за обработку представления для генерации ответа браузеру. Представления Razor - это НТМL шаблоны, содержащие логику С#, которая используется для обработки данных мо дели с целью генерации динамического содержимого, реагирующего на изменения в модели. Работа Razor рассматривается в главе 5. Инфраструктура ASP.NET MVC не налагает никаких ограничений на реализацию модели предметной области. Вы можете создать модель с применением обычных объектов С# и реализовать постоянство с использованием любых баз данных, инф раструктур объектно-реляционного отображения или других инструментов работы с данными, поддерживаемых в .NET . Сравнение MVC с другими паттернами Разумеется, MVC- далеко не единственный архитектурный паттерн программно го обеспечения. Есть много других паттернов подобного рода, и некоторые из них являются или, по крайней мере, были чрезвычайно популярными. О паттерне MVC можно узнать много интересного, сравнивая его с альтернативами. В последующих разделах кратко описаны разные подходы к структурированию приложения и приве дено их сравнение с МVС . Одни паттерны будут близкими вариациями на тему MVC, в то время как другие - совершенно отличаться. Я не утверждаю, что МVС - идеальный паттерн во всех ситуациях. Я сторонник выбора такого подхода. .который лучше всего решает имеющуюся задачу. Как вы вскоре увидите, есть ситуации, когда не.которые конкурирующие паттерны в равной степени полезны или лучше MVC. Я призываю делать информированный и осознан ный выбор паттерна . Сам факт чтения вами этой книги наводит на мысль, что в опре деленной мере вы уже склонны применять паттерн MVC, но всегда полезно сохранять максимально широкую точку зрения. Паттерн ин теллектуального пользовательского интерфейса Один из наиболее распространенных паттернов проектирования известен как ин теллектуальный пользовательский интерфейс (Smart UI). Большинству программис тов приходилось создавать приложение с интеллектуальным пользовательским ин терфейсом на том или ином этапе своей профессиональной деятельности - меня это определенно касается. Если вы использовали Windows Forms или ASP.NET Web Forms, то сказанное относится и к вам.
72 Часть 1. Введение в инфраструктуру ASP.NET Core MVC При построении приложения с интеллектуальным пользовательским интерфейсом разработчики конструируют интерфейс, часто перетаскивая набор компонентов или элементов управления на поверхность проектирования или холст. Элементы управ ления сообщают о взаимодействии с пользователем, инициируя события для щелч ков на кнопках. нажатий клавиш на клавиатуре, перемещений курсора мыши и т.д. Разработчик добавляет код реакции на эти события в набор обработчиков событий. которые являются небольшими блоками кода, вызьmаемыми при выдаче специфичес кого события в определенном компоненте. В конечном итоге получается монолитное приложение, показанное на рис. 3.2 . Код. который поддерживает пользовательский интерфейс и реализует бизнес-логику. перемешан между собой без какого-либо раз деления обязанностей. Код, который определяет приемлемые значения для вводимых данных и запрашивает данные или модифицирует, например, расчетный счет поль зователя, оказывается размещенным в небольших фрагментах, связанных друг с дру гом в предполагаемом поряДitе поступления событий. Запрос ~ Интеллектуальный пользовательский Ответ интерфейс __ ._ Постоянство (обычно с помощью реляционной - - базы данных) Рис. 3.2 . Паттерн интеллектуального пользовательского интерфейса Интеллектуальные пользовательские интерфейсы идеальны для простых проектов, т.к. позволяют добиться неплохих результатов достаточно быстро (по сравнению с раз работкой MVC, которая, как показано в главе 8, требует определенных начальных за трат, прежде чем будут доставлены результаты). Интеллектуальные пользовательские интерфейсы также подходят для построения прототипов пользовательских интерфей сов. Их инструменты визуального конструирования могут оказаться по-настоящему удобными, и если вы обсуждаете с заказчиком требования к внешнему виду и потоку пользовательского интерфейса, то инструмент интеллектуального пользовательского интерфейса может быть быстрым и удобным способом для генерации и проверки раз личных идей. Самый крупный недостаток интеллектуальных пользовательских интерфейсов связан с тем, что их трудно сопровождать и расширять. Смешивание кода модели предметной области и бизнес-логики с кодом пользовательского интерфейса при водит к дублированию, когда один и тот же фрагмент бизнес-логики копируется и вставляется для поддержки вновь добавленного компонента. Нахождение всех дубли рованных фрагментов и применение к ним исправления может быть непростой за дачей. Добавление новой функции может оказаться практически невозможным без нарушения работы каких-то существующих функций. Тестирование приложения с интеллектуальным пользовательским интерфейсом также может быть затруднено . Единственный способ тестирования - эмуляция взаимодействия с пользователем , что является далеким от идеала и трудно реализуемым фундаментом для обеспечения полного покрытия тестами. В мире МVС интеллектуальный пользовательский интерфейс часто называют ан типаттерном, т.е. чем-то таким, чего следует избегать любой ценой . Эта антипатия возникает (по крайней мере, частично) оттого, что к инфраструктуре MVC обращают ся в поисках альтернативы после множества не слишком успешных попыток разра-
Глава 3. Паттерн MVC, проекты и соглашения 73 ботки и сопровождения приложений с интеллектуальным пользовательским интер фейсом , которые попросту вышли из-под контроля. Тем не менее, полный отказ от паттерна интеллектуального пользовательского интерфейса будет заблуждением . В нем не все настолько плохо, и с данным подхо дом связаны положительные аспекты. Приложения с интеллектуальным пользова тельским интерфейсом быстро и легко разрабатывать. Производители компонентов и инструментов проектирования приложили немало усилий, чтобы сделать процесс разработки приятным занятием, поэтому даже совершенно неопытный программист за считанные часы может получить профессионально выглядящий и достаточно фун кциональный результат. Наибольшая слабость приложений с интеллектуальным пользовательским интер фейсом - низкое удобство сопровождения - не проявляется при мелких объемах разработки. Если вы создаете несложный инструмент для небольшой аудитории, то приложение с интеллектуальным пользовательским интерфейсом может оказаться идеальным решением. Просто при этом не гарантируется увеличение сложности, под держиваемое приложением MVC. Архитектура "модель-представление" В приложении с интеллектуальным пользовательским интерфейсом проблемы со провождения обычно возникают в области бизнес-логики. которая настолько рассеяна по приложению, что внесение изменений или добавление новых функций становится мучительным процессом. Улучшить ситуацию помогает архитектура "модель-пред ставление'', которая выносит бизнес-логику в отдельную модель предметной облас ти. Все данные, процессы и правила концентрируются в одной части приложения (рис. 3.3). Запрос Ответ Пользовательский 1-------1~ интерфейс (представление) Модель _._ Постоянство (обычно с помощt реляционной - - базы данных) Рис. З.З. Архитектура "модель-представление" Архитектура "модель-представление'" может быть значительным усовершенствова нием по сравнению с монолитным интеллектуальным пользовательским интерфей сом (скажем, ее гораздо легче сопровождать). но возникают две проблемы. Первая: поскольку пользовательский интерфейс и модель предметной области тесно пере плетены, модульное тестирование любой из этих частей может оказаться затрудни тельным . Вторая проблема проистекает из практики, а не из определения паттерна. Обычно модель содержит большой объем кода доступа к данным (не обязательно. но в большинстве случаев) , а это означает, что модель данных содержит не только бизнес данные, операции и правила. Классическая трехуровневая архитектура Чтобы решить проблемы, присущие архитектуре "модель-представление'", трехзвен ная или трехуровневая архитектура отделяет код реализации постоянства от модели предметной области и помещает его в новый компонент, который называется уровн.ем доступа к данным (data access layer - DAL). Сказанное иллюстрируется на рис. 3.4.
74 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Запрос Ответ Пользовательский 1----.-i интерфейс (представление) Модель Рис. 3.4. Трехуровневая архитектура Уровень доступа к данным Постоянство - - •(обычно с помощью __ реляционной базы данных) Трехуровневая архитектура является наиболее широко используемым паттерном в бизнес-приложениях. Она не устанавливает никаких ограничений на способ реали зации пользовательского интерфейса и обеспечивает хорошее разделение обязаннос тей , не будучи излишне сложной. Кроме того, ценой некоторых усилий уровень DAL может быть создан так, чтобы модульное тестирование проводилось относительно легко. Можно заметить очевидные сходства между классическим трехуровневым при ложением и паттерном МVС. Разница в том, что когда уровень пользовательского ин терфейса напрямую связан с инфраструктурой графического пользовательского ин терфейса типа "щелчок-событие" (такой как Windows Forms или ASP.NET Web Forms), то выполнение автоматизированных модульных тестов становится практически не возможным. А поскольку часть пользовательского интерфейса трехуровневого при ложения может быть сложной, останется много кода, не подвергавшегося серьезному тестированию. В худшем сценарии отсутствие принудительных ограничений, накладываемых трехуровневой архитектурой на уровень поль зовательско го интерфейса, означает, что многие такие приложения в итоге оказываются тонко замаскированными при ложениями с интеллектуальным пользовательским интерфейсом без какого-либо ре ального разделения обязанностей. Это ведет к наихудшему конечному результату: не поддерживающему тестирование и трудному в сопровождении приложению. которое к тому же и чрезмерно сложно. Разновидности MVC Основные принципы проектирования приложений МVС были уже описаны, осо бенно те из них, которые применимы к реализации ASP.NET Core МVС. Другие реа лизации интерпретируют аспекты паттерна MVC иначе, дополняя, подстраивая или ка~<-то еще адаптируя его для соответствия области охвата и целям своих проектов . В последующих разделах мы кратко рассмотрим две наиболее распространенных ва риации на тему MVC. Понимание этих разновидностей не имеет особого значения в случае работы с ASP.NET Саге MVC. Данная информация включена ради полноты картины, потому что такие термины будут употребляться в большинстве обсуждений паттернов проектирования ПО . Паттерн "модель·nредставление-презентатор" Паттерн "модель-представление- презентатор" (model-view-presenter - МVР) яв ляется разновидностью МVС и разработан для того, чтобы облегчить согласование с поддерживающими состояние платформами графического пользовательского интер фейса, такими как Windows Forms или ASP.NEТ Web Forms. Это достойная по пытк а из влечь лучшее из паттерна интеллектуального пользовательского интерфейса, избежав проблем, которые он обычно привносит.
Глава 3. Паттерн MVC, проекты и соглашения 75 В этом паттерне презентатор имеет те же обязанности, что и контроллер МVС, но он также более непосредственно связан с представлением, сохраняющим информа цию о состоянии, напрямую управляя значениями, которые отображаются в компо нентах пользовательского интерфейса в соответствии с вводом и действиями пользо вателя. Существуют две реализации этого паттерна. • Реализация пассивного представления, в которой представление не содер жит никакой логики. Такое представление служит контейнером для элементов управления пользовательского интерфейса, которыми напрямую управляет презентатор . • Реализация координирующего контроллера, в которой представление может от вечать за определенные элементы логики презентации, такие как привязка дан ных, и получать ссылку на источник данных от моделей предметной области. Отличие между этими двумя подходами касается уровня интеллектуальности представления . В любом случае презентатор отделен от инфраструктуры графичес кого пользовательского интерфейса , что делает логику пр езентатора более простой и подходящей для модульного тестирования. Паттерн "модель-представление-модель представления" Паттерн "модель-представление-модель представления" (model-view-view model - МWМ) - это последняя разновидность МVС. Он появился в Microsoft и используется в инфраструктуре Windows Presentation Foundation (WPF). В паттерне МWМ модели и представления играют те же самые роли, что и в MVC. Разница связана с присутс твующей в МWМ концепцией модели представления, которая является абстрактным представлением пользовательского интерфейса. Как правило, модель представле ния - это класс С#, который открывает доступ к свойствам для данных, подлежащих отображению в пользовательском интерфейсе, и операциям с данными. инициируе мым из пользовательского интерфейса. В отличие от контроллера МVС модель пред ставления МWМ не имеет ни малейшего понятия о существовании представления (или любой конкретной технологии пользовательских интерфейсов). Представление МWМ применяет средство привязки WPF, чтобы установить двунаправленное соеди нение между свойствами , доступ к которым открывают элементы управления в пред ставлении (вроде пунктов раскрьmающегося меню или эффекта от щелчка на кнопке), и свойствами . доступ к которым открывает модель представления. На заметку! В MVC также используется термин модель представления, но он относится к простому классу модели, предназначенному только для передачи данных из контроллера в представление, как противоположность моделям предметной области, которые являют ся сложными представлениями данных, операций и правил. Проекты ASP.NET Core MVC При создании нового проекта ASP.NET Core МVС среда Visual Studio предлагает на выбор несколько вариантов начального содержимого, которое желательно иметь в проекте. Идея в том, чтобы облегчить разработчикам-новичкам процесс обучения и применить сберегающий время передовой опыт при описании распространенных средств и задач. Я не поклонник такого подхода в отношении заготовленных про ектов или нода. Намерения обычно хороши, но исполнение никогда не приводит в
76 Часть 1. Введение в инфраструктуру ASP.NET Core MVC восторг. Одной из характеристик, которая мне больше вс его нравит ся в ASP.NET и MVC, является высочайшая гибкость в подгонке платформы под мой стиль разработ ки. Процессы, классы и представления, которые создает и наполняет среда Visual Studio, вызывают у меня чувство , что я вынужден работать в стиле кого-то другого . К тому же я нахожу содержимо е и конфигурацию слишком обобщенными и прими тивными, чтобы приносить хоть какую-нибудь пользу. Разработчики в Microsoft не могут знать, какой вид приложения необходим, поэтому они охватьmают все основы, но настолько обобщенным путем. что в итоге я просто избавляюсь от всего стандар тного содержимого. Моя рекомендация (всем, кто по недоразумению спросил) заключается в том, что бы начинать с пустого проекта и добавлять нужные папки , файлы и пакеты. Вы не только узнаете больше о способе работы МVС, но и будете обладать полным контро лем над тем , что содержит ваше приложение . Но мои предпочтения н е должны формировать вашу практику разработки. Вы мо жете счесть шаблоны более удобными, чем я. особенно если явля етесь новичком в разработке ASP.NET и е ще не выработали стиль, который устраивает лично вас. Вы может е такж е признавать шаблоны проектов полезным ресурсом и источником идей, хотя должны проявлять осмотрительность и не добавлять функциональность к прило жению до того , как полностью поймете, каким образом она работает. Создание проекта Когда вы впервые создаете новый проект ASP.NET Core, то име ете три базовых от правных точки. из которых можно выбирать: шаблон Empty (Пустой), шаблон Web API и шаблон Web Application (Веб-приложение), как показано на рис. 3 .5 . Stftct а t empfote: r ------------- --------., 1 А project templ•I• for cre•t •ng an ASP.NEТ Coro ASP.NEТ Core Te mp lat es 1' / •ppltcat1on wrth "" ampfe ASP.NET MVC Vil!\., ; and Controllers. This ttmplate can af so Ье used for RESTTul I~~ij 1' НТТР servtces . 1 1 Empty Web API Web 1 1 Ш!!!.т.ш Applicatto n 1 l~c-h~an-g-eA_ut_h_e_.nt-ic-al-io~n 1 iL ______________________ 1 Authe:n ticotio n: No AuthentkatIOn <;!) Microso ft Azure G О Host in the cloud ~-5:_rvice --] ~OK~,, ~J Рис. 3.5. Шаблоны проектов ASP.NET Core Шаблон проекта Empty содержит связующие механизмы для инфраструктуры ASP.NET Core, но не включает библиот еки или конфигурацию, требующиеся прило жению МVС. В состав шаблона проекта Web API входят инфраструктуры ASP.NET Core и MVC, а также пример приложения. который демонстрирует получение и обработ ку
Глава 3. Паттерн MVC, проекты и соглашения 77 запросов Ajax от клиентов. Шаблон проекта Web Application содержит инфраструк туры ASP.NET Core и MVC с примером приложения, иллюстрирующим генерацию НТМL-содержимого. Шаблоны Web API и Web Application могут конфигурироваться с различными схемами для аутентификации пользователей и авторизации их доступа к приложению. Шаблоны проектов могут производить впечатление , что для создания определен ного вида приложения ASP.NET вы обязаны следовать специфическому пути, но это не так. Шаблоны - это просто разные отправные точки для получения той же самой функциональности, и вы можете добавлять в проекты, созданные с помощью любого шаблона , любую желаемую функциональность . Скажем, в главе 20 объясняется, как им еть дело с запросами Ajax, а в главах 28-30 - что делать с аутентификацией и ав торизацией, причем все примеры будут начинаться с шаблона проекта Empty. Таким образом, реальная разница между шаблонами проектов связана с началь ным набором библиотек, конфигурационных файлов, кода и содержимого, добавля емого средой Visual Studio при создании проекта. Существует много отличий между самым простым (Empty) и самым сложным (Web Application) проектами, как можно видеть на рис. 3.6, где показано окно Solution Explorer после создания проектов с ис пользованием каждого шаблона. Для случая с шаблоном Web Application пришлось привести снимки окна Solution Explorer с разными открытыми папками, потому что единственный список файлов не уместился на печатной странице. Дополнительные файлы, которые шаблон Web Application добавляет в проект, вы глядят устрашающе, но часть из них - всего лишь заполнители или реализации, ил люстрирующие общие функциональные возможности . .l - Solutionlttm: IJ tJ!r>b•l.J~Ofl }' PIOp('lties tJ t.\of\<hStШn91.j1on ~ -·1001 • • Otptnckntit:s Со Pн19rem .cs .l rJp1ojcrtJи111 n pro;нtJocli:.j~Qn . D P•ojt(t_Rt•dm~html С• St1r11tp.H у wtb.c~fig , 5:1. ..,, ,~ t> J- P109ff11ts •• Rtfrr tncн ." -V.~001 !:> • ·• DrprndtN:iti ..r .- Co nt10Jlt." с• Acc"untCont1olltr.cs t" HomeControllrцs с• М.n19rCon11ollcr.c~ • "" D1t1 • ....• м.g1111cns t> с- OOOOOOOOOOOOOO.Crt1trldf.nlit)·St С" Applк.11tionObCon!txtModf.!Srit с- Applic.t1ionDbContm.cs ь illl! t-.tud~lt • Strvicн Р l[m1i1Srndt1.o t • ISmsSt"dr:цs с• Meщ9tScnAct,.ci Jt 81 Vi~ lJ 1ppsrtting1j10" 61 Ьundlteonfig .jso n с- P1ogr1m.c1 " IJ project.j1or1 .! ~' Projr:cLR"dmc. html e< S1a rtup.cs f) wtt.conl19 • At<O unl @1 Cofll11mEm.11l.c\htmt !!!) (ictvnaltoginConfirm!bon.c1h!ml 8 Ь1:ttl\lllogin FJИu1c.cshtml ~for9ctPшwaid.cshtml В Forg otP1иword Co r1firmation .c1'11ml 8 Locl:out.,shtml В login.rshtml [!!) д.f9•Sttr.cshtml @) P.t u,1P1mwo1d.c.111tml 0 Rt~ttP,шwo1dConfirn1•tion.cshtml В SrndCodt.uhtml 8 'hrifyCodc.c1html • loo.· Homt /!) AЬouttthtml @j Cont1ct.0Nml G) lndu.cshlml ;..· M1"1gr @1 AddPhontN11mЬtt.cshtml 121 Ctш19rPt~nчo1d .<Ui1ml 0 lndu.иhl ml @lNorцgtlogin,.<thtml В SftP•1swo1d. нhtml 0 VtrifyPhc~umbtr.csh1ml • ....- Sh"rd е .tl)'O\ILt~htmt В .L oginP11rti•l.ohtm! В .V11l1d1t1onXript~Patti11Ltshtml [!) (rfor.cshtml @l .Viewimportм~htnil @} .ViewScer1.c1html с- b1(m1llog1nCo"Г. rmtti0nVir...Mod!!I.« с.• for9otP1s~rcМrwModtl.н <• loginVie1.~.'\odrl.cs с• Rrgist rt'lreY<Modd.c s с• Rt1t!Pauwo1dVitwМodrl.{~ t• !.tndCodrV-IC!'WМodrl .п с- \lf1ify(ode\'~Modt1.ts • ;,,..· M1rцg rV1rwModrh t • AddPhontNumbr!VitwModr Lu с- 01n9eP1нwo1dVlrwModrl .< 1 с• ConfigurrTwo F1ctoJV1e1.~ModrJ.c1 С" f1ctcrV1ewMQdrl.ci с- lnduVitwMode!.ts ео J. l..,..9d.vyi11}Vif.'WМ0Ucl.~} С* Rtm0Vtlog1nVi,. .. Modrl,CJ с• SdP•иwoнfVitwf"1odt~C1 С" Vt•ily?hontNumbr<V1ewModt!.c.t С" Applktlil)r!Ultf.U /> • ir"•9н /1$j' • .. .. r1ь " "1i boot1t1Jp !:> ~ j qutty 1> О jqUt"'f·v.tГod1li11n·unoЫru1N't !J _1dtrtncn.j~ f1viton .ico Рис. 3.6. Стандартное содержимое , добавляемое в проект шаблонами Empty и Web Application
78 Часть 1. Введение в инфраструктуру ASP.NET Саге MVC Одни дополнительные файлы настраивают MVC или конфигурируют ASP.NET . Другие представляют собой библиотеки клиентской стороны , которые будут вклю чаться в состав НТМL-разметки, генерируемой приложением. Список файлов в на стоящий момент может выглядеть огромным, но к концу книги вы будете понимать, что делает каждый из них . Независимо от того , какой шаблон применяется для создания проекта , существу ют общие папки и файлы, имеющиеся во всех шаблонах . Одни элементы в проекте играют специальные роли, которые жестко закодированы в инфраструктуре ASP.NET или МVС либо каком-то инструменте из числа поддерживаемых Visual Studio . Другие связаны с соглашениями об именовании, которые используются в большинстве про ектов ASP.NET или MVC. В табл. 3.1 описаны важные файлы и папки, с которыми вы столкнетесь в проекте ASP.NET Core MVC; часть из них не присутствует в проекте по умолчанию , но будет представлена в последующих главах. На заметку! Все папки и файлы, описанные в табл . 3.1, находятся в папке s r c , которая является местоположением, где Visual Studio создает проект ASP.NET Core MVC внутри решения . Таблица 3.1. Сводка по элементам проекта MVC Папка или файл /Area s /Dependencies /Compone nt s / Contro llers /Data /M igrat ions / Mo dels /Views Описание Области - это способ разбиения крупных приложений на мелкие порции. Области рассматриваются в главе 16 Элемент зависимостей предоставляет подробные сведения обо всех пакетах, от которых зависит проект. Диспетчеры пакетов , применяемые Visual Studio, описаны в главе 6 Здесь определены классы компонентов представлений, которые используются для отображения самодостаточных средств, таких как корзины для покупок. Компоненты пред ставлений обсуждаются в главе 22 Сюда помещаются классы контроллеров. Это соглаше- ние. Классы контроллеров могут размещаться где угодно, потому что все они компилируются в одну и ту же сборку. Контроллеры подробно рассматриваются в главе 17 Здесь определены классы контекста баз данных, хотя я предпочитаю игнорировать это соглашение и определять их в папке Mod e ls, как будет продемонстрировано в главе 8 Здесь хранятся детали схем баз данных, чтобы можно было обновлять развернутые базы данных. Процесс развертыва ния будет показан в главе 12 Сюда помещаются классы моделей представлений и моде лей предметной области. Это соглашение. Классы моделей могут определяться где угодно в текущем проекте или даже в отдельном проекте Здесь хранятся представления и частичные представления, обычно сгруппированные в папки с именами контроллеров, к которым они относятся. Представления будут подробно описаны в главе 21
Глава 3. Паттерн MVC , проекты и соглашения 79 Окончание табл. 3. 1 Папка или файл Описание /Views/Shared Здесь хранятся компоновки и представления , которые не являются специфическими для каких-то контроллеров . Представления будут подробно описаны в главе 21 /Views/ _Viewimport s . cshtml Этот файл применяется для указания пространств имен, которые будут включены в файлы представлений Razor, как объясняется в главе 5. Он также используется для установ ки дескрипторных вспомогательных классов (глава 23) /Views / ViewStart . cshtml Этот файл позволяет указать стандартную компоновку для механизма визуализации Razor, как описано в главе 5 /bower. j son По умолчанию этот файл скрыт. Он содержит список паке тов , управляемых диспетчером пакетов Bower (глава 6) /proj ect.j son В этом файле указаны базовые конфигурационные пара метры для проекта , в том числе применяемые им пакеты NuGet (глава 6) /Progr am . cs Этот класс конфигурирует платформу, на которой размеща ется приложение (глава 14) /Startup . cs Этот класс конфигурирует само приложение (глава 14) /wwwroot Сюда помещается статическое содержимое, такое как фай лы CSS и изображений. Кроме того, именно сюда диспет чер пакетов Bower устанавливает пакеты JavaScript и CSS (глава 6) Соглашения в проекте MVC В проекте MVC существуют два вида соглашений . Первый вид - это просто предпо ложения о то м , как про ект может быть структурирован. Например , пакеты JavaScript и CSS от независимых поставщиков, на которые вы полагаетесь , общепринято поме щать в папку www r oot/lib . Им енно там ожидают обнаружить их другие разработчш<И МVС , и сюда их будет устанавливать диспетчер пакетов. Но вы вольны п ер еименовать папку 1 ib или вообще удалить ее и хранить пакеты в другом месте. Это не помешает инфраструктуре MVC выполнить ваше приложение при условии, что элементы script и l i nk в представлениях ссылаются на местоположение , куда вы пом е стили пак еты. Второй вид соглашений вытекает из принципа соглашения по конфигурач,ии (или соглашения над конфигурач,ией, если делать акцент на пр еимуществе соглашения пе ред конфигурацией), который был одним из главных аспектов, обеспечивших попу лярность платформе Ruby оп Rails . Соглашение по конфигурации означает, что вы не должны явно конфигурировать, с1<ажем, ассоциации между контроллер ами и их пред ставлениями. Вы просто следуете определенному соглашению об именовании для сво их файлов - и все работает. Когда приходится иметь дело с соглашени ем такого вида, снижается гибкость в отношении изменения структуры проекта. В последующих раз делах объясняются соглашения, которые используются вместо конфигурации. Совет. Все соглашения могут быть изменены путем замены стандартных компонентов MVC собственными реализациями . В книге будут описаны различные способы делать это , что поможет объяснить, как работают приложения MVC, но с рассматриваемыми здесь согла шениями придется иметь дело в большинстве проектов.
80 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Следование соглашениям для классов контроллеров Классы контроллеров имеют имена, которые заканчиваются на Controller, на пример, ProductController, AdminController и HomeController. При ссылке на контроллер в другом месте проекта, скажем, в случае применения дескрипторного вспомогательного класса, указывается первая часть имени (такая как Product), а ин фраструктура MVC автоматически добавляет к этому имени слово Controller и на чинает поиск класса контроллера. Совет. Это поведение можно изменить, создав соглашение для моделей, как будет описано в главе 31. Следование соглашениям для представлений Представления и частичные представления помещаются в папку /Views/ ИмяКонт роллера. Например, представление, ассоциированное с классом ProductController, должно находиться в папке /Views/Product. Совет. Обратите внимание, что часть Controller имени класса в имени папки внутри Views не указывается, т.е. используется /Views/Product, а не /Views/ProductController. Поначалу такой подход может показаться нелогичным, но он быстро войдет в привычку. Инфраструктура МVС ожидает, что стандартное представление для метода дейс твия должно иметь имя этого метода. Например, представление, ассоциированное с методом действия по имени List (),должно называться List . cshtrnl. Таким обра зом, ожидается, что для метода действия List () класса ProductController стан дартным представлением будет /Views/Product/List. cshtrnl. Стандартное пред ставление применяется при возвращении результата вызова метода View ( ) в методе действия, примерно так: return View() ; Можно указать имя другого представления: return View("MyOtherView"); Обратите внимание, что мы не включаем в представление расширение имени фай ла или путь. При поиске представления инфраструктура МVС просматривает папку, имеющую имя контроллера, а затем папку /Views/Shared. Это значит, что представ ления, которые будут использоваться более чем одним контроллером, можно помес тить в папку /Views/Shared и MVC успешно найдет их. Следование соглашениям для компоновок Соглашение об именовании для компоновок предусматривает снабжение имени файла префиксом в виде символа подчеркивания (_J и размещение файлов компо новки в папке /Views/Shared. Стандартная компоновка применяется по умолчанию ко всем представлениям через файл /Views/_ ViewStart . cshtrnl. Если вы не хотите, чтобы стандартная компоновка применялась к представлениям, то можете изменить
Глава 3. Паттерн MVC, nроекты и соглашения 81 настройки в файле _ViewStart.cshtml (либо вообще удалить его ) , указав другую компоновку в представлении, например: @{ Layout = " -1 _MyLayout. cshtml"; Или же можете отключить компоновку для заданного представления: @{ La yout = null; Резюме В этой главе был представлен архитектурный паттерн МVС и его сравнение с не Сl{Олькими другими паттернами, с которыми вы могли сталкиваться или слышать о них ранее. В следующей главе объясняется структура проектов MVC в Visual Studio и рассматриваются важнейшие средства языка С#, которые используются при разра ботке веб-приложений МVС.
ГЛАВА 4 Важные функциональные возможности языка С # в этой главе будут описаны функциональные возможности С#, используемые при разработке веб - приложений, 1юторые не очень хорошо понимают или часто путают. Однако настоящая книга не посвящена языку С#, так что для каждой воз можности будет приводиться только краткий пример , поэтому вы можете прорабо тать примеры из остальных глав книги и задействовать нужные возможности в своих проектах . В табл . 4.1 приведена сводка для данной главы. Таблица 4. 1 . Сводка по главе Задача Избегание обращения к свойствам по ссылкам nu ll Упрощение свойств С# Упрощение формирования строк Создание объекта и установка его свойств за один шаг Добавление функциональности в класс, который не может быть модифицирован Упрощение использования делегатов и однострочных методов Применение неявной типизации Создание объектов без определения типа Упрощение использования асинхронных методов Получение имени метода или свойства клас са без определения статической строки Решение Листинг Используйте nu ll-условную 4.6-4.9 операцию Используйте автоматически реа- 4.10-4 .12 лизуемые свойства Используйте интерполяцию строк 4.13 Используйте инициализатор 4.14-4 .17 объекта или коллекции Используйте расширяющий 4.18-4 .25 метод Используйте лямбда - выражение 4 .26-4 .33 Используйте ключевое слово var 4.34 Используйте анонимный тип 4.35, 4.36 Используйте ключевые слова 4.37-4.40 asyncи await Используйте выражение nameof 4.41, 4.42
Глава 4. Важные функциональные возмо ж ности языка С# 83 Подготовка проекта для примера Для этой главы создайте в Visual Studio новый проект по имени LanguageFeatur es , используя шаблон ASP.NET Core Web Application (.NET Core) (Веб-приложение ASP.NET Core (.NЕТ Core)). Снимите отметку с флажка Add Application lnsights to Project (Добавить в проект службу Application Insights) и щелкните на кнопке ОК (рис. 4.1). ! !; Recent i ~-·!r" !f\rt.Jllr:d .с Templistes .с Visu1t!C• f itJindo~vs \ ~ Onliм wеь .N E1 Core And1oid Cloud Exten~ibllity iOS Reporting Silverlight ··1 - - ·- - " S.Ort Ьу: ~efau!t_ __ ij ASP.NП \Чf!Ь Applica tion (.NЕТ F ran\~ork} ASP.N ET Core V./eb Applicatio n (.N П Framtwoik) ." .:::: ". !~- ·~::J VisualC: VisualC~ -· -· - - ·- ~ ~,;_c!:'_J~sblled !:m!:!~ts.\~ tr/"fJ. Туре: Vi1ui1I с; Projec t t empl.ttes for cre11tin9 ASP.NET Core •pplic11 tions for Windo\>6, linux and 0) Х using .NП Core. ' ApplkottIOn lnsJ9hts О Add Applit~tion ln~ ight!; to proje<:t Optimize performonce •nd monitot usage in ycur live opplicaticn, Help ntt undtrs~nd Appl1cttio.n lr1s!ghts Pri11;,cy Stat~.ment 1 \ Br~:;:J 1 So lutionnгme Li11n9 u:g~Fe0Jtur~ ., _ _ C.reatedl1 eф;iryforsolutlon ' L~-'---·-·--"-·----~--~-~--_:_--~-----·~-~~~:~~~~-·-·~-Add:~"~j~~~::r J!__:~~ !j Locatiorn Рис. 4.1 . Выбор типа проекта Когда отобразятся различные конфигурации проекта ASP.NET, выберите шаблон Empty (Пустой), как показано на рис. 4.2 , и щелкните на кнопке ОК, чтобы создать проект. 1 1 1 1 1 ~ Microsoft Azu r~ 1 S•l«t• lemplate: 1 с - ~- ~--- -----~- -----------"'--- ----1 An empty p,oject template fo r cr9tin9 an ASP.NП Core j AS P.NEТ Core Tem piates 1 app!kэtion. This templi!!te does not have any content in ' 1 it. 11 ~ij 1~ 1 1 WebAPI \'leb ,.1 I Application 1 1 , 1 _________l jj l~~~~-~~~)-t~~:.~~~~ i__ ··-· -- ·- ·--··-~--~- -- ·--·-·~--·~ ----··----~-J ~~~~~~~~n-----·--- 10 ОHortinthecloud lfдрр S~~~--·:·: '-'--·---~·-·j 1 L DC1~ 1 --- -·-- ·-- ----- ---- ------ --- ·---··· ---------------- --- __________J Рис. 4.2 . Выбор начального содержимого проекта
84 Часть 1. Введени е в инфрастру кт уру ASP.NET Core MVC Включение ASP.NET Core MVC Шаблон проекта Empty создает проект, который содержит минимал ьную конфи гурацию ASP.NET Core без какой-либо поддержки MVC. Это значит, что содержимое заполнитель, добавляемое шаблоном Web Application (Веб-приложени е), отсутствует, но танже означает необходимость в выполнении ряда дополнительных шагов для включения МVС, чтобы заработали такие ср едства , как контроллеры и пр едставле ния . Здесь мы внесем изменения, требуемые для внлючения МVС в проекте, но по1<а не будем вдаваться в детали каждого шага . Первый шаг предусматривает добавле ние сборок .NET дл я МVС , что делается в разд еле dependencies файла proj ect . j son (листинг 4 . 1) . Листинг 4.1 . Добавление сборок MVC в файле proj ect. j son " dependencies ": { " Microsoft . NETCore . App" : "version ": 11 1.0 .0 11 , " type ": " platform" }1 }, " Microsoft . AspNetCore . Diagnostics ": "1. 0 .0", " Microsoft . AspNetCore . Server . IISintegration ": " 1 . 0 . 0 ", " Microsoft.AspNetCore . Server . Kestre l": " 1.0.О ", " Microsoft.Extensions . Logging . Console ": " 1.0.0 ", "Microsoft .AspNetCore .Mvc": "1.0.0" В разделе dependencies файла proj ect. j son перечислены сборки, которые тре буются для про екта. Мы добавили сборку Microsoft. AspNetCore. Mvc, содержащую классы МVС. Обратите внимание на добавление запятой в конце строки, предшес твующей Microsoft . AspNetCore . Mvc. Файлы конфигурации JSON чувствительны к корректному форматированию, а о добавлении запятой легко забыть и тем самым вызвать ошибку. Совет. Каждая сборка указывается с номером версии. Вы должны удостовериться , что все сборки с указанными версиями нормально работают вместе . Во время редактирования файла proj ect. j son среда Visual Studio предоставит списо к доступны х верси й сборок . Простейший подход заключается в том, чтобы указанная вами версия для Microsoft . AspNetCore . Mvc совпадала с версией существующих сборо к в разделе dependencies, который был добавлен Visual Studio , когда создавался проект. На следующем шаге инфраструктуре ASP.NET Core сообщается о необходимости использования MVC, что делается в классе Startup (листинг 4.2). Листинг 4.2 . Включение MVC в файле Startup . cs using Microsoft.AspNetCore . Builder; using Microsoft.AspNetCore . Hosting ; using Microsoft.AspNetCore . Http ; using Microsoft . Extensions.Dependencyinjection;
Глава 4. Важные функциональные возможности языка С# 85 using Mi crosoft . Extensions . Logg i ng ; narnespace LanguageFeatures { puЫic class Star tup { puЫic void Config u reServices (I Se r vi ceCollection services ) { services.AddМvc(); puЫic void Configure(IAppl i cationBu i l d e r ар р, I Host i ngEnvironrnen t env , ILogge r Factory loggerFact ory) { app.UseMvcWithDefaultRoute(); Конфигурировани е приложений ASP.NET Core МVС будет объясняться в главе 14, а пока достаточно знать, что два оператора, добавленные в листинге 4.2, обеспечивают б а зовую н а стройку MVC с примен ением стандартной конфигур ации и соглаш ений . Создание ком поненто в приложения MVC Имея настро енную инфраструктуру МVС, можно добавлять 1<0мпоненты приложения МVС , которые будут исполь зоваться для демонстрации важных языковых средств С# . Создание модели Н ачн ем с создания простого класса модели, чтобы иметь какие-то данные , с кото ры ми можно работать. Добавьте папку по имени Models и создайте в ней файл класса Product . cs с определением, прив еденным в листинге 4.3 . Листинг 4.3 . Содержимое файла Product. cs из папки Models narnespace LanguageFeatures . Models { puЬlic cla s s Product { puЫic string Narne { get ; set ; } puЫic decirnal? Price { get ; set ; puЬlic static Product[] GetP r oducts() Product kayak = new Pr oduct { Narne = "Kayak", Price = 275М }; Product l ifejacket = new Prod u ct Narne = " Lifejacket ", Price = 48 . 95М }; return new Product[] { kayak , lifejacket , n ull }; В ю~асс е Products определ е ны свойства Name и Pri c e, а также статич е ский метод по имени GetP r oducts () , который возвращает массив элементов Product . Один из эле ментов , соде ржащихся в возвращаемом из метода GetProducts () массив е, уста новлен в null.
86 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Создание контроллера и представления В примерах этой главы мы применяем простой контроллер для демонстрации ра з личных языковых средств . Создайте папку Con trollers и добавьт е в не е файл класса по имени HomeContro lle r . cs , содержимое которого показано в листинг е 4.4 . В слу чае использования стандартной конфигурации MVC инфраструктура будет по умол чанию отправлять НТГР-запросы контроллеру Home . Листинг 4.4 . Содержимое файла HomeController . cs из папки Controllers u sing Microsoft . AspNetCore . Mvc ; n amespace Lang u age Feat u res . Contro lle rs puЬlic clas s HomeControl ler : Controll er puЬlic Vi e wRes u lt I ndex() return Vi ew(new string[] { "С#", "Language", "Feat ures" }); Метод действия Index ( ) сообщает инфраструктуре MVC о необходимости визуа лизировать стандартное представление и передает ей массив строк, который должен быть включен в НТМL-разметку, отправляемую клиенту . Чтобы создать соответствую щее пр едставление, добавьте папку Vi ews/Home (сначала создав папку Views , а зат е м внутри нее паш<у Home ) и поместите в нее файл представления по имени Index . cshtml с содержимым, приведенным в листинге 4 .5 . Листинг 4.5 . Содержимое файла Index. cshtml из папки Views/Home @model I E n um eraЫe<string> @{ Layout = n ull; <!DOCTYPE html > <html> <head> <meta name= "viewport" content= "width=device-width" / > <t itle >Language Features</t itl e> </head> <body> <ul> @foreach (string s in Model ) { <li>@s</l i> </ul> </body> </html > Запустив пример приложения путем выбора пункта Start Debugging (Запустить от ладку) в меню Debug (Отладка) , вы увидите вывод , предст авленный на рис . 4.3.
Глава 4. Важные функциональные возможности языка С# 87 D langu age features Х ____ ? ~athost:sз._29_0___ с" .~ • La11guage • Feanю~s _J Рис. 4.3 . Запуск пример приложения Поскольку выводом во всех примерах в данной главе является текст, отображаемые браузером сообщения в дальнейшем будут показаны так: С# Language Features Использование null - условной оп е рац и и С помощью null -условной операции можно более элегантно обнаруживать зна чения null. Во вр емя разработки пр иложений МVС встр ечается много проверок на предмет null, т.к. нужно выяснять , содержит ли запрос специфический заголовок или значение либо содержит ли модель определенный элемент данных. Традиционно работа со значениями null требовала явных проверок, которы е могли становиться утомительны м и и подверженными ошибкам , когда требовалось инспектировать и объект, и его свойства. За счет применения null -условной операции такие проверки будут намного легч е и компактнее (листинг 4.6). Листинг 4.6 . Обнаружение значений null в файле HomeController. cs using Microsoft . AspNetCore . Mv c ; using System.Collections.Generic; using LanguageFeatures.Models; nam e space LanguageFeatures . Controlle rs puЫic class HomeController : Control ler puЬlic ViewResult Index() { List<string> results = new List<string>(); foreach (Product р in Product.GetProducts()) string name = p?.Name; decimal? price = p?.Price; results .Add (string. Format ("Name: {0}, Price: {1}", name, price)); return View(results);
88 Часть 1. Введение в инфраструктуру ASP.NET Саге MVC Статический метод GetProducts (), определенный в классе Product, возвраща ет массив объектов, который инспектируется в методе действия Index () контрол лера с целью получения списка значений Name и Price. Проблема в том, что как объект в массиве, так и значения его свойств могут быть null, т.е. нельзя прос то ссылаться нар. Narne или р. Price внутри цикла foreach, не получив исключе ние NullReferenceException. Во избежание этого используется null-условная операция: string name = p?.Name; decimal? price = p?.Price; null-условная операция обозначается знаком вопроса(?). Если значение р равно null, то переменная name также будет установлена в null. Если значение р не равно null, то переменной name будет присвоено значение свойства Person. Name . Такой же проверне подвергается свойство Price. Обратите внимание. что переменная. ноторой выполняется присваивание с применением null-условной операции, должна быть в состоянии иметь дело со значениями null, поэтому переменная price объявлена с десятичным типом, допускающим null (decimal ?). Связывание в цепочки null-условных операций Для навигации по иерархии объектов null-условные операции могут связывать ся в цепочки и превращаться в по-настоящему эффективный инструмент для упро щения нода и обеспечения безопасной навигации. В листинге 4. 7 I{ классу Product добавлено свойство с вложенными ссылками, что создает более сложную иерархию объектов. Листинг 4. 7. Добавление свойства в файле Product. cs namespace Language Feat ures.Models { puЬlic class Product { puЬlic string Narne { get; set ; } puЫic decimal? Price { g et ; set ; puЫic Product Related { get; set; } puЫic stati c Product[J GetProducts() Product kayak = new Product { Name = " Kayak" , Price = 275М }; Product lifejacket = new Product Name = " Lifejacket" , Price = 48 . 95М }; kayak.Related = lifejacket; return new Product[] ( kayak, lifejacket, null }; Каждый объект Product имеет свойство Related, которое может ссылаться на дру гой объект Product . В методе GetProducts () мы устанавливаем свойство Related
Глава 4. Ва ж ные фун к циональные возмо ж ности языка С# 89 для объекта Produ c t , представляющего каяк . В листинге 4.8 показано, как можно со единить вместе nu ll-условные опер ации для навигации по свойствам объектов. не вызывая исключение . Листинг 4.8 . Обнаружение вложенных значений null в файле HomeController. cs using Microsoft . AspN e tCore . Mv c; using System. Collecti ons . Gener ic; using LanguageFeatures . Models ; namespace Language Featu r es . Co n t r o ll ers puЫic cl ass HomeCont roller : Cont roller puЫic ViewResul t Inde x() { List<string> resu l ts = new List<st r ing>() ; foreach (P r oduct р in Product . GetProducts()) string name = p? .Name; deci mal? price = p? . Price; string relatedName = p?.Related?.Name; results .Add (string. Format ( "Name: {О}, Price: {1}, Related: {2}", name, price, relatedName)); r etur n Vi ew(results) ; n ull-условную операцию можно применять :к каждой части цепочки свойств, например: string relatedName = p? .Related?. Na me; В таком случа е переменная re latedName получит значение n u ll, когда значение null и м еет р или р . Rela ted. Иначе relatedNam e будет присвоено знач ение свойства р . Rela ted . Name . Запустив пример прилож ения, вы увидите в окне браузера следую щий вывод: Name: Kayak, Pri ce: 275, Rel ated: Lifejacket Name: Lifejacket, Pri ce: 48.95 , Related: Name: , Price: , Related: Ко мби ниро в ание null-условной операции и операции объединения с null Для установки альтернативного зн ачения, представляющего n ull, удобно комби ниров ать nu l l -условную оп е рацию (один знак вопроса) и операцию объ един е ния с nu l l (два знака вопроса), как демонстрируется в листинге 4.9. Листинг 4.9 . Сочетание операций работы с null в файле HomeController. cs using Microsoft . As pNetCore . Mvc ; using System.Coll ections.Generic; using LanguageFeatu r es . Models ;
90 Часть 1. Введение в инфраструктуру ASP.NET Core MVC namespace LanguageFeatures.Controllers { puЬl i c class HomeController : Con troller puЫic ViewResult Index() { List<string> results = new List<string>(); foreach (Product р in Product . GetProducts( ) ) string name = р? .Name ?? "<No Name>"; decimal? price = p? . Price ?? О; string relatedName = р? .Related? .Name ?? "<None>"; results.Add(string . Format( " Name: (0 ), Price : (1) , Related : {2} ", name , price , relatedName)) ; return View(results); null-условная операция гарантирует, что при навигации по свойствам объектов не возникнет исключение Nul lReferenceExcep tion , а операция объединения с null обеспечивает отсутствие значений null в результатах, отображаемых в браузере. Если вы запустите пример приложения, то увидите в окне браузера следующий вывод: Name : Kayak , Price : 275 , Related : Lifejacket Name : Lifejacket, Price : 48 . 95 , Re l ated : <None> Name: <No Name>, Price : О, Related : <None> Использование автоматически реализуемых свойств В языке С# поддерживаются автоматически реализуемые свойства, которые мы применяли при определении свойств класса Person в предыдущем разделе: namespace LanguageFeatures .M odels { puЫic class Product { puЫic string Name { get; set; } puЫic decimal? Price { get; set; puЬlic Product Related { get; set; } puЬlic static Product(] GetProducts() Product kayak = new Product { Name = " Kayak ", Price = 275М }; Product lifejacket = new Product Name = " Lifejacket ", Price = 48 . 95М }; kayak . Related = lifejacket ; return new Product[J { kayak , lifejacket , nul l };
Глава 4. Важные функциональные возможности языка С# 91 Данное средство дает возможность определять свойства . не реали зуя блоки кода get и set. Средство автоматически реализуемых свойств позволяет трактовать сле дующее опр еделение свойства: puЫic string Name { get ; set; } как экви в ал ентное приведенному ниже коду: puЫic st r ing Name { get { return name ; set {name =value; } Ср едст в а подобного типа и з вестны как "синтаксический сахар", который делает р аботу с С# более приятной (за счет устранения в этом случа е избыточного кода , дуб л ируем ого в итоге для 1шждого свойства), однако существенно не изм еняет поведение яз ы ка. Термин " сахар" может показаться уничижит ельным, но любые улучшения. ко торые облегчают написание и сопровождение кода . приносят пользу - особенно в крупных и сл ожных про ектах. И спользо ва ни е и ни циал и заторов автомат и чески реализуемых свойст в Автоматич е ски р е ализуе мые свойства поддерживаются, н ачиная с ве р сии С # 3.0. В последней версии С# доступны инициализаторы для автоматич ески реализуемых свойств . которые позволяют устанавливать начальны е значения. не требуя примене ния конструкторов (листинг 4 . 10) . Листинг 4.1 О. Использование инициализатора автоматически реализуемого свойства в файле Product. cs n a mespace LanguageFeatures . Models { puЫic c l ass Product { puЫic string Name { get; s et; } puЬlic string Category { get; set; puЬlic decimal? Pri ce { get; set; } puЫi c Product Related { get ; set ; } puЫic stati c Product[ J GetP r oducts ( ) Product kayak = new Product { Name = "Kayak" , Category = "Water Craft", Price = 275М }; Product l i fejacke t = new Product { Name = "Li fejacket", Price= 48.95М }; ka y ak . Re l ated = lifejacket; "Watersports"; return new Produc t [] { kayak , life jacket , null } ;
92 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Присваивание значения автоматически реализуемому свойству не препятствует применению установщика для изменения свойства в более позднее время, а всего лишь приводит в порядок код для простых типов с конструкторами. которые содер жат список присваиваний свойств, обеспечивающих наличие стандартных значений. В приведенном примере инициализатор присваивает свойству Category значение 11 Wa tersports 11 • Начальное значение может быть изменено, что и делается при со здании объекта k ayak . когда ему указывается значение 11 Water Craft 11 • Создание автоматически реализуемых свойств только для чтения Можно создать свойство только для чтения, используя инициализатор и опуская ключевое слово set из определения автоматически реализуемого свойства, 1юторое имеет инициализатор (листинг 4.11). Листинг 4.11. Создание свойства только для чтения в файле Product. cs namespace LanguageFeatures.Models { puЬlic class Product { puЫic string Name { get ; set ; } puЫ i c string Category { g et; set; puЫic decimal? Price { get ; set ; } puЬlic Product Rel ated { get; set; } puЬlic bool InStock { get; } = true; puЫic static Product [ J GetProducts() Product kayak = new Product Name = " Kayak 11 , }; Category = 11 Wate r Craft ", Price = 275М Product l ifejacket = new Product { Name = 11 Li fe j acket 11 , Price = 48. 95М }; kayak.Related = l ifejacket ; 11 Watersports" ; return new Product[] { kayak , l ifejacket , null } ; Свойство I nS tock инициализируется значением true и не может быть изменено; тем не менее, ему можно присвоить значение внутри конструктора типа, кmt показа но в листинге 4 . 12. Листинг 4.12. Присваивание значения свойству только для чтения в файле Product. cs namespace LanguageFeatures . Models { puЫic class Product ( puЫic Product(bool stock = true) InStock = stock;
Глава 4. Важ ные функциональные возмо ж ности языка С# 93 puЬl ic string Name { get; set; } puЫic string Category { ge t; set ; puЫic decimal ? Price { get; set; } puЫic Product Related { ge t; s et ; } puЫic Ьооl InStock { get; } puЫic st atic Pr od uct[] GetProduct s(} Product kayak = new Product { Name = " Kayak ", }; Category = " Water Craft ", Pri ce = 275М "Water spor ts"; Product lifejacket = new Product (false) Name = "Lifejacket", Price = 48.95М }; kayak . Re l ated = l ifejacket ; return new Product[J { kayak, lifejacket, null }; Конструктор позволяет указывать в 1шчестве аргумента значение для свойства, допускающего только чтение, и если значение не пр едоставлено, тогда он устанавли вает свойство в стандартное значение tru e . Посл е установки конструктором значе ния свойства оно не может быть изменено. Исп ользова ние интерполяции строк Традиционным инструментом С# для образования строк, содержащих значения данных, является метод string. F o r mat ( }. Вот пример применения этого приема в контроллер е Horne : resul ts . Add ( string. Format ( "Name: {О}, Price: { 1}, Related: { 2}", name, price, relatedName) ) ; В С# 6.0 появилась поддержка другого подхода , называемого интерполяцией строк, который позволяет избежать необходимости гарантировать, что ссылки {О} в шаб лоне строки соответствуют переменным, указанным в качестве аргументов. Взамен интерполяция строк использует имена переменных напрямую (листинг 4.13). Листинг 4.1 З. Применение интерполяции строк в файле HomeController. cs using Microsoft . AspNetCore . Mvc ; using System.Coll ections.Generic; using LanguageFeat ur es .M o d e ls; namespace LanguageFeat u res . Controllers puЫic class HomeContro l ler : Contro ll er puЬli c ViewRe s ult Index() { List<string> r e sults = new List<s t ring>();
94 Часть 1. Введение в инфрастру к туру ASP.NET Core MVC foreach (Product р in Product . GetProducts()) string name = p? .Name ?? " <No Name> "; decimal? price = p? . Price ?? О; string relatedName = p? . Related? . Name ?? " <None>"; results . Add($"Name : {name}, Price: {price}, Related: {relatedName}"); return View(results) ; Интерполируемая строка снабжается префиксом в виде символа $ и содержит "дыры", которые представляют собой ссылки на значения, содержащиеся внутри фи гурных скобок ( { и }). Когда строка оценивается, "дыры" заполняются текущими зна чениями указанных переменных и констант. Среда Visual Studio обеспечивает поддержку средства IntelliSense для создания интерполированных строк и предлагает список доступных членов, когда набирается символ {; это помогает минимизировать количество опечаток, а результатом оказы вается формат строки, который легче понять. Совет. Интерполяция строк поддерживает все спецификаторы формата, которые доступ ны для метода string . Format (} . Спецификаторы формата включаются в виде части "дыры", поэтому $ " Pri ce : {price : С2 }" сформатирует значение price как дене ж ное значение с двумя десятичными цифрами . Использование инициализаторов объектов и коллекций При создании объекта в статическом методе GetProducts () класса Product при менялся иниц,иализатор объекта. который позволяет создавать объект и указывать значения его свойств за один шаг, например: Product kayak = new Product Name = "Kayak", Category = "Water Craft", Price = 275М }; Это еще одна форма "синтаксического сахара", делающая язык С# легче в исполь зовании. Без такого средства пришлось бы вызывать конструктор Product и затем применять вновь созданный объект для установки всех его свойств: Product kayak = new Product() ; kayak.Name = " Kayak "; kayak . Category = " Water Craft "; kayak.Price = 275М ;
Глава 4. Ва ж ные функциональные возмож ности язы ка С# 95 Свя з анное с ним сред ство - ин.ич,иализатор колле кции - позволя ет создавать колл е1щию и указ ывать ее содержимое за один шаг. Без такого инициализатора со здани е , к примеру. м ассива потребовало бы указания размера и элементов массива по отдельности, как показ ано в листи н ге 4.14 . Листинг 4 .14. Инициализация массива в файле HomeController. cs using Microsoft . AspNetCore . Mvc ; using System . Co l lections . Gener i c ; using LanguageFeatures . Models ; namespace LanguageFeature s. Control l ers puЫic class HomeController : Controlle r puЫic Vi ewResult Index() { string[] names = new string[З]; names [О] "ВоЬ"; names[1] = "Joe" ; names [2] = "Alice"; return View("Index", names); Инициализатор коллекции дает возм ожность ука зать содержимое массива как часть е го конструирования , что неявно предоставля ет компилятору ра зме р м ассива (листинг 4.15). Листинг 4.15. Использование инициализатора коллекции в файле HomeController. cs using Microsoft . Asp NetCo re. Mvc ; using System.Col lections . Ge neric ; us i ng LanguageFeatures . Mod el s ; namespace LanguageFeature s. Control l ers puЫic class HomeController : Controller puЬlic ViewResult Inde x () { return View("Index", new string[J { "ВоЬ", "Joe", "Alice" }) ; Эл ементы массива задаются м ежду символами { и }, что позволяет получить более кратко е определ е ние коллекции и делает возможным определять ко ллекцию внутри вызова метода. Код в листинге 4.15 дает тот же результю~ что и код в листинге 4.14, и если вы запустит е пример приложения, то получите в окне браузера следующий вывод : ВоЬ Joe Alice
96 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Использование инициализатора индексированной коллекции В С# 6 улучшен способ применения инициализаторов коллекций для создания кол лекций , использующих индексы, таких как словари. В листинг е 4.16 приведен код метода действия Index (), переписанный для определения коллекции с помощью под хода С# 5 к инициализации словаря. Листинг 4.16. Инициализация словаря в файле HomeController. cs using Microsoft .A spNetCore . Mvc ; using System . Col l ections . Generic ; using LanguageFeatures . Models ; n amespace LanguageFeatures . Cont rollers puЫic class HomeController : Controller puЫic ViewResult Index() { Dictionary<string, Product> products = new Dictionary<string, Product> { { "Kayak", new Product { Name = "Kayak", Price = 275М } } , { "Lifejacket", new Product{ Name = "Lifejacket", Price = 48. 95М } }; return View("Index", products.Keys); Синтаксис для инициализации такого типа коллекции слишком сильно полагается на символы { и }, особенно когда значения коллекции создаются с применением иници ализаторов объектов. В С# 6 предлагается более естественный подход к инициализации индексированной коллекции, который согласован со способом извлечения или модифи кации значений после того, как коллекция была инициализирована (листинг 4.17). Листинг 4.17 . Использование синтаксиса инициализатора коллекции С# 6 в файле HomeController. cs using Microsoft.AspNetCore.Mvc; using System . Co llections.Generic; u sing LanguageFeatures . Models ; n amespace LanguageFeatures.Controll ers puЬlic c l ass HomeController : Controlle r puЫ i c ViewResult Index() { Dictionary<string, Product> products = new Di ctionary<string , Product> { [ "Kayak"] = new Product { Name = "Kayak", Price = 275М } , [ "Lifejacket"] = new Product { Name = "Lifejacket", Price = 48. 95М }; return View( " Index ", products.Ke ys) ; Результат остается прежним , т.е. создание словаря, ключами ноторого являются Kayak и Lifejacket, а значениями - объекты Product, но элементы создаются с применением системы обозначений для индексов, используемой в других операциях
Глава 4. Важные функциональные возможности языка С# 97 с коллекциями. Запустив пример приложения, вы увидите в окне браузера следую щий вьmод: Kayak Lifejacket Использование расширяющих методов Расширяющие методы - это удобный способ добавления методов в 1Uiaccы, вла дельцем 1<оторых вы не являетесь и не можете модифицировать напрямую. В листин ге 4.18 приведено определение 1Uiacca Sh oppingCart , добавленного в папку Models в виде файла ShoppingCart . cs, который представляет коллекцию объектов Product. Листинг 4.18. Содержимое файла ShoppingCart. cs из папки Мodels using System . Co llections.Generic ; n a mespace LanguageFeatures . Models puЫic class ShoppingCart { puЫic IEnumeraЬle<P r od uc t> Products { get ; set; } Это простой класс, который действует в качестве оболоч1ш для коллекции List объектов Product (в данном примере необходим лишь эл е ментарный класс). Предположим , что нам требуется возможность определения общей стоимости объек тов Product в классе ShoppingCart , но мы не можем изменить сам класс, возможно потому, что он поступил от третьей стороны, и мы не располагаем его исходным ко дом. Для добавления нужной функциональности можно применить расширяющий ме тод . В листинге 4.19 показан класс MyExtensionМethods, также добавленный в папку Models в виде файла MyExtensionМet hods . cs. Листинг 4.19. Содержимое файла МyExtensionМethods. cs из папки Models namespace LanguageFeatures . Mode l s { puЫic static c l ass MyExtens i o nMethods { puЫic static decimal Tota lPr ices(this ShoppingCart cartParam) { decimal total = О; foreach (Product prod in cartParam . Products) total += prod?.Price ? ? О ; return total ; Ключ е во е слово this, расположенное перед первым параметром, помечает TotalPrices () ка~< расширяющий метод. Первый параметр указывает .NЕТ, к какому классу может применяться расширяющий метод - I< ShoppingCart в данном случае. На экземпляр класса ShoppingCart, к которому применен расширяющий метод. мож но ссылаться с использованием параметра cartParam. Расширяющий метод прохо дит по объектам Product в ShoppingCa r t и возвращает сумму значений их свойств Product . Price. В листинге 4.20 демонстрируется применение расширяющего метода в методе действия контроллера Horne.
98 Часть 1. Введение в инфраструктуру ASP.NET Core MVC На заметку! Расширяющие методы не позволяют нарушать правила доступа, которые клас сы определяют для своих методов , полей и свойств. С помощью расширяющего метода функциональность класса можно расширить, но с использованием только тех членов, к которым в любом случае имеется доступ. Листинг 4.20. Применение расширяющего метода в файле HomeController. cs u sing Micr oso ft . AspNetCore . Mvc ; using Syst e m . Collections . Generic ; using Language F eatures . Mod e ls ; namespace LanguageFeatures . Control l ers puЫic clas s HomeController : Co n tro l l e r puЫic ViewResult I ndex() { ShoppingCart cart = new ShoppingCart { Products = Product.GetProducts() }; decimal cartTotal = cart.TotalPrices(); return View (" Index", new string [] { $" Total: { cartTotal: С2}" } ) ; Вот ключевой оператор: d ecimal cartTotal = cart . Tota l Prices() ; Метод TotalPrices () вызывается на объект е ShoppingCart, как е сли бы он был частью класса ShoppingCart , хотя он является расширяющим методом, которы й оп ределен в совершенно другом класс е . Среда .NET будет обнаруживать расширяющи е классы , если они находятся в области действия текущего класса , т. е. являются частью того же самого пространства имен или пространства имен, которо е указано в опера тор е using. Запустив пример приложения, вы увидите в окне брауз е ра следующий вывод: Total : $323 . 95 Применение рас ш иря ющих методов к интерфейсу Можно такж е создавать расширяющие методы, которые применяются к интерфей су , что позволит вы з ывать таки е расширяющие методы для вс ех кл ас с ов , р е ализую щих этот интерф ейс . В листинге 4.21 приведен код класса ShoppingCart, модифици рованный для реализации интерфейса I Enume raЫ e<P r oduct>. Листинг 4.21 . Реализация интерфейса в файле ShoppingCart. cs using System.Collections; using System . Collection s.Generic ; namespace LanguageFeatur e s . Models puЬlic class ShoppingCart : IEnumeraЫe<Product> puЫic IEnumeraЫe<Product> Pr oducts { get; set;
Глава 4. Важные функциональные возможности языка С# 99 puЫic IEnumerator<Product> GetEnumerator () return Products.GetEnumerator(); IEnumerator IEnumeraЬle.GetEnumerator() return GetEnumerator(); Теперь расширяющий метод можно изменить так, чтобы он работал с интерфей со м IEnumeraЫe<Pro du ct> (листинг 4.22). Листинг 4.22. Модификация расширяющего метода в файле МyExtensionМethods. cs using System.Collections.Generic; namespac e Language Fea tures . Mo de l s puЫic static cl a s s MyExt ensi onMethods puЫic static decimal TotalPrices (this IEnumeraЫe<Product> products) decimal total = О ; for each (Product prod in produc ts) total += prod?.Price ?? О; return total ; тип первого параметра был изменен на I EnumeraЫe<Pro du ct>, а это значит, что цикл fo r e a ch в теле метода работает непосредственно с объектами Prod uct . Переход на использование упомянутого интерфейса означает, что мы можем подсчитать об щую стоимость объектов Produ ct , перечисляемых посредством любого интерфейса IEnumeraЫe<Produc t > . что включает не только экз емпляры Shoppi ngCart , но также массивы объектов Produ c t (листин г 4.23) . Листинг 4.23. Применение расширяющего метода к массиву в файле HomeController. cs using Microsoft . As p NetCore . Mvc ; using System . Collections . Generic ; using LanguageFeatures . Mode l s ; namespace LanguageFe atures . Co ntro ll ers puЫic cl ass HomeControll e r : Controller puЫic ViewResu l t Index() { ShoppingCart cart = new ShoppingCart { Products = Product . GetPr oducts() }; Product [] productArray = { new Product {Name = "Kayak", Price = 275М}, new Product {Name = "Lifejacket", Price = 48.95М} }; decimal cartTotal = cart.TotalPrices(); decimal arrayTotal = productArray.TotalPrices();
100 Часть 1. Введение в инфраструктуру ASP.NET Core MVC return View("Index", new string[] $ "Cart Total: { cartTotal: С2} " , $ "Array Total: { arrayTotal: С2}" } ) ; Если вы запустите проект, то увидите показанные ниже результаты, которые де монстрируют. что расширяющий метод возвращает один и тот же результат, незави симо от способа перебора объектов Product: Cart Total : $323 . 95 Array Total: $323 . 95 Создание фильтрующих расширяющих методов Последний аспект расширяющих методов, о котором необходимо упомянуть - возможность их использования для фильтрации коллекций объектов. Расширяющий метод, который оперирует на интерфейсе IEnumeraЫe<T> и также возвраща ет IEnumeraЫe<T>. может задействовать ключевое слово yield, чтобы применить кри терий отбора к эл ементам в источнике данных с целью ген е рации сокращенного на бора результатов. В листинге 4.24 представлен такой метод, который добавляется в класс MyExtensionMethods . Листинг 4.24 . Добавление фильтрующего расширяющего метода в файле МyExtensionМethods . cs using System . Collections . Generic ; namespace LanguageFeatures.Models { puЫic static class MyExtensionMethods puЫic static decimal TotalPrices(this IEnumeraЫe<Product> products) { decimal total = О; foreach (Product prod in products) total += prod?.Price ?? О ; return total ; puЫic static IEnumeraЫe<Product> FilterByPrice( this IEnumeraЫe<Product> productEnum, decimal minimumPrice) foreach (Product prod in productEnum) { if ( (prod?. Price ?? О) >= minimumPrice) { yield return prod; } Расширяющи й метод по имени FilterByPrice () принимает дополнительный параметр. который позволяет фильтровать товары, так что в результате возвраща ются объекты Product, значение свойства Price которых совпадает или превыша ет значение, указанное в параметре. Использование этого метода демонстрируется в листинге 4.25.
Глава 4. Важные функциональные возможности языка С# 101 Листинг 4.25. Применение фильтрующего расширяющего метода в файле HomeController. cs using Microsoft . AspNetCore.Mvc; using Sys tern . Collections . Generic ; using LanguageFeatures . Models ; narnespace LanguageFeatures . Controllers puЫic class HorneController : Controller puЫic ViewResult Index() { Product[J productArray = { new Product {Narne "K aya k", Price = 275М} , new Product {Narne "L ifejacket ", Price = 48 . 95М), new Product {Name = "Soccer ball", Price = 19.SOM}, new Product {N ame = "Corner flag", Price = 34. 95М} }; decimal arrayTotal = productArray.FilterByPrice(20) .TotalPrices(); return View("Index", new string[] { $ 11 Array Total: {arrayTotal:C2}" }) ; При вызове метода FilterByPrice () на массиве объектов Product метод TotalPrices () получает и использует для подсчета суммы только те из них, которые стоят больше $20. Запустив пример приложения, вы увидите в окне браузера следу ющий вьmод: Total : $358 .90 Использование лямбда-выражений Лямбда-выражения - это средство, которое служит причиной многочисленных заблуждений и не в последнюю очередь из-за того, что упрощаемое им средство само вызывает путаницу. Чтобы понять решаемую задачу, рассмотрим расширяющий ме тод FilterByPrice (), который был определен в предыдущем разделе. Метод реали зован так, что он может фильтровать объекты Product по цене, а это значит. что если вы захотите фильтровать объекты по названию, то вам придется создать второй ме тод наподобие приведенного в листинге 4.26. Листинг 4.26. Добавление фильтрующего метода в файле МyExtensionМethods . cs using Systern.Collections.Generic; namespace LanguageFeatures . Models puЫic static class MyExtensionMethods puЫic static decirnal TotalPrices(this IEnurneraЫe<Product> products) { decirnal total = О ; foreach (Product prod in products) total += prod?.Price ?? О ; return total ;
102 Часть 1. Введение в инфраструктуру ASP. NET Соге MVC puЫic static IEnumeraЫe<Product> Fi l te r ByPrice( this IEn umeraЬle<Pro d uct> productEnum , decimal minimumPrice) { foreach (P r oduct prod in p r oductEnum) { if ((prod?.Price ?? 0) >= minimumPrice) { yield return prod ; puЫic s tatic IEnumeraЫe<Product> FilterByName ( this IEnumeraЫe<Product> productEnum, char firstLetter) { foreach (Product prod in productEnum) { if (prod?.Name?[O] == firstLetter) { yield return prod; В листинге 4.27 демонстрируется прим енение в контролл ере обоих фильтрующих мет одов для с оздания двух ра зных итоговых сум м . Листинг 4.27. Использование двух фильтрующих методов в файле HomeController. cs using Mi crosoft . AspNetCore . Mvc ; using System . Col l ections . Generic ; using LanguageFeat u res . Mode l s ; namespace LanguageFeatures . Cont rollers puЫic clas s HomeControl ler : Co n troller puЫ i c ViewResult Index() { Produc t[] pr oductArray = { }; new Product {Name " Kayak", Price = 275М} , new Prod u ct {Name " Lifejacket", Price = 48 . 95М} , new Product {Name "Soccerball", Price 19.50М), new Product {Name " Corner flag ", Price = 34 . 95М} decirnal priceFilterTotal = productArray.FilterByPrice(20) .TotalPrices(); decimal nameFilterTotal = productArray.FilterByName('S') .TotalPrices(); return View( 11 Index 11 , new string[] { $ 11 Price Total: {price FilterTotal: С2} 11 , $ 11 Name Total: {nameFil terTotal: С2} " }) ; Первый фильтр отбирает все товары с ценой $20 и выше, а второй фильтр - то вары с названиями, начинающимися на букву S. После запуска примера приложения вы увидите в окне браузера следующий вывод : Price Total : $358 . 90 Name Total : $19 . 50
Глава 4. Ва ж ные функциональные возмо ж ности языка С# 103 Определ ение функций Описанный выше процесс можно повторять до бесконечности и создавать филь трующие м етоды для каждого интересующего свойства и сочетания свойств. Более элегантный подход предусматривает отделение кода, обраб атывающего перечисле ние, от критерия отбора. В С# это делается легко, поскольку функции разрешено пе р едавать нак объекты . В листинге 4.28 показан единственный расширяющий метод, 1юторый фильтрует перечисление объектов Product , но делегирует отдельной функ ции решени е о том, каки е из них должны быть включ е ны в р езультат. Листинг 4.28. Создание универсального фильтрующего метода в файле МyExtensionМethods . cs using System . Collections . Generic ; using System; namespace LanguageFeatures . Mode ls puЫic static class MyExtensionMe t hods puЫic static decimal TotalP r ices(th is IEnumeraЫe<Product> products) decimal total = О; foreach (Product prod i n p r oducts) total += prod? . Price ?? О; }. return total; puЬlic static IEnumeraЫe<Product> Filter( this IEnumeraЬle<Product> productEnum, Func<Product, bool> selector) { foreach (Product prod in productEnum) if (selector(prod)) { yield return prod; Вторым аргументом метода Fil ter () является функция, которая принимает объ ект Product и возвращает значение типа boo l. М етод Filter () вызывает эту фун кцию для каждого объекта Product и включает его в результат. если функция воз вращает true . Для применения метода Filter () можно указать метод или создать автономную функцию (листинг 4.29). Листинг 4.29. Использование функции для фильтрации объектов Product в файле HomeController. cs using Microsoft . AspNetCore . Mvc ; us i ng System . Collections .G e neric; using LanguageFeatures . Models ; using System; namespace Language Featu res . Contro l lers puЫic class HomeController : Controller
104 Часть 1. Введение в инфраструктуру ASP.NET Core MVC bool FilterByPrice(Product р) { return (p?.Price ?? 0) >= 20; puЫic ViewResult Index() { Prod uct [ ] productArray = { }; new Product {Name " Kayak", Price = 275М} , new Product {Name " Lifejacket", Price = 48 .95М} , new Product {Name " Soccer ball" , Price 19.SOM} , new Product {Name " Corner flag", P rice = 34 . 95М} Func<Product, bool> nameFilter = delegate (Product prod) { return prod?. Name? [О] == 'S' ; }; decirnal priceFilterTotal = productArray .Fi lter(FilterByPrice) . TotalPrices () ; decirnal nameFilterTotal = productArray .F i lter(narneFilter) . TotalPrices О ; return View( " Index ", new string [] { $ "P rice To ta l: {priceFi l terTota l: C2} ", $ " Name Total : {nameFilterTotal : C2} " }) ; Ни один из подходов не идеален. Определение методов вроде FilterByPrice () засоряет определение класса. Создание объекта Func<Product, boo l > устраняет данную проблему. но сопряжено с неудобным синтаксисом, который труден для вос приятия и сопрово:нщения. Именно эту задачу решают лямбда-выраж ения, позволяя определять функции более элегантным и выразительным способом, как показано в листинге 4.30 . Листинг 4.30. Использование лямбда-выражений в файле HomeController. cs using Microsoft.AspNetCore .Mvc; using System . Collections .Generic ; using Languag eFeat ures.Models; using System ; namespace Lang u ageFeatures .Co nt rol ler s puЫic c l ass HomeContro l ler : Con t rolle r puЫic ViewResult Index() { Product [] productArray = { 1; new Product {Name " Kayak", Price = 275М} , new Product {Name " Lifej acket", Price = 48 . 95М} , new Product {Name " Soccer ball ", Price 1 9.50М} , new Product {Na me " Corner flag", Price = 34 .95М} dec i mal priceFilterTotal = productArray . Filter(p => (p? . Price ?? О) >= 20) . Tota lP rices(} ;
Глава 4. Важ ные функциональные возмо ж ности языка С# 105 decimal nameFilterTotal = productArray .Filter(p=>p?.Name?[O] == 'S') . TotalPrices(); return View( " Index ", new string[] $"Price Total: {priceFilterTotal : C2} ", $ " Name Total : {nameFilterTotal : C2}" }) ; Лямбда-выражения выделены полужирным. Пар аметры выражаются без указания типа, который будет выведен автоматически. Символы => можно читать как "направ ляется в" и связывают параметр с результатом лямбда-выражения. В рассматривае мом примере параметр Product по имени р направляется в результат bool, который будет равен true, если значение свойства Price эквивалентно или превышает 20 в первом выражении или если значение свойства Name начинается с буквы S во втором выражении. Этот код работает тем же самым образом, что и отдельный метод и деле гат в виде функции, но он короче и для большинства людей легче в восприятии. Другие формы лямбда-выражений Представлять логику делегата посредством лямбда-выражения вовсе не обязательно . С тем же успехом можно вызвать метод, подобный следующему: prod => EvaluateProduct(prod) Если требуется лямбда - выражение для делегата, который имеет несколько параметров, то параметры должны быть заключены в круглые скобки: (prod, count) => prod . Price > 20 && count >О И, наконец, если в лямбда-выражении необходима логика, которая требует более одного оператора, то ее можно реализовать, используя фигурные скобки ( { } ) и завершая блок опе ратором return: (prod , count) => // ... несколько операторов кода ... return r esult ; Вы не обязаны применять лямбда-выражения в коде, но они служат изящным способом вы ра же ния сложных функций в читабельной и ясной манере. Вы будете часто встречать лямб да-выражения в примерах, приводимых в этой книге. Использование методов и свойств в форме лямбда-выражений В С# 6 поддержка лямбда-выражений была расширена так, что их можно при менять для реализации методов и свойств. Во время разработки приложений MVC, особенно при написании контроллеров, часто появляются методы, которые содержат единственный оператор, выбирающий данные для отображения и пр едставление для визуализации. В листинге 4.31 приведен код метода действия Index (), переписан ный в соответствии с таким общим шаблоном.
106 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Листинг 4.31. Создание общего шаблона действия в файле HomeController. cs using Microsoft . AspNetCore . Mvc ; using System . Col l ections . Generic ; using LanguageFeatures . Models ; using System; using System.Linq; namespace LanguageFeatures.Controllers puЫic c l ass HomeController : Controlle r puЬlic ViewResult Index() { return View(Product.GetProducts() .Select(p => p?.Name)); Метод действия Index () получает от статического метода Product. GetProducts () коллекцию объектов Product и с помощью LINQ строит проекцию значений свойств Name. Затем эта проекция применяется в качестве модели представления для стан дартного представления. Запустив пример приложения, вы увидите в окне браузера следующий вывод: Kayak Lifejacket В окне браузера также будет присутствовать пустой элемент списка, потому что метод GetProducts () включает в свой результат ссылку null, но в настоящем разде ле главы это неважно. Когда тело метода состоит из единственного оператора, его можно переписать в виде лямбда-выражения (листинг 4.32). Листинг 4.32. Представление метода действия как лямбда-выражения в файле HomeController. cs using Microsoft . AspNetCore . Mvc; using System . Collect ions.Generic ; using Lang uage Features.Model s ; using System; using System . Linq ; namespace LanguageFeatures.Control l ers puЫic class HomeController : Controller puЫic ViewResul t Index () => View{Product.GetProducts{) .Select{p => p?.Name)); Лямбда-выражения для методов позволяют опустить ключевое слово return и ис пользуют символы = > (направляется в) для связывания сигнатуры метода (включ ая аргум енты) с его реализацией. Метод Index (), показанный в листинге 4.32, работает точно так же, как метод Index () из листинга 4.31, но выражается более лаконично. Тот же самый базовый подход можно также применять для определения свойств . В листинге 4.33 демонстрируется добавление в класс Product свойства, которое ис пользует лямбда-выражение.
Глава 4. Ва ж ные функциональные возмо ж ности языка С# 107 Листинг 4.33 . Представление свойства как лямбда-выражения в файле Product. cs namespace LanguageFeatures.Models { puЫic class Product { puЫic Product(bool stock = true) InStock = stock ; puЫic string Name { get ; set ; } puЫic string Category { get ; set; " Watersports "; puЫic decimal ? Price { get; set; } puЬlic Product Related { get ; set; } puЫic bool InStock { get; } puЫic bool NameBeginsWi thS => Name? [О] 'S' ; puЬlic static Product[] GetProducts() { Product kayak = new Product { Name = " Kayak ", }; Category = " Water Craft ", Price = 27 5М Product lifejacket = new Product(false) { Name = " Lifejacket ", Price = 48.95М }; kayak . Related = lifejacket ; return new Product[ ] { kayak, lifejacket, null ); Использование автоматического выведения типа и анонимных типов Ключевое слово var языка С# позволяет определять локальную переменную без явного указания ее типа, как показано в листинге 4.34. Такой прием называется выведением типа или неявной типизацией. Листинг 4.34. Использование выведения типа в файле HomeController. cs using Microsoft.AspNetCore . Mvc ; using System . Col l ections . Generic ; using LanguageFeatures . Mode ls; using System ; using System.Linq; namespace LanguageFeatures.Controllers puЫic class HomeContro l ler : Controller puЫic ViewResult Index() { var names = new [] { "Kayak", "Lifejacket", "Soccer ball" } ; return View(names);
108 Часть 1. Введение в инфраструктуру ASP. NET Core MVC Речь идет вовсе не о том, что переменная myVar iaЫe не имеет типа; мы всего лишь предложили компилятору самостоятельно вывести тип из кода. Компилятор ис следует объявление массива и решает, что он является строковым. Выполнение при мера дает следующий вывод: Kayak Li fejacket Soccer ball Использование анонимных типов Комбинируя инициализаторы объектов и выведение типов, можно создавать про стые объекты модели представления, которые удобны для передачи данных между контроллером и представл ением. без необходимости в определении класса или струк туры (листинг 4.35). Листинг 4.35 . Создание анонимного типа в файле HomeController. cs using Microsoft . AspNetCore . Mvc ; using System . Co l lect i ons.Generic ; using LanguageFea tures . Models ; u sing System ; us ing System.Linq ; namespa ce LanguageFeatures.Controllers puЫic class HomeC ontroller : Contr ol l e r p u Ыic ViewResult Index() { var products = new [] { new { Name = "Kayak", Price = 275М } , new { Name = "Lifejacket", Price = 48. 95М } , new { Name = "Soccer Ьall", Price = 19.SOM}, new { Name = "Corner flag", Price = 34. 95М } }; return View(products.Select(p => p.Name)); Каждый объект в массиве product s относится к анонимному типу. Это не значит. что объекты являются динамическими в том смысле, в каком считаются динамически ми переменные JavaScript. Это просто означает, что определени е типа будет создано ав томатически компилятором. Строгая типизация по-прежнему обеспечивается. Скажем. вы можете получать и устанавливать только те свойства, которые были определены в инициализаторе. Запустив пример. вы увидите в окне браузера следующий вывод: Kayak Lifejacket Soccer ball Corner f lag Компилятор С# генерирует класс на основе имени и типов параметров в инициа лизаторе. Два анонимно типизированных объекта, которые имеют те же самые имена и типы свойств, будут относиться к одному и тому же автоматически сгенерированно му классу. В результате все объекты в массиве products получат один и тот же тип, поскольку они определяют те же самые свойства.
Глава 4. Ва ж ные функциональные возмо ж ности языка С# 109 Совет. Для определения массива анонимно типизированных объектов должно применять ся ключевое слово var , т.к. тип не будет создан до тех пор , пока код не скомпилирует ся , и его имя не известно. Все элементы в массиве анонимно типизированных объектов обязаны определять те же самые свойства , иначе компилятор не сможет выяснить тип массива . В целях демонстрации изменим вывод в листинге 4.36, чтобы отображать имя типа вместо значения свой ства Name. Листинг 4.36 . Отображение имени анонимного типа в файле HomeController. cs using Microsoft . AspNetCore . Mvc ; using System . Col lections.Generic; using LanguageFeatures . Models ; using System ; using System.Linq ; namespace LanguageFeatures . Controllers puЫic class HomeController : Contro l ler puЬlic ViewResult Index() { var products = new [] { }; new { Name " Kayak", Price = 275М }, new { Name " Lifejacket ", Price = 48.95М } , new { Name " Soccer ball ", Price 19.50М }, new ( Name "Corner flag", Price = 34 .95М } return View(products.Select(p => p.GetType() .Name)); Всем объектам в массиве был назначен один и тот же тип, что можно увидеть , запустив пример. Имя типа не выглядит дружественным к пользователю, но оно и не рассчитано на непосредственное исполь з ование, к тому же в вашем случае может оказ аться другим: <>f~AnonymousType0'2 <>f ~A nonymousType0 ' 2 <>f ~AnonymousType0 ' 2 <>f~AnonymousTy pe0' 2 Использование асинхронных методов Одни м из недавних крупных добавлений к языку С# явля ется улучшение спосо ба работы с асинхронными методами. Асинхронные методы осуществляют возврат и выполняют работу в фоновом режиме с уведомлением о ее завершении, позволяя коду в это время заниматься другими действиями. Асинхронные методы - важный инс трумент при устранении узких мест в коде; они позволяют приложениям извл екать преимущества от наличия нескольких процессоров и процессорных ядер. выполняя работу в параллельном режиме. В инфраструктуре МVС асинхронные методы могут применяться для увеличения общ ей прои зв одительности приложения, предоставляя серверу большую гибкость от-
11 О Часть 1. Введение в инфраструктуру ASP.NET Core MVC носительно того, как запросы планируются и выполняются. Для вьmолн ения работы асинхронным образом используются два ключевых слова С# - async и await. Для целей данного раздела в пример проекта понадобится добавить новую сбор1\у .N ET . чтобы можно было делать асинхронные НТТР-запросы. В листинге 4.37 показа но добавление, произведенное в разделе dependenci es файла proj ect. j son. Листинг 4.37. Добавление ссылки на сборку в файле project. json "dependencies ": }' " Microsoft . NETCore.App" : "ver sion": "1.О.0", " type" : "platform" }' "M icrosoft . AspNetCore . Diagnostics ": " 1. О. О", "Mi crosoft .AspNetCore.Serve r.I ISintegrat ion": "1 . 0 . О ", "Mi crosoft.AspNetCore .Server.Kestrel ": "1. 0 . 0 ", " Microsoft.Extens i ons . Logg ing .C onso l e ": "1. 0.О ", "M icrosoft.AspNetCore . Mvc ": "1 . 0 .О", "System.Net.Http": "4 .1. 0" После сохранения файла pro j ect. j son среда Visual Studio загрузит сборку System. Net. Http и добавит ее в проект. В главе 6 процесс будет описан более подробно. Работа с задачами напрямую Язык С# и платформа .NET предлагают великолепную поддержку для асинхрон ных методов, но код быстро становится многословным, а разработчики , не привык шие к параллельному программированию , зачастую не могут справиться с необыч ным синтаксисом. В качестве примера в листинге 4.38 приведен метод по имени GetPageLength (), который определен в классе MyAsyncMethods, добавленном в пап ку Mode ls в виде файла MyAsyncMethods . cs . Листинг 4.38 . Содержимое файла МyAsyncМethods. cs из папки Мodels using System . Net .H ttp ; using System . Threading.Tasks; namespace LanguageFeatures.Models puЫic class MyAsyncMethods { puЫic static Task<long?> GetPageLength() HttpClient client = new HttpClient() ; va r httpTask = client.GetAsync("http://apress.com"); 11 Во время выполнения НТТР-запроса //можно было бы делать другую работу . return httpTask.ContinueWith((Task<HttpResponseMessage> antecedent) => { return antecedent . Resu l t . Content . Headers.ContentLength; });
Глава 4. Важные функциональные возможности языка С# 111 Метод использует объект System. Ne t . Ht tp. HttpC lie nt для запрашивания со держимого домашней страницы издательства Apress и возвращает его длину. Работа, которая будет выполняться асинхронно , представлена в .NET как объект Ta sk . Объ е кты Task строго типизируются на основе результата , выдаваемого фоновой ра ботой . Таким образом , при вызове метода HttpClient . GetAs y n c () получается объ ект Task<HttpResponseMe s sage> . Он сообщает о том, что запрос будет выполнен в фоновом режиме и его р езультатом будет объект HttpRespons eM e ssage. Совет. Когда мы употребляем понятия вроде фоновый режим, то опускаем массу деталей, чтобы подчеркнуть только ключевые аспекты, которые важны для мира MVC . Поддержка . NE T для асинхронных методов и параллельного программирования в целом превосход на, и ее рекомендуется внимательно изучить, если вы хотите создавать по-настоящему высокопроизводительные приложения, которые могут извлекать преимущества от много процессорного или многоядерного оборудования . Вы увидите , как MVC облегчает созда ние асинхронных веб-приложений далее в книге по мере представления разнообразных функциональных средств. Самой непонятной для большинства программистов частью является продолже ние , пр едставляющее собой механизм , с помощью которого указыва ется то, что долж но произойти, когда фоновая задача завершится . В приведенном примере применяет ся метод Con tinueWi th () для обработки объекта HttpResponseMe s sag e , получаемого из метода Ht t pC lient . GetAsync () . Это делается с использованием лямбда-выраже ния , которо е возвращает значение свойства, содержащего длину полученного от веб се рвера Apress содержимого. Вот код продолжения : return httpTask . ContinueWith((Task<HttpRespon s eMessag e > anteceden t ) => { return antecedent .Result .Content .Headers . ContentLengt h; }); Обратите внимание , что ключевое слово r e turn встречается два раза. Именно эта часть вызывает путаницу . Первое применение ключевого слова return указыва ет, что возвращается объект Ta s k<Http Re spons eMessage> , который при завершении задачи возвратит (второе ключевое слово r e t u r n ) длину из заголовка Co nte n t Le ngth. Заголовок Con tentLength возвраща ет р е зультат long ? (тип long , допускающий зна чения nul l), т.е . результатом метода Ge tPage Length () является Ta s k< l ong?> : puЫic static Task<long?> GetPageLength() { Не переживайте , если все сказанное выглядит для вас бессмысленным - в этом вы не одиноки. Как раз по такой причине в Microsoft и решили добавить в С# ключевые сло в а , упрощающи е р а боту с асинхронными методами. Применение ключевых сло в async и await Разработчики из Microsoft ввели в язык С# два ключевых слова, которые специаль но призваны облегчить использование асинхронных методов, подобных HttpClient . GetAsync () . Ими являются async и a wa i t ; в листинге 4.39 показано, как с их помо щью упростить наш пример метода.
112 Часть 1. Введение в инфраструктуру ASP.NE T Core MVC Листинг 4.39. Применение ключевых слов async и await в файле МyAsyncМethods. cs using System.Net .H ttp; using System.Threading.Tasks; namespace LanguageFeatures.Models puЫic class MyAsyncMethods { puЫic async static Task<1ong?> GetPageLength() HttpClient client = new HttpClient(); var httpMessage = await client .G etAsync("http://apress.com"); return httpMessage.Content.Headers.ContentLength; Ключевое слово awai t используется при вызове асинхронного метода. Оно сооб щает компилятору С# о том, что необходимо подождать результата Task, который возвращается методом GetAsync (), и затем заняться выполнением остальных опе раторов в том же методе. Применение ключевого слова awai t означает. что мы можем трактовать результат метода GetAsync (), как если бы он был обычным методом, и просто присвоить воз вращаемый им объект HttpResponseMessage какой-нибудь переменной. Еще лучше то, что затем можно использовать ключевое слово return традиционным образом для выдачи результата из другого метода - значения свойства ContentLength в рассмат риваемом случае. Это намного более естественный подход, к тому же нам не придется беспокоиться по поводу метода ContinueWith () и многократного при менения юпоче вого слова return. При использовании ключевого слова await потребуется также добавить к сигнатуре метода ключевое слово async, как бьmо сделано в рассмотренном выше примере. Тип результата метода не изменяется - метод GetPageLength () по-прежнему возвращает Task<long?>. Причина в том, что ключевые слова await и async реализованы с при менением ряда искусных трюков компилятора, которые позволяют использовать более естественный синтаксис, но не изменяют того, что происходит внутри методов, к кото рым они применены. Программист, вызывающий метод GetPageLength () , по-прежне му имеет дело с результатом Task<long?>, т.к. все еще существует фоновая операция, которая выдает значение long, допускающее null; хотя, конечно же, программист мо жет также предпочесть пользоваться ключевыми словами awai t и async. Такой шаблон повсеместно соблюдается в контроллере MVC, что позволяет легко писать асинхронные методы действий (листинг 4.40). Листинг 4.40. Определение асинхронных методов действий в файле HomeController. cs using Microsoft . AspNetCore.Mvc; using System.Collections . Generic ; using LanguageFeatures.Models; using System; using System.Linq; using System.Threading.Tasks; namespace LanguageFeatures.Controllers puЫic class HomeController : Controller
Глава 4. Важные функциональные возможности языка С# 113 puЬlic async Task<ViewResult> Index () { long? length = await MyAsyncMethods.GetPageLength(); return View(new string[] { $ 11 Length: {length}" }) ; Тип результата метода действия Index () изменен на Task<ViewResul t>. Это со общает MVC о том, что метод действия будет возвращать объект Tas k, который по завершении выдаст объект ViewResul t, а тот предоставит детали представления, подлежащего визуализации, и требующиеся ему данные . К определению метода было добавлено ключевое слово async, что позволило использовать 1wючевое слово awai t при вызове метода MyAsyncMethods. Ge tPathLength (). О продолжениях позаботят ся инфраструктура MVC и платформа .NET, а результатом будет код, который легко писать, читать и сопровождать. Запустив приложение, вы увидите вывод, похожий на показанный ниже (хотя наверняка с отличающейся длиной, т.к. содержимое веб сайта Apress часто изменяется): Length : 62164 Получение имен Во время разработки веб-приложений есть много задач, в которых необходимо ссылаться на имя аргумента , переменной, метода или класса. Распространенными примерами являются ситуации с генерированием исключения или созданием ошибки проверки достоверности при обработке пользовательского ввода. Традиционный под ход предполагал применение строкового значения с жестко закодированным именем (листинг 4.4 1). Листинг 4.41. Использование жестко закодированного имени в файле HomeController. cs using Microsoft . AspNetCore . Mvc; using System . Collections.Generic; using LanguageFeat ur es . Mo del s ; using System ; using System.L inq ; namespace LanguageFeatures . Controllers puЫic class HomeController : Co n troller puЬlic ViewRes ul t I ndex() { var products = new [] { new { Name = "Kayak", Price = 275М } , new { Name = "Lifejacket", Price = 48. 95М } , new { Name = "Soccerball", Price = 19.SOM }, new { Name = "Corner flag", Price = 34. 95М } }; return View(products. Select(p => $"Name: {p.Name}, Price: {р. Price} ")); Вызов метода Select ( ) из LINQ генерирует последовательность строк, каждая из которых содержит жестко закодированную ссылку на свойства Name и Price. Запуск приложения дает следующий вьmод в окне браузера:
114 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Name: Kayak , Price: 275 Name: Lifejacket , Price: 48.95 Name: Soccer ball, Price: 19 . 50 Name: Corner flag, Price : 34 . 95 Проблема этого подхода в том, что он предрасположен к ошибкам, которые обус ловлены либо неправильно набранным именем, либо некорректно обновленным име нем в строке после рефакторинга. Результат может вводить в заблуждение , что осо бенно проблематично для сообщений, отображаемых пользователю. В С# 6 появилось выражение nameof, благодаря которому ответственность за формирование строки имени возлагается на компилятор (листинг 4.42) . Листинг 4.42 . Использование выражений nameof в файле HomeController. cs using Microsoft.AspNetCore.Mvc; using System . Collections . Generic ; using LanguageFeatures . Models ; using System ; using System . Linq ; namespace LanguageFeatures.Control lers puЬlic class HomeC ontroller Contro l ler puЫic ViewResult Index() { var products = new [] { }; new { Name " Kayak ", Price = 275М ), new { Name "Lif ejacket ", Price = 48.95М } , new { Name "Soccer ball" , Price 19. SOM }, new { Name "C orner flag ", Price = 34.95М ) return View(products.Select(p => $" {nameof (p.Name)}: {p.Name}, {nameof (р. Price)}: {р. Price} ")); Компилятор обрабатывает ссылку наподобие р . Name таким образом, что только последняя часть включается в строку, порождая тот же самый вывод, как и в преды дущем примере . Среда Visual Studio располагает поддержкой средства IntelliSense для выражений nameof , поэтому вы будете снабжены подсказками при выборе ссылок, а выражения корректно обновятся в случае рефакторинга кода. Поскольку за работу с nameof отвечает компилятор, применение недопустимой ссылки приводит к ошибке на этапе компиляции , что не позволит некорректным или устаревшим ссылкам ус кользнуть от глаз. Резюме В настоящей главе был дан обзор основных языковых средств С#, которые дол жен знать результативный программист приложений MVC. Язык С# является гибким в достаточной мере для того , чтобы обычно существовало несколько способов р еше ния любой задачи, но есть средства, с которыми вы будете чаще всего встречаться во время разработки веб-приложений и видеть повсюду в при ме рах , рассматриваемых в данной книге. В следующей главе будет представлен механизм визуализации Razor и приведены объяснения , как его использовать для генерации динамического содержи мого в веб-приложениях MVC.
ГЛАВА 5 Работа с Razor в приложении ASP.NET Core MVC для выпуска содержимого, отправляемого кли ентам , используется компонент, который называется мехш-шзмом визуалu за4uu. Стандартным механизмом визуализации является Razor, и он обрабатывает аннотированные НТМL-файлы, производя поиск инструкций, которые вставляют ди намическое содержимое в вывод, отправляемый браузеру. В этой главе дается краткое введение в синтаксис Razor, так что вы сможете опоз нать выражения Razor, когда столкнетесь с ними. Мы не собираемся превращать гла ву в исчерпывающий справочник по Razor; считайте ее в большей степени ускорен ным курсом по синтаксису. Особенности Razor будут раскрыты в последующих главах книги при рассмотрении других средств МVС. В табл. 5.1 приведена сводка , позволя ющая поместить Razor в контекст. Таблица 5.1. Помещение Razor в контекст Вопрос Что это такое? Чем он полезен? Как он используется? Существуют ли какие то скрытые ловушки или ограничения? Существуют ли альтернативы? Изменился ли он по сравнению с версией MVC5? Ответ Razor - это механизм визуализации, отвечающий за встраивание данных в документы HTML Возможность динамической генерации содержимого являет- ся неотъемлемой частью процесса написания веб-приложения . Механизм визуализации Razor предоставляет средства, которые упрощают работу с остальной инфраструктурой ASP.NET Core MVC с применением операторов С# Выражения Razor добавляются к статической НТМL-разметке в файлах представлений. Эти выражения оцениваются для генерации ответов на клиентские запросы Выражения Razor могут содержать почти любые операторы С#. Иногда может быть трудно решить, должна ли логика принадлежать представ лению или контроллеру, что способно разрушить принцип разделения обязанностей, который является центральным в паттерне MVC В главе 21 объясняется , как написать собственный механизм визу ализации . Доступны механизмы визуализации от независимых пос тавщиков, но они обычно полезны только в ограниченных ситуациях и для них не гарантируется долгосрочная подцержка Механизм визуализации Razor работает в значительной степени таким же образом, как и в MVC 5, но с рядом удобных улучшений. Файл импортирования представлений используется для указания пространств имен, в которых будет производиться поиск типов при обработке представления , а также для определения мест, где нахо дятся дескрипторные вспомогательные классы (глава 23)
116 Часть 1. Введение в инфраструктуру ASP.NET Core MVC В табл. 5.2 приведена сводка по главе. Таблица 5.2 . Сводка по главе Задача Решение Доступ к модели представления Используйте выражение @Model для опре деления типа модели и выражение @rnodel для доступа к объекту модели Использование имен типов без Создайте файл импортирования их уточнения с помощью про странств имен Определение содержимого, которое будет использоваться множеством представлений Указание стандартной компоновки представлений Применяйте компоновку Используйте файл запуска представления Передача данных из контролле- Применяйте объект ViewBag ра представлению за предела ми модели представления Выборочная генерация содержимого Генерация содер жимого для кажд ого элемента в массиве или коллекции Используйте условные выражения Razor Применяйте выражение @foreach механизма Razor Подготовка проекта для примера Листинг 5.6, 5.15, 5.18 5.7, 5.8 5.9 -5 .11 5.12-5.14 5.16,5.17 5.19, 5 .20 5.21, 5 .22 Для демонстрации работы механизма визуализации Razor мы создали проект ASP.NET Core Web Application (.NET Core) (Веб-приложение ASP.NET Core (.NET Core)) по имени Razor, используя шаблон Empty (Пустой), как делали это в предыдушей гла ве . Отредактировав раздел dependencies файла proj ect . j son, мы добавили сборку МVС (листинг 5.1). Листинг 5.1 . Добавление сборки MVC в файле proj ect. j son 11 dependenci е s 11 : { }, 11 Microsoft . NETCore.App 11 : 11 version 11 : 11 1.0.0 11 , 11 type 11 : 11 platform11 }, 11 Mi crosoft . AspNetCore.Diagnostics 11 : 11 1.0.0 11 , 11 Microsoft .AspNetCore.Server.IISintegration 11 : 11 1.0 .0 11 , 11 Microsoft.AspNetCore . Server . Kestrel ": "1.0 . 0 ", 11 Microsoft .Extensions.Logging .C onsole 11 : " 1.0.0 11 , "Microsoft . AspNetCore.Mvc 11 : 11 1.0.0 11
Глава 5. Работа с Razor 117 После сохранения изменений , внесенных в proj ect . j son, среда Visual Studio добавит в проект сборку Microsoft. AspNetCo re. Mv c . Далее мы включаем инфра структуру MVC с ее стандартной конфигурацией в файле Startup . cs (листинг 5.2). Листинг 5.2 . Включение инфраструктуры MVC в файле Startup. cs using Microsoft.AspNetCore . Builder ; using Microsoft . AspNetCore.H osting ; using Microsoft.AspNetCore .H ttp; using Microsoft . Extensions .D ependencyinj ection ; using Microsoft . Extens i ons .Logging ; namespace Razor { puЬlic class Startup puЬlic void ConfigureServices( I ServiceCollection servi ces) { services.Adc!Мvc(); puЫic void Configure(IApplicationBuilder арр , IHostingEnvironment env , ILoggerFactory loggerFactory) { app.UseMvcWithDefaultRoute(); Определение модели Затем мы созда ем папку Models и добавляем в нее файл класса по имени Product . cs с приведенным в листинге 5.3 определением простого класса модели. Листинг 5.3 . Содержимое файла Product. cs из папки Models namespace Razor . Models { puЫic class Product { puЫic int ProductID { get; set ; puЫic string Name { get; set ; } puЬlic string Description { get ; set; puЬlic decimal Price { get ; set; puЫic string Category { set ; get ; } Создание контроллера Стандартная конфигурация, установленная в файле Startup . cs , следует соглаше нию MVC относительно отправки запросов контроллеру по имени Ноте по умолчанию. Мы создали папку Controllers и добавили в нее файл класса HomeController. cs , поместив в него простое определение контроллера (листинг 5.4).
118 Часть 1. Введение в инфрастру к туру ASP. NET Core MVC Листинг 5.4 . Содержимое файла HomeController. cs из папки Controllers using Microsoft . AspNetCore.Mvc ; using Razor . Models ; namespace Razor.Contro l lers { puЬl i c class HomeController : Controller p uЫ ic ViewRes ult Index() { Product myP rod u ct = new Product { ProductID = 1, Name = "Kayak", }; Description = " А boat f or one person ", Category = " Watersports ", Price = 275 М r etu rn View(myP roduct); В контроллере Ноте определен метод действия по имени Index () , в котором со здается объект Product с заполнением его свойств. Объект Product передается ме тоду View () ,так что он используется как модель во время визуализации представле ния. При вызове метода View () имя файла представления не указывается , поэтому будет применяться стандартное представление для метода действия. Создание представления Чтобы создать стандартное представление для метода действия Index () , мы со здаем папку Views/ Hom e и добавляем в нее файл типа MVC View Page (Стр аница пр ед ставления MVC) по имени Index . cs html, куда помещаем содержимое, показанное в листинге 5.5 . Листинг 5.5 . Содержимое файла Index. cshtml из папки Views/Home @model Razor . Models . Product @{ Layout = nul l; < ! DOCTYPE html > <html > <head> <meta name= " viewpo rt" content= " wid th=device - width " /> <tit le > Index </ title> </hea d > <body> Conte nt will go here </body> </html >
Глава 5. Работа с Razor 119 В последующих разделах мы рассмотрим различные части представления Razor и продемонстрируем разнообразные вещи. которые с ним можно делать. При изучении Razor полезно помнить. что представления существуют для выражения пользователю одной или более частей модели - и это означает генерацию НТМL-разметки. которая отображает данные, извлеченные из одного или множества объектов. Если не забы вать, что мы всегда пытаемся строить НТМL-страницу, которая может быть отправ лена клиенту. тогда вся активность механизма визуализации Razor обретает смысл. Запустив приложение. вы увидите простой вывод, приведенный на рис. 5.1. [J lndex 1--~ С [ф localhost:60753 L o11tent \vill go 11е1·е --- --- Рис. 5.1 . Выполнение примера прило жения Работа с объектом модели Давайте начнем с самой первой строки в файле представления Index. cshtml: @model Razor.Models .Product Выражения Razor начинаются с символа@. В данном случае выражение @model объявляет тип объекта модели, который будет передаваться представлению из метода действия. Это позволяет ссылаться на методы, поля и свойства объекта модели пред ставления посредством @Model, как показано в листинге 5 .6 , в котором приведено простое дополнение к представлению Index. Листинг 5.6 . Ссылка на свойство объекта модели представления в файле Index. cshtml @mode l Razor.Models.Product @{ Layout = null; < !DO CTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Index</title> </head> <body> @Model.Name </body> </html>
120 Часть 1. Введение в инфраструктуру ASP.NET Core MVC На заметку! Обратите внимание, что тип объекта модели представления объявляется с ис пользованием @model (со строчной буквой m), а доступ к свойству Nam e производится с применением @Model (с прописной буквой М). Поначалу зто может немного запутывать, но со временем станет вполне привычным . Запустив приложение, вы увидите вывод, представленный на рис. 5.2 . [j lndox х "·· С [QS-;-~~ 1110~1:w7sз --~--------==*l ~-------·· ' . . ·-·- - - Kaya k ---· ·------.·~------·--·-·--··~-·---·-w-·--w-·--·---*-"•-...,•-• Рис. 5.2 . Результат чтения значения свойства внутри представления Представление, которое использует выражение @model для указания типа, назы вается строго типизированным представлением. Среда Visual Studio способна при менять выражение @mo d e l для открытия окна со списком предполагаемых имен чле нов, когда вы вводите @M od e l с последующей точкой (рис. 5.3). Razor - 1:1 Х , ~del R~:tor ./-lodels . Product @{ L~yout ~ null; <!ООСТУРЕ html > ~ B <html> . B<t,ead> ·i <me ta nэrr.e•"viewport" contenta'·\ -..i dth =device·\"lidth'' /> i <title>l ndex</title> •l </head> B <body > i l!t'lodel .J11 1</Ьоdу> ., , , Category [ </html> - " Oe<cription Ф Equ ols ·Ф Gt:tHashCode i 100% -~ tl __ ,.._ __ _ _ _ Ф GetType "' ---~ ,,, Pric e - " ProductlD ,Ф . ToS.tring l+- Рис. 5.3. Среда Visual Studio предлагает список предполагаемых имен членов на основе выражения @Model Список предполагаемых имен членов, отображаемый Visual Studio, помогает из бегать ошибок в представлениях Razor. При желании вы можете игнорировать эти предположения, и тогда Visual Studio будет подсвечивать проблемные имена членов,
Глава 5. Работа с Razor 121 чтобы вы внесли исправления, как поступает в обычных файлах классов С#. Пример подсветки проблемы можно видеть на рис. 5.4, где мы пытается сослаться на свойство @Mo de l . No tAR e alProperty. Среда Visual Studio обнаруживает, что класс Pro duc t, указанный в качестве типа модели, не содержит такого свойства, и подсвечивает ошибку в редакторе . .1· ,r. FJ<head) l l <rneta n an;e ="vie1~port " conte n t " "1~idtl1=device-wi d t h " /> <title>I nd ex</title> </head> Э <Ьоdу> l @f'todel.~e~y,, </body> · </html> ~ ~...; ,..," ".~ , .... .м. ~ r Рис. 5.4. Среда Visual Studio сообщает о проблеме с выражением @M o de l Использование файла импортирования представлений При определении объекта модели в начале файла Index . cshtml необходимо было включать пространство имен, которое содержит класс модели, например: @mod el Razor.Models.Pro duct По умолчанию все типы, на которые производится ссьmка в строго типизирован ном представлении Razor, должны уточняться своими пространствами имен. Это не особо крупная проблема, когда есть только ссылка на объект модели, но понимание представления может значительно затрудниться при написании более сложных вы раж ений Razor, таких как рассматриваемые позже в настоящей главе. Добавив в проект файл импортuрования представлений. можно указать набор про странств имен, в которых должен осуществляться поиск типов. Файл импортирования представлений размещается в папке Vi e ws и имеет имя _View impo rt s . c sht ml. На заметку! Файлы в папке Vi ew s, имена которых начинаются с символа подчеркивания ( ), пользователю не возвращаются , что позволяет с помощью имен файлов проводить различие между представлениями, подлежащими визуализации, и поддерживающими их файлами. Файлы импортирования представлений и компоновки (будут описаны вскоре) имеют имена, начинающиеся с символа подчеркивания. Чтобы создать файл импортирования представлений, щелкните правой кнопкой мыши на папке View s в окне Solution Explorer, выберите в контекстном меню пункт Addc::>New ltem (Добавить<=:> Новый элемент) и укажите шаблон MVC View lmports Page (Страница импортирования представлений МVС) из категории ASP.NET (рис. 5.5). Среда Visual Studio автоматически назначит файлу имя_Vi ew i mp o r t s. cshtml, а щелчок на кнопке Add (Добавить) приведет к его созданию. Поместите в файл выра жение, показанное в листинге 5.7 .
122 Часть 1. Введение в инфраструктуру ASP.NET Саге MVC Clitnt·side Code t> Onl ine Nыn<: MVC View l •yout Page MVC у;..,, St4rt Page ASP. NП Ccnfig ur ation File ~е to 90 onlin e and find templates. _ Vie wlmports.c shtn' I ASP.NEТ ASP.NEТ ASP .NEТ • Тур~: ASP.N~ MVC Vi e\v lmpc t J'! ·. Рис. 5.5 . Создание файла импортирования представлений Листинг 5.7. Содержимое файла_Viewimports. cshtml из папки Views @using Razor . Mod e ls Пространств а имен , в которых должен проводиться поиск классов, пр именя емых в представлениях Razor, указываются с использованием выражения @using, за кото рым следует пространство имен. В листинге 5. 7 добавлена запись для пространства имен Razor . Models , содержащего класс модели в при м ере приложения . Теперь, когда пространство имен Ra z or . Models включено в файл импортирова ния представлений, можно удалить пространство имен из файла Index . cshtml (лис тинг 5.8). Листинг 5.8 . Ссылка на класс модели без пространства имен в файле Index. cshtml @model Product @{ Layout = null ; < ! DOCTYPE html> <html> <head> <meta name= "vi ewport " content="wi dth=devi ce-width" /> <t i tle> I ndex</title> </ h ead> <body> @Model.Name </body> </html >
Гл ава 5. Работа с Razor 123 Совет. Выражение @using м ожн о также до бавлять в отдельные файлы представл ений, ч то позволит применять внутри них типы без указания пространства имен. Работа с компоновками Ниже приведено еще одно важное выражение Razor из файла представления Index. cshtml: @{ Layout null; Это пример блока кода Razor, который позволяет включать в представление опе раторы С#. Блок кода открывается посредством @{ и закрывается с помощью }, а содержащиеся в нем операторы оцениваются при визуализации представления . Показанный выше блок кода устанавливает значение свойства Layou t в null. Представления Razor компилируются в классы С# внутри приложения MVC, а в ба зовом классе, который они используют, определено свойство Layout. В главе 21 объ ясняется, нак все это работает, а пока достаточно знать, что результатом установки свойства La yout в null является сообщение инфраструктуре МVС о том, что наше представление является самодостаточным, и оно будет визуализировать все свое со держимое, которое необходимо возвратить клиенту. Самодостаточные представления хороши для простых примеров приложений, но реальный проект может включать десятки представлений. и некоторые представле ния будут иметь общее содержимое. Дублированное общее содержимое в представ лениях становится трудным в управлении, особенно если нужно внести изменение и отследить все представления, подлежащие модификации. Более эффективный подход предусматривает использование компоновки Razor, яв ляющейся шаблоном, который хранит общее содержимое и может быть применен к одному или большему числу представлений. Когда вы вносите изменение в компонов ку , оно автоматически воздействует на все представления, которые ее используют. Создание компоновки Компоновки обычно совместно используются представлениями , применяемыми множеством контроллеров, и хранятся в папке по имени Views/Shared, которая входит в перечень местоположений, просматриваемых Razor в попытках найти файл. Чтобы создать компоновку. создайте папку Views/Shared, щешшите на ней правой кнопкой мыши и выберите в контекстном меню пункт AddФNew ltem (ДобавитьФНовый элемент). Укажите шаблон MVC View Layout Page (Страница компоновки представле ний МVС) в категории ASP.NET и введите _BasicLayo ut . cshtml в качестве имени файла (рис. 5.6). Щелкните на кнопке Add (Добавить) для создания файла. (Подобно файлам импортирования представлений имена файлов компоновок начинаются с символа подчеркивания .) В листинге 5.9 показано начальное содержимое файла _Basi c Layout . cshtml, до бавленное средой Visual Studio при создании файла.
124 Часть! . Введение в инфраструктуру ASP.N ET Саге MVC " lnstall<d f'Se;rc~' ln<talled Templ•lf ASP .Nff ~· Туро: ASP.NEТ fl; MVC Controller Closs ASP . NEТ Clie nt-s id• MVC Vi.,,v Loyout Pog Code ~·fl; Web AP I Cont ro ll er Cla1S ASP.N EТ fl- Online . ~· МVС View lmp or1s Pog e ASP .NET ~·fl; Ritzor Tag H~lper ASP.NET "' Cljclc here to go onlioe itnd find tempi<!t es. _Bo sicl"yout.c sl,tml Рис. 5.6. Создание компоновки Листинг 5.9. Начальное содержимое файла _BasicLayout. cshtml <!DOCTYPE html> <html> <head> <m eta name="viewport " content="width=device-width" /> < title> @ViewBag.Title</title> </head> <body> <div> @RenderBody () </div> </body> </html> i Компоновки - это специализированная форма представлений; в листинге 5.9 выражения @ выделены полужирным . Вызов метода @RenderBody () вставляет в разметку компоновки содержимое представления. указанное с помощью метода действия. Другое выражение Razor в компоновке обращается к свойству по имени ViewBag . Ti tle для установки содержимого элемента ti t le . Объ ект ViewBag явля ется удобным средством, которое позволяет передавать значения данных внутри при ложения - в рассматриваемом случае между представлением и его компоновкой . Вы увидите, как это работает, когда компоновка будет применена к представлению. Элементы HTML в компоновке будут применены к любому представлению. которое использует компоновку , предоставляя шаблон для определения общего содержимого. В листинге 5.1 О к компоновке добавлена простая разметка, чтобы ее эффект как шаб лона был очевидным.
Глава 5. Работа с Razor 125 Листинг 5.10. Добавление содержимого в файл_BasicLayout. cshtml < !DOCTYPE html> <html> <head> <meta name= "viewport " content= " width=device - width" /> <title>@ViewBag .Ti tle</title> <style> #mainDiv { padding: 20рх; border: solid medium Ыасk; font-size: 20 pt } </style> </head> <body> <hl>Product Information</hl> <div id="mainDiv"> @RenderBody () </div> </body> </html > Здесь был добавлен элемент заголовка, а также стиль CSS для стилизации со держимого элемента div, в котором находится выражение @RenderBody (), прос то ради прояснения, какое содержимое поступает из компоновки, а KaJ{Oe - из представления. Применение компоновки Чтобы применить компоновку к представлению, понадобится установить значение свойства Layou t и удалить НТМL-разметку, которая теперь будет предоставляться компоновкой, такую как элементы html, head и body (листинг 5.11). Листинг 5.11. Применение компоновки в файле Index. cshtml @model Product @{ Layout = "_BasicLayout"; ViewBag .Title = "Product Name"; Product Name: @Model.Name Свойство Layout указывает имя файла компоновки, который будет использовать ся для представления, без расширения cshtml . Механизм Razor будет искать указан ный файл компоновки в папках /Views/Home и Views/Shared . Кроме того, выполнено присваивание свойства ViewBag. Ti tle . Оно будет приме няться компоновкой для установки содержимого элемента t i t 1 е при визуализации представления. Трансформация представления значительна даже для такого простого приложе ния. Компоновка располагает всей структурой, требуемой для любого НТМL-ответа , оставляя представлению только заботу о динамическом содержимом, которое отоб-
126 Часть 1. Введение в инфраструктуру ASP.NET Core MVC ражает данные пользователю. Когда инфраструктура MVC обрабатывает файл Index. cshtml, она применяет компоновку для создания унифицированного НТМL ответа (рис. 5 .7). х , -> С ' CJ localhost:6063 '1 :=J 1 . --------- --- --- P1·oduct Information Ргоdнсt Nаше: Kayak Рис. 5. 7. Результат применения компоновки к представлению Использование файла запуска представления Осталась еще одна небольшая шероховатость, которую необходимо устранить - мы должны указывать файл компоновки для применения в каждом представлении. Следовательно, если нужно переименовать файл компоновки, то придется отыскать все ссылающиеся на него представления и внести изменение, что будет чреватым ошибками процессом и противоречит лейтмотиву, пронизывающему разработку при ложений MVC - легкости сопровождения. Решить упомянутую проблему можно с использованием файла запуска представ ления. При визуализации представления инфраструктура МVС ищет файл по имени _ V iewStart . cshtml. Содержимое этого файла будет трактоваться так, если бы оно присутствовало внутри самого файла представления , и данное средство можно при менять для автоматической установки свойства Layout . Чтобы создать файл запуска пр едставления, щелкните правой кнопкой мыши на папке Views, выберите в контекстном меню пункт AddqNew ltem (ДобавитьqНовый элемент) и укажите шаблон MVC View Start Page (Файл запуска представления МVС) в категории ASP.NET (рис . 5.8). Среда Vlsual Studio автоматически назначит файлу имя _ViewStart.cshtml; щелчок на кнопке Add (Добавить) приведет к созданию файла с начальным содержи мым, приведенным в листинге 5.12. Листинг 5.12. Начальное содержимое файла ViewStart. cshtml @{ Layout "_Layout "; Для применения компоновки ко всем представлениям в приложении значение, присваиваемое свойству Layout, изменено, как показано в листинге 5.13.
Глава 5. Р абота с Aazor 127 . Туре: ASP.N ET MVC View l"yout Page ASP.NEТ Client~side MVC View St•rt Page Code ~ Online ji" MVC View lmports Page ASP.NEO # ~·t.; Razo r Т119 Helper ASP.NET ~· 5 ~ Middleware Class ASP.NEТ ~·t.; Startup class ASP.NEТ , у1] ASP.NEO Configurotion File ASP.NEТ Cli<:k lч~re !О go onli ne § ПО [ind A~ml2; 1 c1t~ . Nome: _V iewStart .cshtml Рис. 5.8 . Создание фай л а запуска представления Листинг 5.1 З. Применение стандартного представления в файле_ViewStart. cshtml @{ Layout = "_BasicLayout"; Поскольку файл запуска представления содержит значение для свойства Layout, можно удалить соответствующее выражение из файла Iпd e x . c shtml (листинг 5.14). Листинг 5. 14 . Обновление файла Index. cshtml, позволяющее отразить использование файла запуска представления @model Product @{ ViewBag . Ti t le = " Pr oduct Name"; Produc t Na me: @Model.Name Специально указывать, что должен применяться файл запуска представления, не требуется . Инфраструктура MVC автоматически обнаруживает данный файл и ис пользует его содержимо е . Преимущество отдается значениям , определяемым в фай ле представления, что делает переопределение файла запуска представления очень простым. Можно также применять несколько файлов запуска представлений , чтобы устано вить стандартное поведение для разных частей приложения . Механизм Razor ищет самый близкий файл запуска для обрабатываемого представления, а это значит, что стандартные настройки можно переопределять, добавляя файл запуска представле ния в папку View s /Home или Views / Share d.
128 Часть 1. Введение в инфрастру к туру ASP.NET Core MVC Внимание! Важно понимать разницу между отсутствием свойства Layout в файле пред ставления и его установкой в null. Если представление является самодостаточным, и вы не хотите применять компоновку, то установите свойство Layout в n ul l. Если же просто опустить свойство Layo u t , то инфраструктура MVC будет считать, что компоновка вам необходима, и она должна использовать значение, которое найдет в файле запуска представления. Использование выражений Razor Теперь , когда вы видели основы представлений и компоновок, давайте переклю чимся на другие виды выр ажений, поддерживаемые Razor, и посмотрим , к а к их при менять для создания содержимого представлений. В хорошем приложении MVC им е ется четкое разделение между ролями метода действия и представления . Роли просты и кратко описаны в табл. 5.3. Таблица 5.3. Роли, исполняемые методом действия и представлением Компонент Метод действия Представление Что делает Передает представлению объект модели представления Использует объект модели пред ставления для отображения со держимого пользователю Чего не делает Не передает представлению сформатированные данные Не изменяет ни одного аспе кта в объекте модели представления Далее в книге мы будем неоднократно возвращаться к этой теме. Чтобы извлечь максимум из MVC , необходимо соблюдать и обеспечивать разделение между разными частями приложения. Вы увидите , что механизм Razor позволяет делать очень мно гое , включая применение операторов С# , но вы не должны использов ать Razor для выполнения бизнес-логики или манипулирования объектами моделей предметной области. В качестве простого примера в листинге 5.15 демонстрируется добавлени е нового выраж ения в представление I n d ex . Листинг 5.15. Добавление выражения в файл Index. cshtml @mode l Prod u ct @{ ViewBag . Title = " Product Name "; <p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p> Значени е свойства Price можно было бы сформатировать в м етоде действия и п е редать его представлению . Это бы работало, но такой подход разрушает пр еимущест во шаблона МVС и сокращает возможность реагировать на изменения в будущем. Как уже упоминалось, мы возвратимся к данной теме снова , но вы должны запо мнить , что инфраструктура ASP.NEТ Core MVC не принуждает правил ьно применять паттерн МVС и нужно учитывать влияние решений, принимаемых во время проектирования и написания кода .
Глава 5. Работа с Razor 129 Обработка или форматирование данных Важн о отличать обработку данных от их форматирования. Представления форматируют дан ные , поэтому в предыдущем разделе представлению передается объект Product, а не отображаемая стро ка с форматированными свойствами этого объекта. За обработку данных, включая выбор объе ктов данны х для отображения, отвечает контроллер , который будет об ращаться к модели с целью получения и модификации требующихся данных. Иногда трудно понять, где проходит черта между обработкой и форматированием, но в качестве эмпири ческого правила рекомендуется занять осторожную позицию и выносить из представления в контроллер все кроме просте й ших выраже ний. Вставка значений данных Самое простое, что можно делать с помощью выражения Razor - вставлять значе ния данных в разметку. Наиболее распространенный способ сделать это предполага ет использование выражения @Model . Представлени е Index уже содержит примеры применения такого подхода, подобные показанному ниже: <p>Prod uct Name : @Model.Name </p> Вставлять значения можно также с использованием объекта ViewBag, кото рый применялся для установки содержимого элемента t i tle в компоновке . Объ ект ViewBag мож но использовать для передачи данных из контроллера в представление, дополняющее модель (листинг 5 .1 6) . Листинг 5.16. Применение ViewBag в файле HomeController. cs using Microsoft . AspNetCore .Mvc; using Razor.Models; namespace Razor . Controlle rs { puЬlic class HomeController : Controller puЬlic ViewResult Index() { Product myProduct = new Product { ProductID = 1 , }; Name = " Kayak", Description = " А boat for one person ", Category = "W atersports ", Price = 275 М ViewBag. StockLevel = 2; return View(myProduct); Свойство ViewBag возвращает динамический объект, который может использо ваться для определения произвольных свойств. В листинге 5.16 было определено и установлено в 2 свойство по имени StockLevel. Так как объект ViewBag является динамическим, объявлять имена свойств заранее необязательно, но это означает, что
130 Часть 1. Введение в инфраструктуру ASP.NEТ Саге MVC для свойств ViewBag среда Visual Studio не в состоянии предоставлять предполагае мые варианты автозавершения. Знание того, когда применять ViewBag, а когда расширять модель - вопрос опы та и сложившихся предпочтений. Мой персональный стиль заключается в том, что бы использовать объект ViewBag только для предоставления визуальных подсказок о способе визуализации данных и не применять его для значений данных, которые отображаются пользователю. Но зто просто то, что подходит лично мне. Если вы хо тите использовать ViewBag для данных, отображаемых пользователю, тогда обращай тесь к значениям с помощью выражения @ViewBag (листинг 5.17). Листинг 5.17 . Отображение значения ViewBag в файле Index. cshtml @model Product @{ ViewBag.Title = " Product Name "; <p>Product Name : @Model.Name</p> <p>Product Price: @($ " (Model.Price:C2} " )</p > <p>Stock Level: @ViewBag.StockLevel</p> Результат можно видеть на рис. 5.9 . ~ Product N"m~ Х 1 С ~- localho ;GO~ - ---- -------· - - _ j] ~-1 1 P1·oduct I11fo1·matio11 1 1 Product Name: Kayak 1 1 ::::::::~~е2$275 00 1 i 1 L_ ... _ _ -_ -_ -----_ -_ -_- _ - _ =:_-:_ -_ -_ - _ - _ - _ - _ - __ -_ -_ - _ -_ - _ ::_ -- .._ -: -:-:-: -:-:. .._, Рис. 5.9. Применение выражений Razor для вставки значений данных Установка значений атрибутов Во всех рассмотренных до сих пор примерах устанавливалось содержимое элемен тов, но выражения Razor можно использовать также для установки значений атри бутов элемента . В листинге 5.18 демонстрируется применение выражений @Model и @ViewBag для установки содержимого атрибутов в элементах представления Index.
Глава 5. Работа с Razor 131 Листинг 5.18. Использование выражений Razor для установки значений атрибутов в файле Index. cshtml @model Product @{ ViewBag . Title = " Product Na me "; <div data-productid="@Model.ProductID" data-stocklevel="@ViewBag.StockLevel"> <p>Product Name : @Model . Name</p> <p>Product Pr i ce : @($ "{ Model . Price : C2 }" )</p> <p>Stock Level : @ViewBag . StockLevel</p> </div> Выраж е ния Razor пр и меняются для установки знач е ний неко т орых атрибутов данных в элементе div . Со вет. Атрибуты данных , которые представляют собой атрибуты с именами, начинающимися на data- , в течение многих лет были неформальным способом создания специальных атрибутов и затем сделаны частью формального стандарта HTML5. Чаще всего они ис пользуются так , что код JavaScript может находить специфические элементы, или так , что стили CSS могут применяться более ограниченным образом. Запустив прим ер приложения и прос мотрев НТМL-разм етку , отправленную брау зе ру, вы увидите, что механизм Razor установил значения атрибутов : <div data-stocklevel="2" data-productid="l"> <p>Product Name : Kayak</p> <p>Product Price : $2 7 5 . 00</р> <p>Stock Level : 120</р> </div> Использование условных операторов Механизм Razor спо собен обр абатывать условны е операторы, что озн ачает воз можность настройки вывода из представления на основ е значений данных представ ления . Такой при ем является наиболее важной частью Razor и позволя ет создавать сложн ы е и и зм е нчивые компоновки , которые по-прежн е му остаются простыми в пони мании и сопровождении. В листинг е 5 . 19 прив едено обновленное содержи мое представл ения Index, включающ ее условный оператор . Листинг 5.19. Применение условного оператора Razor в файле Index. cshtml @model Product @{ ViewBag . Title = " Produc t Name "; <div data- producti d= " @Model . Pr odu ctID" data-stocklevel= " @Vie wBag . StockLeve l" > <p>Product Name: @Model. Na me</p> <p>Product Price : @($ " {Model . Price : C2 }" )</p> <p>Stock Level :
132 Часть 1. Введение в инфраструктуру ASP.NET Core MVC @s witch ( (int)Vie wBag . StockLe vel ) caseО: @:Out of Stock brea k; case 1: case 2: case 3: <b>Low Stock (@V iewBag . StockLeve l)</b> break; defaul t: </р> </ div> @: @ViewBa g. StockLev e l in Sto ck break; Чтобы начать условный оператор , перед условным ключевым словом С# (в этом примере switch) помещается символ @ . Блок кода завершается закрывающей фигур ной скобкой (}) подобно обычному блоку ~юда С#. Совет. Обратите внимание, что для использования внутри оператора sw i tch значение свойства Vi ewBag . ProductCount должно быть приведено к типу int. Причина в том, что Rаzоr-выра жение @sw i tch не может оценивать динамическое свойство - необходи мо приведение к специфичному типу, чтобы было известно , как выполнять сравнения. Внутри блока кода Razor в вывод представления можно включать НТМL- элементы и значения данных , просто определяя НТМL-разметку и выражения Razor, например : <Ь>Low Stock (@ViewBag.StockLevel)</b> Помещать элементы или выражения в кавычки либо отмечать их каким-то спе циальным образом не требуется - механизм Razor будет интерпретировать это как вывод, подлежащий обработке. Тем не менее, если нужно вставить в представление литеральный текст, когда он не содержится в НТМL-элементе, то об этом понадобится сообщить Razor, добавив к строке префикс: @: Out of Stock Символы @: пр едотвращают обработку механизмом Razor данной строки как опе ратора С#, что является стандартным поведением в отношении те кста . Результат вы полнения такого условного оператора показан на рис. 5.10. Условные операторы играют важную роль в представлениях Razor, т.к. они позво ляют варьиров ать сод е ржимое на основе значений данных, которые пр едставл ение получает от метода действия. В качестве дополнительной демо нстрации в листин ге 5.20 приведено представл ение Index . c s h t ml с добавленным оператором if - еще одним распространенным условным оператором.
Глава 5. Работа с Razor 133 С) ProdU<t N""' Х 1- С (~ l~~~~~t~60i_S3 -==--=·=-=--·=--==='---~ 1 P1·oduct Info1·mation 1 1 Prodнct Nаше: Kayak 1 P1·od.нct PI"ice: $275.00 1 Stock Level: Lo\V Stock (2) l_ ---- --- ----------------------------- Рис. 5. 1о. Применение оператора sw i tch в представлении Razor Листинг 5.20. Использование оператора if в файле Index. cshtml @model Product @{ ViewBag.Title = " Product Name "; <div data - productid= " @Model . ProductID " data - stocklevel= " @ViewBag . StockLeve l" > <p>Product Name : @Model . Name</p> <p>Product Price : @($ "{ Model.Price : C2} ") </p> <p>Stock Level : @if (ViewBag.StockLevel == 0) @: Out of Stock } else if (ViewBag. StockLevel > О && ViewBag. StockLevel <= 3) { <b>Low Stock (@ViewBag.StockLevel)</b> else { @: @ViewBag.StockLevel in Stock } </р> </div> Этот условный оператор выдает те же самые результаты, что и оператор swi tch , но мы просто хотели проде монст рировать применение условных операторов С# в представлениях Razor. В главе 21, где представления рассматриваются более подроб но, будут даны объясн ения, как все это работает. Проход по содержимому массивов и коллекций При разработке приложения МVС часто необходимо выполнять проход по содер жимому массива или друrой разновидности коллекции объектов с генерацией подроб ной информации для каждого объекта. Чтобы продемонстрировать, как это делается,
134 Часть 1. Введение в инфраструктуру ASP.NET Core MVC в листинге 5.21 показан модифицированный метод действия I ndex () в контроллере Home , 1<0торый теперь передает представлению массив объектов Pr odu ct . Листинг 5.21. Использование массива в файле HomeController. cs using Mi crosoft . As pNetCo re. Mvc ; usi ng Razor.Models; namespace Razor . Controllers { puЫic class HomeController : Controller puЫi c IActionResult Index () { Product [] array = { new Product {Name = new Product {Name new Product {Name new Product {Name }; return View(array); "Kayak", Price = 275 М}, "Lifejacket", Price = 48. 95 М}, "Soccerball", Price 19.50М}, "Corner flag", Price = 34.95 М} Метод действия Index ( ) создает объект Produ ct [] , который содержит простые значения данных, и передает его методу Vi ew () для визуализации с применени ем стандартного представления. В листинге 5.22 изменен тип модели для пр едставления Index и с помощью цикла fore ach производится проход по объектам в массиве. Листинг 5.22 . Проход по массиву в файле Index. cshtml @mode l Product[J @{ ViewBag .T itl e <taЬle> "Product Name "; <thead> <tr><th>Name</th><th>Price</th></tr> </thead> <tЬody> @foreach (Product р in Model) <tr> <td>@p.Name</td> <td>@($"{p.Price:C2}")</td> </tr> } </tЬody> </taЬle> Оп ератор @f orea c h выполняет проход по содержимому массива объектов моде ли и генерирует для каждого элемента массива строку в таблице. Вы видите, что в цикле fo r each создана локальная переменная по имени р , на сво й ства которой осу ществляется ссылка с использованием выражений Razor вида @р . Name и @р . Price. Результат приведен на рис. 5.11 .
Резюме ['j Product Nome Х . С Гф lo-ca-111-os -t .60- 75- =3==== = -- --- ~-==---= - - --=:-:- _ -:::::-::;::_-:::;:::::::-_-: -::::::::-: ..- ._ - ________ ---- Product Info1·mation Name Price Kayak $275.00 Lite j ac ket $48.95 Soccer ball $ 19.50 Согпе1· flag $34.95 ---------------- Глава 5. Работа с Razor 135 Рис. 5.11 . Применение Razor для прохода по массиву В этой главе был предложен обзор механизма визуализации Razor и показано, как его использовать для генерации НТМL-разметки. Мы взглянули, каким образом ссы латься на данные, передаваемые из контроллера, через объект модели представления и объект Vi e wBa g, а также продемонстрировали применение выражений Razor для настройки ответов пользователю на основе значений данных. В оставшихся главах книги вы увидите много разных примеров использования Razor, а в главе 21 найдете подробное обсуждение функционирования механизма визуализации МVС. В следую щей главе будут описаны некоторые средства, предлагаемые Visual Studio для работы с про ектами ASP.NET Core MVC .
ГЛАВА 6 Работа с Visual Studio в этой главе рассматриваются основные средства, предоставляемые Visual Studio для разработки проектов ASP.NET Core MVC . В табл. 6.1 приведена сводка по главе. Таблица 6. 1. Сводка по главе Задача Добавление к проекту пакетов .N ET Добавление к проекту пакетов JavaScript или CSS Просмотр результатов изменения представления или класса Отображение детальны х сообще ний в браузере Получение детальной информа ции и контроля над выполнением приложения Повторная загрузка одного или большего числа браузеров с при менением Visual Studio Сокращение количества НТТР запросов и ширины полосы про пускания, требуемой для файлов JavaScript и CSS Решение Отредактируйте раздел зависимостей (dependencies) файла proj ect . j son или воспользуйтесь инструмен том NuGet Создайте файл bower. j son и добавь те требующиеся пакеты в его раздел dependencies Используйте модель итеративной разработки Используйте страницы исключений разработчика Используйте отладчик Используйте средство Browser Link (Ссылка на браузер) Листинг 6.1 -6 .6 6.7, 6.8 6.9-6.11 6.12 6.13 6.14 -6 .16 Используйте расширение Bundler & Minifier 6 .17-6 .28 (Упаковщик и минификатор) На заметку! Ка к объяснялось в главе 2, в Microsoft заявили , что в будущих выпусках Visual Studio изменят инструменты , которые применяются для создания приложе ний ASP. NEТ Core . Это означает, что приводимые в данной главе инструкции могут устареть . Проверяйте веб-сайт издательства на предмет пересмотренных инструкций, которые я напишу, когда новые и нструменты станут стабильными .
Глава 6. Работа с Visual Studio 137 Подготовка проекта для примера Для ц елей настоящей главы мы создали новый проект ASP.NET Саге Web Application (.NET Саге) (Веб-приложени е ASP.NET Core (.NET Core)) по имени WorkingW ithVisualStudi o, и спол ьзуя шаблон Empty (Пустой). Мы добавили сборку МVС за счет редактирования раздела dependenci es файла proj ect. j son (листинг 6 .1) . Листинг 6.1 . Добавление сборки MVC в файле proj ect. j son " dependenci es " : { " Microsoft . NE TCore . App": " version ": 11 1.0.0", " type ": " platform " }, }' "Microsoft.AspNetCore . Diagnostics": "1.О . О", "Microsoft . AspNetCore . Server .I ISintegrat ion ": " 1 . 0 . 0 ", " Microsoft . AspNetCore . Server . Kestre l": "1. 0 . 0 ", "Microsoft.Ext ensi ons .Logging .Consol e ": "1.0 .0 ", 11 Мicrosoft . AspNetCore. Mvc" : 11 1.О.О 11 Затем мы включили инфраструктуру MVC с ее стандартно й конфигурацией в фай ле Startup . cs (листинг 6.2). Листинг 6.2 . Включение инфраструктуры MVC в файле Startup. cs using Microsoft . AspNetCore . Bui lde r ; using Microsoft .AspNet Core .Hosti ng; using Microsoft . AspNetCo r e . Http ; using Mi crosoft .Extens i ons .Depe n denc yinj ection ; using Microsoft . Extensions . Logg i ng ; namespace Work i ngW i thVi sualStud i o puЫic class Sta r tup { puЫ i c void ConfigureSe r vices (IServi ceCol lecti on se r v i ces) { services.AddМvc(); puЫic void Configu r e (IAp pl i catio nBuilder ар р, IH ost ingEnv i ronment env , ILoggerFact ory l ogg erFact ory) { app.UseMvcWithDefaultRoute(); Создание модели Со здайте папку Mode l s и добавьте в нее файл класса по имени Produ ct . cs с оп редел е ни ем, показанным в листинге 6.3 .
138 Часть 1. Введение в инфраструктуру ASP. NЕТ Саге MVC Листинг 6.3 . Содержимое файла Product. cs из папки Models namespace Work i ngWithVisualStudio . Models puЫic class Product { puЫic string Name { get ; set; } puЬlic decimal Price { get ; set; Чтобы создать простое хранилище объектов Product, добавьте в папку Models файл 1<ласса по имени SimpleReposi tory . cs и поместите в н е го опр ед еле ние, при веденное в листинге 6.4 . Листинг 6.4 . Содержимое файла SimpleReposi tory. cs из папки Models using System . Collections . Generic ; namespace WorkingWithVisualStudio.Models puЫic class SimpleRepository { private static SimpleRepository sharedRepository = new SimpleRepository( ) ; private Dictionary<string , Product> products = new Dictionary<string , Product>(); puЬlic static SimpleRepository SharedRepository => sharedRepository; puЫic SimpleRepository() { var initialitems = new [] { new Product { Name " Kayak ", Price = 275М }, new Product ( Name "Lifejacket ", Price = 48. 95М } , new Product { Name " Soccer ball ", Price 19. 50М } , new Product { Name "Corner flag", Price = 34.95М } }; foreach (var р in initialitems) { AddProduct(p); puЫic IEnumeraЫe<Product> Products => products.Values; puЫic void AddProduct(Product р) = > products.Add(p.Name, р) ; Класс SimpleReposi tory хранит объекты модели в памяти, т.е. любые внесенные в модель изменения утрачиваются, когда приложение останавлива ется или переза пускается . Для примеров этой главы непостоянного хранилища вполне достаточно, но такой подход не может применяться во многих реальных про ектах; в главе 8 рас сматривается пример создания хранилища, которое запоминает объекты модели на постоянной основе, используя реляционную базу данных. На заметку! В листинге 6.4 определено статическое свойство по имени SharedRepos i tory, предоставляющее доступ к единственному объекту SimpleReposi tory , который может применяться повсюду в приложении . Это не является установившейся практикой, но я хочу продемонстрировать распространенную проблему, с которой вы столкнетесь при разработке приложений MVC; в главе 18 будет описан более эффективный способ работы с разделяемыми компонентами.
Глава 6. Работа с Visual Studio 139 Создание контроллера и представлени я Добавьте в проект папку Control l er s и поместите в нее ф айл класса по имени HorneController . cs, в котором определен контролл е р. показанный в листинге 6.5 . Листинг 6.5 . Содержимое файла HomeController. cs из папки Controllers u sing Microsoft . Asp NetCore . Mvc ; using WorkingWithVis u alStudi o . Mo d el s; narnespace Worki ngWithVisualStudio.Cont r ol lers puЬlic class HorneContr oll er : Cont r oller { puЫic IAct ionR esult Inde x( ) => View(SirnpleRepository . SharedRepository . Pr oducts) ; Здесь имеется един ственный метод действия Index () ,который получает вс е объ екты модели и переда ет их методу Vi ew () для визуализации стандартного представ ления. Чтобы добавить это пр едставление, создайте папку Views/Home и поместите в нее файл представления по имени I n dex . cshtml, содержимое которого приведено в листинге 6 .6. Листинг 6.6 . Содержимое файла Index. cshtml из папки Views/Home @model IEnurneraЫe<WorkingWithVisualStudio.Models. Product> @{ Layout = null ; < ! DOCTYPE htrn l> <html> <head> <meta name= " v iewport " c ont ent=" width=device - wi dth " /> <title>Working with Vis ual Studi o</t i t le> </head> <body> <tаЫе> <thead> <tr><td>Narne</td><td >P r ice </td></ t r> </thead> <tbody> @foreach (var р in Model) <tr> <td>@ p.Narne</td> <td>@p . Pr i ce</td> </tr> </tbody> </tаЫе> </body> </html> Представление включает т аблицу, в которой с помощью Rаzоr-цикла f o r each со здаются строки для всех объектов модули, причем каждая строка содержит ячейки для значений свойств Name и Pr i c e. Запустив пример приложения, вы увидите ре зультаты. п01ш занные на рис. 6.1 .
140 Часть 1. Введение в инфраструктуру ASP.NET Core MVC - -·- ----------- -----·1 С l.~~~~~ost:612~----------···----·--~ -- ------ -------· ----------~- ~- -~ Nаще Pric e Kayak 275 Lifejacket 48.95 Soccer ball 19.50 Сошеr flag 34 .95 Рис. 6.1. Выполнение примера приложения Выбор исполняющей среды .NET При создании нового проекта ASP.NET Core предлагается выбрать один из двух шаблонов проек тов с похожими названиями : ASP.NET Core Web Application (.NET Core) (Веб-приложение ASP. NET Core (.NET Core)) и ASP.NET Core Web Application (.NET Framework) (Веб-приложение ASP.NET Core (.NЕТ Framework)). Оба шаблона можно использовать для создания приложений с применением инфраструктуры ASP.NET Core MVC, а отличаются они тем, какая исполняющая сре да .NET выполняет код . .NE T Core - это небольшая оптимизированная исполняющая среда, первоначально созданная специально для ASP.NET, но теперь она играет более широкую роль для других типов приложений .N ET. Она была спроектирована как межплатформенная и предоставляет возможности для раз вертывания приложений ASP.NET за пределами традиционного набора платформ Windows, а так же в легковесные облачные контейнеры, подобные Docker. Исполняющая среда .N ET Core будет поддерживать Windows , macOS, FreeBSD и Linux; она спроектирована как модульная и включает только сборки, которые требуются приложению, что дает в результате меньший и более простой отпечаток для развертывания. Кроме того, АРl-интерфейс .NET Core API также имеет меньшие размеры, поскольку из него изъяты средства, которые являются специфичными для Windows и не могут поддерживаться на других платформах . В краткосрочной перспективе выбор исполняющей среды для проектов будет управляться ис пользуемыми инструментами и библиотеками . У независимых поставщиков уйдет некоторое время на обновление своего программного обеспечения для работы с .NET Core и доведение его до уровней стабильности, требуемых для производственного применения. Если вы зависи те от пакета либо инструмента, которому необходима полная платформа .NET Framework (или у вас есть унаследованная кодовая база, обновить которую невозможно), тогда при создании проектов ASP.NEТ вам придется использовать вариант ASP.NET Core Web Application (.NET Framework). Вы по-прежнему можете применять все средства, описанные в настоящей книге, и единственное отличие будет связано с исполняющей средой, которая запускает код. Тем не менее, будущим ASP. NEТ является среда .NET Core. Это вовсе не означает, что вы должны немедленно перевести на нее существующие проекты, но означает, что вам не следует создавать новые зависимости от .NET Framework, если их можно избежать, и при выборе новых инструмен тов и библиотек понадобится учитывать возможность поддержки ими .NET Core. Дополнительные сведения о .NЕТ Core доступны по адресу https: / /docs .rnicrosoft. corn/en-us/ dotnet/articles/welcorne.
Глава 6. Работа с Visual Studio 141 Управление программными пакетами Проектам ASP.NET Core МVС требуются два разных вида программных пакетов. В последующих разделах будут описаны эти типы пакетов вместе с инструментами, которые среда Visual Studio предлагает для управления ими. Инструмент NuGet Среда Visual Studio предоставляет графический инструмент для управления паке тами .NET, который включается в проект. Чтобы открыть этот инструмент, выберите в меню Tools~NuGet Package Manager (Сервис~Диспетчер пакетов NuGet) пункт Manage NuGet Packages for Solution (Управл ение пакетами NuGet для решения) . Инструмент NuGet откроется и отобразит список уже установленных пакетов (рис. 6 .2). Bro'.V$e ~ Updates ConsoHdate St.trth lttft"[) - Mltro,oft.A s pNetCote.Dlllg nostlcs Ьу Mlooio11.o\lpNotC01t.O!•gn1a1ш 8'о ASPJ~fТCo1t middfcw1rt for U(1:pt>0n hcrtdГ.ntJ, e:ctci1tion df1pl.y p•ljt\, 111d di.1gn"'1k~ 1nfo11Ntion. Jrn:lvdes devtloptr U<tp!!On P'!i" m1ddJtw~te, tнq}tiOl'I h•n~. vl.0.0 Mlcrosoft .AspNetCore .Sc:rver.llSJntegratlon bylA1c10'. 0fl .AsJJfle1Co1t.Sm·t _ ..i .o.o t!) ASP.NП Co•t c.cmpotм:nti fc1 wo1l~n9with1~ h~ д!pNt1CcrtM<1dul•. 18 Mlcrosoft.AJpNe1Core.S.rvtr.llSlntegration.Tool s ЬуМЮ> v!Л.O·prlE'М"NHin11 ~ llS1rittg1.1tion yublish t.:io~ for .NfТ Cort ClL Cant11n' tht dctntl·puhll,h· lllt1~•1t 111 comn11rn:1 lc1 pЩ:lnk ing wdi 1ppl1c.1Ucrn !о bt hoottd ustng ltS. • M/crosoft.AspNotCore.Scrvcr.Кo1tre l ЬyM•c101oh.д1ptj(1Ca•c.StNcr.K~t1tl /\SP.Шf Ccrt ~tsщl cro~~·pl1tfc1mwct- ltNtt. vl .Cl.O 1111 Mlcrosoft.Extenslons. Logglng.Con,olc Ьу M1crosott.Ьl:tn11on1.. t o99•n9.Con1 ... 1 .0 .0 -О Con1cl t to99trptov1dtrнт1pltmtnatю11 fo1 Mkro1011 .(;..tcn1iont.L0991n9 111 MlcroJoft. NEТCore.App ЬуМюоw.t ~ дtti cf .NET lJll"~ INI olt' mduded ln the dt!1i.rlt .NП Cort f IK411on modrL ~1.0.0 Manage Packages for Sol u lio n . о 1 ."8 Micro so r.t .AspNetCore.Mvc j Vщlon(,) · 1 5 . 10P1ojtct :_---~-:i ' 0 Ut\Vlor1cincJт'MhVi~u 1.0.О 1 1 1 1 1 -- --- -- __ :J lм t.llled: 1 .!1.О Ot:Кrlpllon ASP.IJE TCottMYC i1.i web fщТitio1cnl: t~t 9r1n you1 p o,.."ttfu~ p1lltt,.,1·b.1~«iw1ytobuild dyntmit .... 11ь1rtei 1r.d wtb AP!~. ASP.NH (Ot( мvс " Рис. 6.2 . Использование диспетчера пакетов NuGet На в1шадке lпstalled (Установленные) приводится сводка по пакетам, которые уже установлены в проекте. Вкладку Browse (Обзор) можно применять для нахождения и установки новых пакетов, а вкладку Updates (Обновления) - для получения списка пакетов, для которых были выпущены свежие версии. Список и местоположение пакетов NuGet Инструмент NuGet управляет содержимым раздела зависимостей (dependencies) файла project . j son , который создается Visual Studio при настройке нового проек та, даже когда используется шаблон Empty. На заметку! В Microsoft намерены изменить инструментальную оснастку для ASP.NET в бу дущих выпусках Visual Studio. Одно из изменений, которое было анонсировано (но не ре ализовано), связано с тем, что файл proj ect. j son больше не будет применяться для управления пакетами NuGet. Проверяйте веб-сайт издательства на предмет обновлений для этой книги, когда компания Microsoft выпустит новые версии .
142 Часть 1. Введение в инфрастру кту ру ASP.NEТ Core MVC Другие разделы файла proj ect. j son будут описаны в главе 14, но если вы вни мательно изучите список пакетов, отображаемый инструментом NuGet, то заметите, что он соответствует элементам раздела dependencies, которые для примера проек та выглядят следующим образом: 11 dependencies 11 : { 11 Microsoft . NETCore.App ": "version 11 : "1.0.0", "type": "platform" }' }' " Microsoft . AspNetCore . Diagnostics ": "1.0 .0 11 , " Microsoft . AspNetCore.Server . IISintegration" : "1. 0 . О", "M icrosoft.AspNetCore . Server.Kestrel ": " 1.0 . 0 ", " Microsoft . Extensions . Logging . Console": 11 1.0.0 11 , 11 Microsoft . AspNetCore.Mvc 11 : 11 1.0 .0" Каждый пакет указывается с использованием его имени и обязательного номера версии. Некоторые пакеты, такие как Microsoft . NetCore . Арр в примере про екта, имеют дополнительную конфигурационную информацию, как объясняется в главе 14. Среда Visual Studio отслеживает содержимое файла proj ect . j son, т.е. вы можете до бавлять или удалять пакеты , редактируя файл напрямую, что и делается повсеместно в книге , т.к. это помогает гарантировать получение ожидаемых результатов , если вы прорабатываете примеры. Когда вы применяете NuGet для добавления в проект какого-то пакета, он автома тически устанавливается наряду со всеми пакетами, от которых зависит. Исследовать пакеты NuGet и их зависимости можно, открыв в окне Solution Explorer элемент References (Ссылки), который содержит записи для всех пакетов NuGet в файле proj ect . j son. В результате развертывания записи пакета отображаются пакеты, от которых он зависит (рис . 6.3). Solution Explo rer ФФ_@I~-1. ~-r~!~- 3..:~г:!:~о~:ХУ~!~:.:) _ · - - • ·1 Referenc~ • ili .NEТCoroApp,Vorsi on:v1 .0 t> ·~ Microsoft.AspNetCo re.Oiagnortics (1.0.0) ~ ·~ MicrosoftAspNetCo re.Мvc.ApiExplorer (1.0 .0) ·~ Mic rosoft.AspNetCore.Мvc .Cors (\.О.О) ·~ Microsoft.AspNetCore.Mvc .DataAnnot"tions (1.0 .0) t> ·~ Microsoft .Asp NetCore.Mvc.FormottersJson (1.0 .0} t> ·~ Microsoft.AspNetCore.Mvc.loc"lization (1. 0 .0) ·~ Microsoft.AspNotCore.Мvc .Rdzor (1.0 .0) 0 1lj Microsoft.AspN<Kore.Mvc.TogHelpers (1.0 .0) ·~ Micro s: oft.AspNetCore. Мvc .ViewFeature.s (1.0 .0) ·~ Microsoft.Extensions.Caching.M emory {1.0 .0) ·~ Microsoft. Extensions.Oependencylnjection (1.0 .0) • ·• Microsoft.As pNotCore.Mvc . dl l ~ 0fl! Microsoft.AspNetCo re.Se.rver.llSlnt~ration (1.0 .0) ·~ Microsoft .AspNotCor~Server.Kestrel (1.0.0) 'l lj Microsoft.Extensions.Logging.Console (1.0.0) 0 il} Microsoft.NEТCore.App {1.0.0} ·~ Microsoft,Visua l Studio.\Чeb.Browserlink. loader (14.0 .0) • CJх Рис. 6 . 3. Раздел References окна Solution Explorer
Глава 6. Работа с Visual Studio 143 На рис. 6.3 видно, что при добавлении пакета Mi cros o ft. As pNetCo r e . Mv c в лис тинге 6.1 инструмент NuGet загружает и устанавливает его и множество других паке тов, которые требуются для разработки приложений MVC . Инструмент Bower Пакет клиентской стороны включает содержимое, которое отправляется клиенту , такое как файлы JavaScript, стили CSS либо изображения . Для управления этими пакетами можно привычно пользоваться инструментом NuGet, но ASP.NEТ Core MVC полагается на новый инструмент под названием Bower. Инструмент с открытым ко дом Bower был разработан за пределами Microsoft и мира .NET и широко применялся в разработке веб-приложений, отличных от ASP.NET. В действительности Bower стал настолько успешным , что некоторые популярные пакеты клиентской стороны рас пространялись только через Bower. Список пакетов Bower Пакеты Bower указываются в файле bower . j s on . Чтобы создать этот файл , щелк ните правой кнопкой мыши на элементе проекта Wo rkingW i thVisua l Studio в окне Sol uti oп Ex plo re r, выберите в контекстном меню пункт Add<=:>New lte m (Добавить <=:> Новый элемент) и укажите шаблон элемента Bowe r C o пfiguratio п Fil e (Файл конфигурации Bower) из категории Clie пt- s i d e (Клиентская сторона), как показано на рис. 6.4 . ASP.NET (lient-side Code ~ Online Sort Ьу: Def•ult TypeScript JSON Configur•tion File Client-s ido n рП1 Co11figuration File Сlien\· •ide rJs Gulp Configurotion File Client -side rJSGrunt Configu r•tion File ClienHide п JSON Schemo Fi le Client-side гJs <iiil>J JS XFile Client-side (lick here to go opline • nd find !empla\б. • Туре: Client-side Bo wer Configuration and .bo 1.;ve rrc . Рис. 6.4. Создание файла конфигурации Bower На заметку! Для загрузки пакетов клиентской стороны Bower использует инструмент g i t. Чтобы обеспечить корректную работу Bower, потребуется заменить версию gi t из Vis ual Studio, как было описано в главе 2.
144 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Среда Visual Studio установит b ow er. j s o n в качестве имени и после щелч ка на кнопке Add (Добавить) добавит в проект файл со стандартным содержи мым (листинг 6. 7). Совет. По умолчанию Visual Studio скрывает файл b owe r . j s o n; чтобы его было видно, нуж но щелкнуть на кнопке Show All Files (Показать все файлы) в верхней части окна Solution Explorer. Листинг 6. 7. Стандартное содержимое файла bower . j s on "name ": "asp .net", "private": t rue , "depende ncies ": } В листинге 6.8 демонстрируется добавление в файле b ower . j s o n пакета клиент ской стороны, что делается включением записи в раздел dependenci es с примене нием такого же формата, как в файле proj ect. j son. Совет. Хранилище пакетов Bower находится по адресу h ttp: //bower . io/ sea r ch, где можно искать пакеты для добавления в свои проекты. Листинг 6.8 . Добавление пакетов в файле bower. j son "name ": "asp.net", "private": true, "depende nci e s": { "bootstrap": "3.3.6" Выделенная полужирным строка обеспечивает добавление в пример проекта СSS пакета Bootstrap. При редактировании файла b ower. j s on среда Visual Studio предло жит список имен пакетов и перечислит доступные версии пакетов (рис. 6 .5). l>(J \'Jork1n9WithVosuolStud•o - bower.1son' - С1Х NuGet • Solu tion • Sch e m a: http ://json.sc hem astore.org/ bower в{ - - - 1 \ "narne" : "asp .net" , "p1·ivate" : tгue , 8 "dependen<ies" : { / "bootstrap" : " ~ f}} ! f..-3.-3.б----./ т~e lot~t ~i~Ы•v•rsion-;r th• ~.~k•g• " ш лз.З.б ~ -3.З.б Рис. 6.5 . Перечисление доступных версий пакетов клиентской стороны
Глава 6. Работа с Visual Studio 145 На момент написания главы последней версией пакета bootst r ap была 3.3 .6. Однако обратите внимание, что Visual Studio предлагает три варианта: 3. 3 . 6, л 3. 3 . 6 и - 3. 3. 6. Номера версий в файле b o we r . j son м огут указываться в виде диапазо на разнообразными способами, наиболее полезные из которых описаны в табл. 6.2. Самый безопасный способ указания пакета предусматривает использование явного номера версии. Это гарантирует, что вы всегда будете работать с той же самой верси ей до тех пор, пока пр еднамере нно не обновите файл b o wer . j son для запроса другой в е рсии. Таблица 6.2. Ра спространенные форматы для номеров версий в файле Ьower. j son Формат 3.3 .6 * >3.3 .6 >=3 .3 .6 <3.3 .6 <=3 .3 .6 -3.3.6 лз.3.6 Описание Выраже ние номера версии напрямую приведет к установке пакета с точно совпадающим номером версии, например , 3 . 3 . 6 Применение символа звездочки позволяет инструменту Bower загружать и устанавливать любую версию пакета Снабжение номера версии префиксом > или >= позволяет инструменту Bower устанавливать любую версию пакета, которая больше либо больше или равна заданной версии Снабжение номера версии префиксом < или <= позволяет инструменту Bower устанавливать любую версию пакета, которая меньше либо меньше или равна заданной версии Снабжение номера версии префиксом в виде тильды (символ - ) позволяет инструменту Bower устанавливать версии , даже если номер уровня исправ лений (последнее число в номере версии) не совпадает. Например, указание -3 . 3. 6 позволяет инструменту Bower устанавливать версию 3 .3.7 или 3.3.8 (которая будет исправлена до версии 3.3 .6), но не версию 3.4.0 (которая бу дет новым младшим выпуском) Снабжение номера версии префиксом в виде символа л позволяет инстру м енту Bower устанавливать версии, даже если номер младшего выпуска (вто рое число в номере версии) или номер исправления не совпадает. Например, указание л 3 . 3 . О позволит Bower устанавливать версии 3 .3.1 , 3.4.0 и 3 .5.0, но не версию 4 .0.0 Совет. Для примеров в настоящей книге мы создали и отредактировали файл bower . j s o n напрямую . Редактировать файл очень просто , к тому же это способствует получению предсказуемых результатов в случае проработки примеров. Среда Visual Studio также предоставляет графический инструмент для управления пакетами Bower, который можно открыть, щелкнув правой кнопкой мыши на проекте WorkingWithVisualStudio в окне Solution Explorer (родительский элемент файла b o we r. j s o n ) и выбрав в контекстном меню пункт Manage Bower Packages (Управление пакетами Bower) . Среда Visual Studio отслеживает изменения в файле bower . j son и автоматически задействует инструмент Bower для загрузки и уста новки пакетов. Посл е сохранения изменений, внесенных в файл согласно листингу 6.8, среда Visual Studio загрузит па кет Bootstrap и установит его в папку wwwroot/ lib (рис. 6.6).
146 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC 1> bootstrap t> jquery Рис. 6.6 . Добавление в проект пакетов клиентской стороны Подобно NuGet инструмент Bower управляет зависимостями пакетов, добавляемых в проект. Некоторые расширенные средства пакета Bootstrap завис ят от JavaScript- библиoтeки jQuery, отчего на рис . 6.6 показаны два пакета. Для просмотра списка па кетов и их зависимостей необходимо развернуть элемент Depeпdeпcies (Зависимости) в окне Solution Explorer (рис. 6.7). ... ii.J Bo wer ~ ~ bootstrap (3.3 . б) ~ jquery (2.2 .4) "'1:1 х Рис. 6. 7 . Исследование пакетов клиентской стороны и их зависимостей Что произошло с инструментами NPM и Gulp? На начальных стадиях разработки ASP.NET Соге в Microsoft адаптировали два других популярных инструмента с открытым кодом извне экосистемы .NET - NPM и Gulp . Средство NPM представ ляет собой диспетчер пакетов для инструментов разработки , который выполняется JаvаSсriрt механизмом Node .js , а Gulp является основанным на JavaScript инструментом запуска задач, который позволяет писать сценарии для выполнения задач разработки , таки х как объединение и минификация файлов . Перед самым выпуском ASP.NET Саге 1.0 в Microsoft внесли изменение в ядро и упомянутые инс трументы больше не задействуются автоматически в шаблонах проектов MVC. Одна из наиболее распространенны х задач , где использовался инструмент Gulp , теперь выполняется расширением Visual Studio, которое описано в разделе " Подготовка файлов JavaScript и CSS для развертыва ния " далее в главе . Среда Visual Studio по-прежнему поддерживает инструменты NPM и Gulp, и они все еще могут применяться для проектов, которые имеют дело со сложным компонентом клиентской стороны . Это может быть полезно, поскольку существуют удобные инструменты и пакеты , которые доступ ны толь ко через NPM и могут настраиваться исключительно с использованием Gulp . За деталями обращайтесь в мою книгу Pro Client Development for ASP.NEТ Саге MVC Developers.
Глава 6. Работа с Visual Studio 147 Итеративная разработка Разработка веб-приложений часто может быть итеративным процессом, когда вы вносите небольшие изменения в представления или классы и запускаете приложение, чтобы протестировать результат. Инфраструктура МVС и среда Visual Studio на пару поддерживают такой итеративный подход, делая наблюдение за влиянием изменений быстрым и легким. Внесение изменений в представления Razor Во вр емя разработки изменения, внесенные в представления Razor, вступают в силу, как только поступает Н1ТР-запрос от браузера. Чтобы увидеть это в работе, за пустите приложение, выбрав пункт Start Debugging (Запустить отладку) в меню Debug (Отладка) , и после открытия вкладки браузера, отображающей данные, внесите в файл Index. cshtrnl изменения, приведенные в листинге 6.9 . Листинг 6.9 . Внесение изменений в файле Index. cshtml @rnodel IEnurneraЬle<WorkingWithVisualStudio.Models.Product> @{ Layout = null; } < ! DOCTYP E html> <htrnl> <head> <meta name =" v i ewport " content="width=device- width " /> <title>Working with Vi s ual Studio</title> </head> <body> <hЗ>Products</hЗ> <tаЫе> <thead> <tr><td>Name</td><td> Pri ce</ td></ tr> </thead> <tbody> @foreach (var р in Model) <tr> <td>@p .N ame</td> <td>@($"{p.Price:C2}")</td> </tr> } </tbody> </tаЫе> </body> </html> Сохраните изменения в представлении Index и перезагрузите текущую веб-стра ницу с помощью кнопки обновления в браузере. Изменения в представлении (добав ление заголовка и форматирование свойства Price объекта модели как денежного значения) оказьmают воздействие и отображаются в браузере (рис. 6.8) . Совет. Процесс подготовки к использованию представлений Razor рассматривается в главе 21.
148 Часть 1. Введение в инфраструктуру ASP.NET Саге MVC С !. <D localhost.:61207 *j:1 ---- --==-----==------ -===--·--::::::::......·-·-! P1·od11cts Nаше Price Lifejacket S48 .95 Soccer ball S19 .50 Сошеr flag $34 .95 L --- ! 1 1 1 ·- --·--__ ) Рис. 6.8 . Результаты внесения изменений в представление Внесение изменений в классы С# Для классов С#, включая контроллеры и модели, способ обработки изменений за· висит от того, как запускается приложение. В последующих разделах объясняются два доступных подхода, которые инициируются посредством разных пунктов в меню Debug и кратко описаны в табл. 6.3. Таблица 6.3 . Пункты меню Debug Пункт меню Start Without Debuggiпg (Запустить без отладки) Start Debuggiпg (Запустить отладку) Описание Классы в проекте компилируются автоматически, когда посту пает НТТР-запрос, делая возможной более динамичную практи ку разработки . Приложение запускается без отладчика , что не позволяет взять под контроль выполнение кода При таком стиле разработки вы должны явно компилировать проект и перезапускать приложение, чтобы изменения вступили в силу. Во время выполнения к приложению присоединен отлад чик, позволяя инспектировать его состояние и анализировать причины возникновения любых проблем Автоматическая компиляция классов В течение нормальной стадии разработки быстрый итеративный цикл позволя ет видеть результаты изменений немедленно независимо от того, связаны они с до бавлением нового действия или с изменением способа, которым выбираются данные модели представления. Для разработки такого вида Visual Studio поддерживает об наружение изменений , как только получен НТГР-запрос из браузера, и автоматичес кую перекомпиляцию классов. Чтобы посмотреть, как это работает, выберите пункт Start Without Debugging (Запустить без отладки) в меню Debug (Отладка) среды Visual Studio. После отображения данных приложения в браузере внесите в контроллер Home изменения, показанные в листинге 6.10.
Глава 6. Работа с Visual Studio 149 Листинг 6.1 О. Фильтрация данных модели в файле HomeCon troller. cs using Microsoft . AspNetCore . Mvc ; using WorkingWithVisualStudio . Models ; using Systern.Linq; narnespace WorkingWithVisua l Studio . Cont r ol l ers puЫic class HorneController : Controller { puЫic IActionResult Index() => View(SirnpleRepository.SharedRepository.Products .Where(p => p.Price < 50)); Изменения связаны с применением LINQ для фильтрации объектов Product, что бы представл ению передавались только те из них , свойство Price которых имеет значение меньше 50 . Сохраните изменения, произведенные в файле класса контрол лера, и обновите окно браузера, не останавливая или не перезапуская приложение в Vlsual Studio. Отправленный брау з ером НТГР-запрос инициирует процесс компиля ции, и приложение будет запущено повторно с использованием модифицированного класса контроллера, генерируя результаты, в которых из таблицы исчез товар Kayak (рис. 6 .9). С) Working with Vi~ual S с Х 11 С [ Ф localhost :6120:_________~ : ----· ---·· - ---------- -- -----·---·------··-- -~- P1·oducts Nаше Price Lifejacket $48.95 J Soccer ball $19 .5 0 1 Соше.1' flag S34.95 '------------------------ _J Рис . 6.9 . Автоматическая компиляция классов Средство автоматической компиляции удобно, когда все идет по плану . Его недо статок в том, что ошибки этапа компиляции и времени выполнения отображаются в браузере, а не в Visual Studio, затрудняя выяснение причины возникновения пробле мы . В качестве примера в листинге 6.11 демонстрируется добавление ссылки null в коллекцию объектов моделей внутри хранилища. Листинг 6.11 . Добавление ссылки null в файле SimpleReposi tory. cs using Systern . Collections . Generic ; narnespace WorkingWithVisualStudi o . Models puЫic class SirnpleRepository { private static SirnpleRepository sharedRepository = new SirnpleRepository();
150 Часть 1. Введение в инфраструк туру ASP.NEТ Саге MVC private Dictionary<string , Product> products = new Dictionary<string , Product>(); puЫic static SimpleRepository SharedRepository => sharedRepository ; puЫic SimpleRepository() { var initialitems = new[] { }; new Product { Name "Kayak", Price = 275М ), new Product { Name "Lifejacket ", Price = 48 . 95 М }, new Product { Name " Soccer ball", Price 19. 50 М } , new Product { Name "Corner flag", Price = 34 . 95 М ) foreach (var р in initialitems) { AddProduct(p) ; products. Add ( "Error" , null) ; puЬlic IEnumeraЬle<Product> Products => products . Values ; puЫic void AddProduct(Product р) => products . Add(p . Name, р); Средство InteШSense среды Visual Studio будет подсвечивать места в коде, где при сутствуют проблемы с синтаксисом. но проблема вроде ссьmки null не будет обнару жена вплоть до запуска приложения. Перезагрузка страницы в браузере приведет к тому, что класс SimpleReposi tory скомпилируется, а приложение запустится зано во. Когда инфраструктура МVС создает экземпляр класса контроллера для обработки НТГР-запроса от браузера, конструктор HomeController создаст экземпляр класса SimpleReposi tory, 1шторый в свою очередь попытается обработать ссылку null, добавленную в листинге 6.11 . Ссылка null приведет к возникновению проблемы, но сущность проблемы не будет очевидной, потому что браузер не отобразит какое-то полезное сообщение (а браузер Chrome вообще не выведет никаких сообщений. отобразив взамен пустую вкладку). Включение страниц исключений разработчика Во время процесса разработки при возникновении проблемы может быть удобно отображать более полезную информацию в окне браузера. Это можно делать, включив страницы исключений разработчика, что требует изменения конфигурации в классе Startup, как показано в листинге 6 . 12 . Роль класса Startup подробно объясняется в главе 14, а пока достаточно знать, что вызов расширяющего метода UseDeveloperExceptionPage () устанавливает описательные страницы ошибок. Листинг 6.12 . Включение страниц исключений разработчика в файле Startup. cs using Microsoft.AspNetCore . Builder ; using Microsoft.AspNetCore .H osting ; using Microsoft . AspNetCore .Http; using Microsoft . Extensions.Dependencyinjection; using Microsoft.Extensions .L ogging ; namespace WorkingWithVisualStudio {
Глава 6. Работа с Visual Studio 151 puЫic class Startup { puЫic void ConfigureServices(ISe rviceCollection services) { services.AddMvc(); puЫic void Configure(IApplicat ionBuilder арр , IHostingEnvironment env, ILoggerFactory loggerFactory) { app.UseDeveloperExceptionPage(); app . UseMvcWithDefaultRoute(); Если вы перезагрузите окно браузера, то автоматически запускаемый процесс компиляции перестроит приложение и выдаст более полезное сообщение об ошибке (рис. 6.1 О). D lnt<rмl Server Error х 1-- --~ ~;~-~host:12~.~~=---=· ~ Ап unhandled exception occшred while processing the request. I NL1 llReferenceExceptioп: Ol)jec 1·eference r.ot set to an insta nc e of an object . <lndex>b_ O _O in HomeCont roller. c:.. line 10 • Que1y Cookies Heade1s NullRefereпceException : Object refere1кe rюt set to ап in sta nce of an object . <lr.·:le<> b_O _C •n Hon1e Cont:ro ller . cs 10. . \olhere(p •> р.Р гiс е < 50)) ; f/ cv~Noxt L~lo·,el·Jext 1r, Index . cs html 17. @foгeach (va1· р in Hodel) ( -~-~~·---·· ------- · ··--·--···---:! Рис . 6.1 О. Страница исключения разработчи ка Отображаемого в браузере сообщения об ошибке может быть достаточно для вы явления простых проблем, особенно из-за того, что итеративный стиль разработки означает, что причиной, скорее всего, стали самые последние изменения. Но для уст ранения более сложных проблем - и для проблем, которые не становятся очевидны ми немедленно - требуется отладчик Visual Studio. Использование отладчика Среда Visual Studio также поддерживает выполнение приложения MVC с примене нием отладчика, который позволяет останавливать приложение для инспектирования его состояния и пути прохождения запроса по коду. Это требует другого стиля раз работки , поскольку модификации классов С# не будут применены до тех пор, пока приложение не запустится заново (хотя изменения, вносимые в представления Razor, по-прежнему вступают в силу автоматически) .
152 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Такой стиль разработки не настолько динамичен, как использование средства автоматической компиляции, но отладчик Visual Studio великолепен и может предо ставлять намного более глубокое проникновение в суть способа работы приложения. нежели то, что возможно с помощью сообщений, отображаемых в окне браузера . Чтобы запустить приложение с применением отладчика, выберите пункт Start Debugging (Запустить отладку) в меню Debug (Отладка) среды Visual Studio. Перед запуском приложения среда Visual Studio скомпилирует классы С# в проекте, но вы можете также вручную скомпилировать свой код, используя пункты меню Build (Постро ение). Пример приложения все еще содержит ссылку null, т.е. необработанное исключе ние NullReferen ceExcept i o n, которое генерируется в классе S i mpleReposi tory , прервет приложение и передаст управление выполнением отладчику (рис. 6.11). HomeControПtr.cs ~ Х ~\Vo;.kin9WrthVi$u11IStu~~~p-p,Version_:l~ \4/01ki~~i!_~ViwalStud_i~~~~~m~~~!.~.----·~-----·-,.: B u ~ing ~lic"osoft.д!ipfle-t'Core.Мvc ; li'. lusing Worki ngWithVis ualStudio .М<Юtl::o ; r,-- . ....,.....,.._.,,,...~--:-""::..,,...... ,,. ., __~ -~. -. -- -- ;i using Syste•.linq; /-1.'..-~~~.!!;~ «-~c,epttono<tut~-· _.· _. -- - ~ -- - 2 1 j &:ctpdon thtown: "Sys ttm.Nul\Refe1en ce.f.'щ1ption' in 1 19 n1и1espace Wo гkingWithVi:иJ1йStudio . Cont rollers { ,/ l WoikingW! tliViм'5 tud'ю.dlr 8 p\1Ьli c class 11-)!l'eCм·uol.t~r : Contrclle { / 1 J /' / Additi"onal lnfo~tion: OЬjr.ct rr.ferencl! oot sd to an inst.sn<.t of an objC!.ct.'. •> Vitw( 51ro:rlt'1'to;>cs 1 tor}· · Sh!l':'C!~R!Po~J.~y.Products ! ~~~:~~-· ----------------· · -· · -- 1 puЬlic !1\tticnRc!.iJlt I ndex(} · -· -·- --"-------....,.~- --~ - -- - ..... ··-- ·• -· .lihere(p •> P.·Pric~ < S!)); / f.bШ.!.1?..~~!f!1i!'.!A.!.ti~..o..ЧLts.ti.t!'...Yl!.~.!to!! HM!~ 9Jht..~.~~:;.!bl ~ 1 • t} 1 . Ustt lhe •ntO\'м kt)'lf'.Old tc ctt<!tt dn cbjкt 1nst•nt~ '1 lJ_~_!t'~~~~~lpfo~-:~~~.~~~-·---···-·----~···--··-=-~ ~rch for n1ore Н~р 01'il1ne... 1 "--- --· -- - -- -- - ••" --· -- . " _" - . 1 bc~ptlon ~t1ings-: ! f2J Bruk \.\th~ n tJш rxc.ttp ti11м t).• pe i: thгa 1vn 1д,..;;;-- -- . -- - .. -- --- -·-·- "_,_ _, -·. "----, 1 v""o",;1". j 100о/о. " -i 1 (О р/ e.~ce ptщn detщl to the cl1pbocr1d " ______________________8811!! _ O~nc:c-~~=~tin9~----------------------__J Рис. 6.11. Возникновение необработанного исключения Совет. Если отладчик не перехватывает исключение, тогда выберите пyнктWiпdowsQException Settiпgs (ОкнаQНастройки исключений) в меню Debug (Отладка) среды Visual Studio и удостоверьтесь , что в списке Common Language Runtime Exceptions (Общие исключе ния языка во время выполнения) отмечены флажки для всех типов исключений. Настройка точки останова Отладчик вовсе не показывает основную причину проблемы, а только место, где она проявилась . Подсвечиваемый Visual Studio оператор указьшает, что проблема слу чилась при фильтрации объектов с применением LINQ, но для получения деталей и выявления лежащей в основе причины потребуется проделать небольшую работу. Точка останова - это инструкция, которая сообщает отладчику о необходимости остановить выполнение приложения и передать управление программисту. Вы може те исследовать состояние приложения, посмотреть, что произошло, и при желании возобновить выполнение.
Глава 6. Работа с Visual Studio 153 Чтобы создать точку останова. щелкните правой кнопкой мыши на операто ре кода и выберите в контекстном меню пункт Br eakpointqln sert Breakpoint (Точка остановаqВставить точку останова). В целях демонстрации поместите точку останова на метод AddProduct (} в классе SimpleReposi tory (рис. 6.12). o<J V-/ork1n9V11thVi~ualStud10 - Г-'J Х [!) V/orkingWrthVisuaLSt~_dio"NEТCo reApp, Ve r5ion •] ~ \t./crlungW1thViiu~IStudio.Mode:ls. Simple:Reposi • [ 19 Simpl~('.poяtoty{) 1 '$ 1 li~ 1 1 11 • ll, 100 8 .'7 .. puЫic S!mploRopos itory() { vаг initi.slitems • new[ ] { }; ne\'l Pr·oduct { l~a~ • "Kayak" 1 Price " 27S~' } , ne't'1 Product { tlame .- "Lifejacket" 1 Price • 48.95М }, 11ew P rod tн:t { Nam~ • "Soc.ce1· ball" , Price'"' 19.50!'1 } , new Prodщ:t { fJame • ''Coгner flag" , Ргiсе '"' 34.9SМ } foreach (var р in initi.s litems ) { AddProd uct(p) ; pr od ucts . Add( "Error" , null); puЫic П1\ul!'.eг aЫ~<Product> Products "'> products . Vii!l lues ; puЫic 11o id AddP rod uc t(Produtt р) "" > P:ffAМфhfdl!фW3J ; Рис. 6.12. Создание точки остан о ва ' 1• Выберите пункт меню Deb ugc::>Start Debugging (ОтладкаqЗапустить отладку). чтобы запустить приложение, используя отладчик, или Debugc::>Restart (Отладка~::> Перезапустить), если приложение уже выполняется. Во время обработки начального НТГР-запроса от браузера будет создан экземпляр класса SimpleRepository. а вы полнение кода достигнет точки останова, где возникнет пауза. В этот момент вы можете применять пункты меню Debug среды Visual Studio или элементы управления в верхней части окна для управления выполнением приложения либо использовать разнообразные представления отладчика, доступные через меню DebugqWindows (ОтладкаqОкна), чтобы инспектировать состояние приложения. Просмотр значений данных в редакторе кода Наиболее распространенное применение точек останова связано с отслеживанием дефектов в коде. Прежде чем можно будет исправить дефект, вы должны выяснить , что происходит, и одним из самых полезных средств, предлагаемых Visual Studio, является возможность просмотра и наблюдения за значениями переменных прямо в редакторе кода. Если вы наведете курсор мыши на аргумент р в методе AddProduct (), подсве ченном отладчиком, то появится всплывающее окно, которое показывает текущее значение р (рис . 6 . 13). Рассмотреть всплывающее окно может быть затруднительно, поэтому на рис. 6.13 показана также его увеличенная версия. Результат может не выглядеть особо впечатляющим, т.к. объект данных определен в том же конструкторе, что и точка останова, но это средство работает для любой пе ременной. Вы можете исследовать переменные, чтобы увидеть значения их свойств и полей. Правее каждого значения находится небольшая кнопка с изображением кан целярской кнопки, которую можно использовать для наблюдения за значением пере менной, когда выполнение кода продолжится .
154 Часть 1. Введение в инфраструктуру ASP. NEТ Core MVC Simpl•Repos~ory.cs IOJ"w~rkingWithVis~11IStudio..NETCoreApp.Vers ion " ~ Wo~ki~gWi tl1VisuolStudio .Mo dei~:Si mpl eReposi . ... }; foreaich (vl!lr р in initi alitems ) { 1 nl!\'l Pl"'Qduct { Name • "Corner flag " , Price • 34.9S.Ч } + AddProd uc1:( p); '"s-,,_-p----{W-or-ki-n9-l~-it-hV-is-u•-IS-tu-d-io-.M-o-de-ls-.P-ro-d-uc-.t} о pr-oducts.Add("Errcir" 1 rшl l) ; 1" p.Name Р '" "Koyak" #11 p.P ri ce 275 Gliil р JI p.Name JI p.Pric e {Wo rkingWrthVisualStud io.Mod els. Product} Р •"Кауаk" 275 Рис. 6.1 З. Инспектирование значения данных Наведите курсор мыши на переменную р щелкните на кнопке с изображением канцелярской кнопки, чтобы закрепить ссылку на Product . Разверните закреплен ную ссылку. чтобы также закрепить свойства Name и Price, получив в итоге резуль тат, представленный на рис. 6 . 14 . lf!J Work 1n 9WithVis uolStud10"NEТCort.App, Vers1on ~ \ otk1ng\V1thV1"u"JStud10.Modf:ls.S1 1npl~Repos1 rФ SimpleR~positoryO new Product { Name • "Corner flag") Price • 34.95r>t } о Рис. 6.14. Закрепление значений в редакторе кода Выберите пункт Continue (Продолжить) в меню Debug (Отладк а) среды Visual Studio, чтобы продолжить выполнение приложения . Поскольку приложение выпол няет цикл foreach , снова произойдет пауза, когда точка останова встретится опять. Закрепленные значения показывают, как изменяется объект, присвоенный пер емен ной р . и его свойства (рис. 6.15) .
Глава 6. Работа с Visual Studio 155 G~р JI p.Name /1 p.Price {WorkingWithVisualStudio.Models.Product} Р " "lifejacket" 48.9S Рис. 6.15. Наблюдение за изменением состояния с применением за к реплен н ых значений Использование окна Locals Связанным средством является окно Locals (Локальные) , которое открывается пу тем выбора пункта меню Debugr::>Wiпdows r::> Locals (Отладкаr::> Окнаr::>Локальные). В окне Locals отображаются значения данных похожим на закрепленные значения образом, но здесь присутствуют все локальные объекты, относящиеся к точке останова (рис. 6.16) . locals 1 Name " oi this " 1' Producl< ~oi10) ~oi[1] ~ oi Raw Vie\v ~ !M!!fi!!.ijЩi!!Wi ~ "1: Static members Value {WorkingWit hVisualStudio .M odels.SimpleRepo sitory) Count = 2 {WorkingWithVisualStudio.Models.Product) {\"lorkingWithVisualStudio.Mod<Ols.Product} • 1:1х ; Туре_ __ • _ ··- WorkingWithVisualStudio.Mode ls.Sim pleR epos Syste m.Colfecticns.Generic .I EnumeraЫe<Work WorkingWitl1VisualStudio.Mode ls.P roduct Wo rki ngWithVisualStudio .Model s. Product Sy;tem.Colfectrons.Generic.D1ct1щшy<5tring, i " " р1' Nan10 {WorkingWit hVi sualStudio.Mod els. Product} · soccerЬа11· WorkingWit hVisualStudio.Mod el<.Product Q. • string 1' Price 19.SO decimal Рис . 6.16. Окно Locals Каждый раз, когда вы выбираете пункт Continue , выполнение приложения возоб новляется и в циюrе foreach обрабатывается новый объект. Продолжая поступать так, вы увидите ссылку null в окне Locals и в закрепленных значениях, отображае мых внутри редактора кода . Применяя отладчик для управления выполнением при ложения, вы можете отслеживать его продвижение по коду и получить представление о том, что происходит. Устранить проблему со ссылкой null можно было бы за счет очистки коллекции объектов Product, но альтернативный подход предусм атривает увеличение надеж ности контроллера, как показано в листинге 6.13, где для проверки на предмет зна чений null используется null -условная операция (описанная в главе 4). Листинг 6.13. Исправление проблемы со ссылкой null в файле HomeController. cs using Microsoft . AspNetCore . Mvc ; using WorkingWithVisualStudio . Models; using System . Linq ; namespace Wo rkingWithVisualStudio.Controllers puЬlic class HomeController : Controller { puЫic IActionResult Index() = > View(SimpleRepository.SharedRepository . Products .Where(p => p?.Price < 50));
156 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Отключить точку останова можно, щелкнув правой кнопкой мыши на операто ре кода, где она находится, и выбрав в контекстном меню пункт Breakpoint~Delete Breakpoint (Точка останова<=:> Удалить точку останова). Перезапустив приложение, вы увидите простую таблицу с данными (рис. 6.17). [:) Working with Visu•I Stu, Х · С [Ф~·;h:;s~;~1 207 ·---- ----· - -----;-] ·----·- ------ -· -~· ·-·- P1·o<l11cts Nа ше Pri ce Lifejackc r $48. 95 L Socce1· bal\ $19.50 Сошеr flag $34 .95 ----· --·---------------1 Рис. 6.17 . Устранение дефекта По сравнению с задачами, которые требует реальное выявление ошибок, эта про блема была решена просто, однако отладчик Visual Studio чрезвычайно эффективен, и с помощью многочисленных доступных представлений приложения и управления вы полнением вы можете легко углубляться в детали для выяснения, что пошло не так. Использование средства Browser Link Средство Browser Link (Ссылка на браузер) позволяет упростить процесс разра ботки за счет помещения одного или большего числа браузеров под контроль Visual Studio. Оно особенно полезно, если нужно видеть влияние изменений в некотором диапазоне браузеров. Средство Browser Link работает с или без отладчика, но оно наиболее полезно в случае применения средства автоматической компиляции клас сов. потому что появляется возможность модифицировать любой файл в проекте и видеть влияние изменения, не переходя в окно браузера и не перезагружая страницу вручную. Настройка средства Browser Link Включение средства Browser Link означает добавление в проект еще одной сборки и изменение его конфигурации. В листинге 6.14 демонстрируется добавление сборки Mi cro s oft . VisualStudio. Web . Browse rLink. Loader в раздел dependencies фай лаproject.json. Листинг 6.14. Добавление сборки Browser Link в файле proj ect. j son " dependencies ": { " Microsoft .N ETCore.App ": "version": "1.0.О", " type ": " platform " }' " Microsoft . AspNetCore.Diagnostics ": " 1 . 0 . 0 ",
}' Глава 6. Работа с Visual Studio 157 " Microsoft . AspNetCore.Server . IISintegration ": "1 . 0 . О" , "M icrosoft . AspNetCore . Server. Kes trel 11 : "1.О.О 11 , " Microsoft .Ex tens i ons .Logging.Console ": "1. 0 .О", "Microsoft .AspNetCore . Mvc ": "1. О . О ", 11 Мicrosoft.VisualStudio.Web.BrowserLink.Loader": "14.0 .0" В листинге 6.15 показано соответствующее изменение в классе Startup. Листинг 6.15. Включение средства Browser Link в файле Startup. cs using Microsoft . AspNetCore . Builder; using Microsoft . AspNetCore . Hosting ; using Microsoft . AspNetCore . Http ; using Microsoft.Extensions .De pendencyin jection ; using Microsoft . Extensions .Lo gging ; namespace WorkingWithVisualStudio puЫic class Startup { puЫic void ConfigureServices(IServiceCollection services) { services.AddMvc(} ; puЫic void Configure(IApplicationBuilder арр, IHostingEnvironment env , ILoggerFactory loggerFactory) { app . UseDeveloperExcept i onPage(); app.UseBrowserLink(); app.UseMvcWithDefaultRoute(); Использование средства Browser Link Чтобы понять, как работает средство Browser Link , выберите пункт Start Without Debugging (Запустить без отладки) в меню Debug (Отладка) среды Visual Studio. Среда Visual Studio запустит приложение и откроет новую вклад1<у в окне браузера для отоб ражения результатов . Просмотрев НТМL-разметку , отправленную браузеру, вы заме тите, что она содержит дополнительный раздел, подобный приведенному ниже: < ! DOCTYPE html> <html> <head> <meta name= 11 viewport 11 content= " width=device-width " /> <title>Working with Visual Studio</title> </head> <body> <h3>Products</h3> <tаЫе> <thead> <tr><td>Name</td><td>Price</td></tr> </thead> <tbody>
158 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC <tr><td>Lifejacket</td><td>$48 . 95</td></tr> <tr><td>Soccer ball</td><td>$19 . 50</td></tr> <tr><td>Corner flag</td><td>$34 . 95</td></tr> </tbody> </tаЫе> < ! - - Visual Studio Browser Link --> <script type="application/json" id=" browserLink initializationData"> - - {"requestid":"9eOOcбfB05Bf436981Be7ba315c9bdde", "requestмappingFromServer" : false} </script> <script type="text/javascript" src="http://localhost:56147/e7bB5fe070c5419Ba04ld57c363cee40/browserLink" async="async"></script> < ! -- End Browser Link --> </ body> </html > Среда Visual Studio добавляет в НТМL-разметку , посылаемую бр аузеру, пару эле ментов script , которые применяются для открытия долговечного НТГР-подключения к серверу приложений, чтобы ср еда Visual Studio могла обеспечить принудител ьную перезагрузку страницы браузером. (Если вы не видите элементы script, тогда удос товерьтесь в том, что отмечен пункт ЕnаЫе Browser Link (Включить Browser Link) в меню, показанн ом на рис. 6 . 18.) В листинге 6.16 приведено изменение, внесенное в представл ение Index, которое проиллюстрирует результат использования средства Browser Link. .1ninistrator) р t:! х "ools Test Дnalyze Window Help Adam Freeman • 11 Any CPU . ~ ' • llS Express • ·~~ • ' [ ·ff ~-;j[__~ i:f!.L'~"~J~_!_.:!.:i! .:L . . .. ._ ___. G Refresh Lin ked Browsers ~ Ctrl+Alt. - Enter . .S ~@} BromerlinkOashboard 1 ,~ ЕnаЫе Browser Link " ЕnаЫе CSS Auto·Sync - - --"...,.--- ... " = Solution ltem s 61 global.json Рис. 6.18. Применение средства Browser Link для перезагрузки браузера Листинг 6.16. Добавление временной отметки в файле Index. cshtml @model IEnumeraЫe<WorkingWithVisualStudio.Models.Product > @{ Layout = null; } < !DOCTYPE html> <html> <head> <meta name= " viewport " content= " width=device - width " /> <title>Working with Vi sua l Studio</titl e> </head>
<body> <h3>Produ c ts < /h3 > Глава 6. Работа с Visual Studio 159 <p>Request Time: @DateTime .Now. ToString ("HH:mm: ss") </р> <tаЫе> <thead> <tr><td>Name</td><td>Pri ce</td></ tr> </thead> <tbody> @foreach (var р in Model) <tr> <td>@ p. Name</td> <td>@( $" {p . Price : C2) " )</td> </tr> </tbody > </tаЫе> </body> </html> Сохраните изменение в файле представления и выберите пункт Refresh Linked Browsers (Обновить связанные браузеры) в меню средства Browser Link внутри панели инструментов Visual Studio (см. рис. 6.18). (Если средство Browser Link не работает, попробуйте перезапустить Visual Studio и повторить попытку.) Код JavaScript, встроенный в отправляемую браузеру НТМL-разметку, будет пере загружать страницу, де монстрируя результат добавления , которым является появле ние простой временной отметки. Каждый раз , когда выбирается пункт меню Refresh Linked Browsers , браузер будет делать новый запрос к серверу. Результатом запроса будет визуализация представления Index и генерирование новой НТМL-страницы с обновленной временной отметкой. На заметку! Элементы scr i p t, относящиеся к средству Browser Link, встраиваются толь ко в успешные ответы. Это значит, что если во время компиляции класса , визуализации представления Razor или обработки запроса возникает исключение , то подключение меж ду браузером и Visual Studio утрачивается и вам придется перезагружать страницу, ис пользуя сам браузер, пока проблема не будет устранена. Использование нескольких браузеров Средство Browser Link может применяться для отображения приложения в не скольких браузерах одновременно, что удобно, когда нужно уладить вопросы несходс тва реализаций между браузерами (особенно при реализации специальных таблиц стилей CSS) или увидеть, как приложение визуализируется набором настольных и мобильных браузеров . Чтобы отобрать браузеры, которые будут использоваться, выберите пункт Browse With (Просмотреть с помощью) в меню, связанном с кнопкой llS Express в панели инс трументов Visual Studio (рис. 6.19). Среда Visual Studio отобразит список известных ей браузеров. На рис. 6.20 пока заны браузеры, установленные в моей системе; часть из них установлена вместе с Windows (lnternet Explorer и Edge), а часть - лично мною по причине их широкого распространения.
160 Часть 1. Введение в инфраструктуру ASP.NET Core MVC '' ~D fr ~Q;ii~k L~unf llSExpr~s . / 115 Express WorkingWithVi.sualStudio th• device-..,1idth" /> 'title> Web Bro,vstr (Google Chrome Canary) Brows.Wrth" . Рис. 6.19. Выбор множества браузеров Bro wse rs (select one or more): Google Ch rome Ca nary lnt erna l Web Browse r Opera lnt ern et Br owse r Ptogram: Size of browse r window: [---· --·· ·---] Set as Default .;,.~~......- - - - Рис. 6.20. Отбор браузеров из списка ' Среда Visual Studio ищет общие браузеры во время процесса установки, но с по мощью кнопки Add (Добавить) вы можете указывать браузеры, которые не были об наружены автоматически. Вы можете также настроить инструменты тестирования от независимых поставщиков наподобие Browser Stack, которые запускают браузеры на расположенных в облаке виртуальных машинах, так что вам не придется управлять крупной матрицей операционных систем и браузеров во время тестирования. На рис 6.20 выбраны три браузера: Chrome, Internet Explorer и Edge . Щелчок на кнопке Browse (Обзор) приводит к запуску всех трех браузеров и заrрузки в них URL примера приложения (рис . 6.21). Чтобы посмотреть , какими браузерами управляет средство Browser Link , выберите пункт Browser Link Dashboard (Инструментальная панель Browser Link) в меню средс тва Browser Link; откроется окно Browser Link Dashboard (Инструментальная панель Browser Link) , представленное на рис. 6.22. Инструментальная панель показывает URL, отображаемый каждым браузером , и позволяет обновлять каждый браузер по отдельности.
Глава 6. Работа с Visual Studio 161 EJ Worki ng with Visua1 Stu 1 Х 11\ttp.://l ocilhost:60 )1 8/ ~ 'Norr. in9 ""ilh Visu1'.IStvd- Х · I ~· -) () 1 l0<.1 ~••:: _ • i} ..O:. .~~~~ait1 ~~~~~18~==~:::.~-=--=---=-~ S R•questTime: 17:5 1:26 Pi·odurts 1 P1·oducts 1 Name P rice 11 Nasne Pri ce 1 ReqнestT11110: 17:51.26 Lifejacke t !:48.95 Soccer ball .Е19.50 Lif•jocke t f 4&. 95 1 No111е Price ' Corne r ftag 1:34 .95 \ Soccer ball П9.50 L1 f<;ncket S4S .95 L ·-. ___ --·----- ___ __ j Corner fl ag 04.95 Sосш bnll $19 .5 0 j Сошеr flag $34.95 1 t--·" - -------------·---------·~ --~-- · -- - -- -- Рис. 6.21 . Работа с несколькими браузерами Browser Link Dash board .А \1,'o rkingWithVisua lStudi o (0 co nne ctions) .А Co nnectio ns 1·...zo c ur re"t~onnec 1ons View in Brow~er .А U nkno\'IП (З con nections) .А Connections Chrome • http:/ /l o cal h ost6051 8f Microsoft Edge ... htt p;//l o c a l hostб0518/ Mozilla ... htt p:// localhost60518/ Learn n1ore about Bro\'ISEI Link Рис. 6.22 . Окно Browser Link Dashboard Подготовка файлов Java Script и CSS для развертывания При создании клиентской части веб-приложения вы обычно будете создавать ряд специальных файлов JavaScript и CSS, которые дополняют аналогичные файлы в па кетах, установленных инструментом Bower. Такие файлы требуют специальной обра ботки с целью оптимизации их доставки в производственную среду, чтобы миними зировать количество НТГР-запросов и долю полосы пропускания , необходимую для доставки файлов клиенту. В этом разделе будет описано расширение Visual Studio, которо е Microsoft пр едоставляет для выполнения указанной задачи . Включение доставки статического содержимого Инфраструктура ASP.NET Core располагает поддержкой для доставки статических файлов из папки wwwroot клиентам , но когда проект созда ется с применением шаб лона Empty, упомянутая поддержка по умолчанию отключена. Чтобы включить подде ржку статических файлов , в раздел dep e nde nci es файла p roj ect . j s on потребуется добавить новый пакет (листинг 6 . 17) .
162 Ч асть 1. Введение в инфраструктуру ASP.NET Core MVC Листинг 6.17 . Добавление пакета в файле project. json " dependencies ": { "Microsoft . NETCore . App ": "version": "1 .0 .0", " type ": " platform " }' }' " Microsoft . AspNetCore . Diagn ostics ": " 1 .0 .0", " Microsoft . AspNetCore . Server.IISintegration ": " 1.0 .0 ", "Microsoft . AspNetCore . Server . Kestrel ": " 1 . 0 . 0 ", " Microsoft . Extensions . Logging . Co nsole ": " 1 . 0 .О", "Microsoft.AspNetCore .Mvc": "1 .О . О", " Microsoft . VisualStudio . Web .BrowserLink . Loader": " 14 . 0 . 0 ", "Microsoft . AspNetCore.StaticFiles": "1.0.0" Пакет Microsoft . AspNetCore . Stat icFiles содержит функциональность для обработки статических файлов, которая должна быть включена в кла с се Startup, как показано в листинге 6.18. Листинг 6. 18. Включение поддержки статических файлов в файле S tartup . cs using Microsoft . AspNetCore.Builder ; usi ng Microsoft . AspNetCore .H osting ; using Microsoft . AspNetCore . Http; using Microsoft . Extensions.Dependencyinjection ; using Microsoft.Extensions.Logging; namespace Work i ngWithVisualStudi o puЫ i c class Startup { puЫic void ConfigureServices(I ServiceCollection services) { services . AddMvc() ; puЬlic void Configure(IApplicationBui l der арр , IHostingEnvironment env , ILoggerFactory l oggerFactory) { app . UseDeveloperExceptionPage() ; app . UseBrowserLink(); app.UseStaticFiles(); app .U seMvcWithDefaultRoute() ; Добавление в проект статического содержимого Чтобы продемонстрировать процесс пакетирования и минификации, пон адобится добавить в проект какое-нибудь статическое содержимое и обеспечить возможность его доставки клиенту. Для начала создайте папку wwwroot/css, где будут храниться специальные файлы CSS. Затем добавьте файл по имени first . css, используя шаб лон элемента Style Sheet (Таблица стилей), как показано на рис. 6 .23.
ASP.NEТ Cli,e.nt-~e Code ~ Online Глава 6. Работа с Visual Studio 163 TypeSc:tipt JSX File Cli~nt·side. TypeScript JSON Configuraticn File Client ·side- Bo\VtrConfigu rc!tion file Cl1ent-side npm Con figuration Fi!e ( lient· side N.ame first .css Рис. 6.23. Создание таблицы стилей CSS Поместитевфайл first .сss стилиCSSизлистинга6.19. Листинг 6.19. Содержимое файла f1rs t. css из папки wwwroot/ cs s hЗ{ font- size: 18 pt; font - family: sans - serif ; tаЫе, td { border : 2рх solid Ыасk; border - collapse : collapse ; padding : 5рх; font - family: sans - serif ; Повторите процесс для создания в папке wwwroot/css еще одной таблицы стилей по имени second. css с соде ржимым , прив еденным в листинге 6.20. Листинг 6.20. Содержимое файла second. css из папки wwwroot/css р{ font - family: sans -se rif ; font- size: 10 pt; color: darkgreen ; background-color : antiquewhite ; border : lpx solid Ыасk ; padding : 2рх ; Специальные файлы JavaScript хранятся в папке wwwroot/j s . Создайте эту папку и с применением шаблона элемента JavaScript File (Файл JavaScript) добавьте в нее файл third . j s (рис . 6.24).
164 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC ' ASP. NEl ~client·sid~ (ode " Туре: Client·side f А script fi le co nt ai ning JlJvllScri pt со ~ Onl inf Nllme: f;J f;J Type:S cr ipt File Type Script JSX File TypeSc ript JSO N Co nf igurati on File Bowe r Co nfi gura t1on File Client·sid~ Client·side Clie nt· side Client-~ide Click here. to $19 0 11line an d find t~ third .js 1 { -----------~-~-----·--"-~-·----ц Рис. 6.24. Создание файла JavaScript Поместите в новый файл простой код JavaScript, представленный в листинге 6.21. Листинг 6.21 . Содержимое файла third. j s из папки wwwroot/ j s document . addEventListener( " DOMContentLoaded ", function () { var element = document . createElement("p " ) ; element.textContent = "This is the element from the third .js file "; document . querySelector( "body " ) . appendChild(element) ; )); Нам необходим еще один файл JavaScript. Создай те в папке wwroot/js файл по имени fourth. j s с содержимым , показанным в листинге 6.22. Листинг 6.22 . Содержимое файла fourth. j s из папки wwwroot/ j s document.addEventListener("DOMContentLoaded", function () { var element = document . createElement ( "р"); element. tex tContent = " This is the element fr om the fourth. j s file "; document . querySelector( " body " ) . appendChi l d(element) ; }); Обновление представления Последний подготовительный шаг связан с обновлением представления Index. cshtml для использования новых таблиц стилей CSS и файлов JavaScrlpt (листинг 6.23). Листинг 6.23. Добавление элементов script и link в файле Index. cshtml @model IE numeraЬle<WorkingWithVisua l Studio . Models . Produc t > @{ Layout = null; )
< !DO CTYPE html> <html> <head> Глава 6. Работа с Visual Studio 165 <meta name="viewport" content= " width=device - width" /> <title>Working with Visual Studio</title> <link rel="stylesheet" href="css/first.css" /> <link rel= "stylesheet" href="css/second.css" /> <script src="js/third . js"></script> <script src="js/fourth. js"></script> </head> <body> <h3>Products < /h3> <p>Request Tim e : @DateTime . Now . ToString("HH:mm:ss")</p> < tаЫе> <thead> <tr><td>Name</td><td>Price</td></tr> </thead> <tbody> @foreach (var р in Model) <tr> <td>@p . Name</td> <td>@($ " {p.Price : C2} ")</td> </tr> } </tbody> </tаЫе> </body> </html> Запустив пример приложения, вы увидите содержимое, приведенное на рис. 6.25. Существующее содержимое было стилизовано посредством таблиц стилей CSS, а код JavaScript добавил новое содержимое. D \'Jorking V1ith Vi'u•I Stu· Х ---· ___с _ СФ:~~~:;~~~~=~-~-::..---::::~~-=--=====----_-::::'____~-----~~- :=-~~-- ! \ Products l /Request Time: 17'29: 48 Name Pri ce Lifejacket $48 .95 Socc er ball $19 .50 Corner flag $34 .95 1 1~,Th-l-sl_s_th-e-el-em_en_t_r_ro-,n-the-th-lro-.J-s-fi~---------------------~111 ~his is the elemer.1 fron1 the fourth.js file . Рис . 6.25. Выполнение примера приложения
166 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Пакетирование и минификациS1 в приложениS1х MVC В данный момент есть четыре статических файла, и браузер должен делать четы ре запроса, чтобы получить эти статические файлы. При доставке клиенту каждый такой файл отнимает больше полосы пропускания. чем должен. поскольку содержит пробельные символы и имена переменных, которые являются содержательными для разработчика, но не имеют никакого значения для браузера . Объединение файлов одного и того же типа называется пакетированием. Уменьшение размеров файлов называется минификацией . Обе задачи выполня ются в приложениях ASP.NET Core MVC с помощью расширения Bundler & Minifier (Упаковщик и минификатор) для Visual Studio. Установка расширения Visua/ Studio Первый шаг заключается в установке расширения. Выберите пункт меню Toolsc::>Extensi o ns and Updates (СервисqРасширения и обновления) и щелкните на кате гории On line (Онлайновые), чтобы отобразить галерею доступных расширений Visual Studio. В поле поиска справа вверху введите строку Bund ler & Mini fie r (рис . 6.26). Найдите расширение Bundler & Minifler и щелкните на кнопке Download (Загрузить). чтобы добавить его в Visual Studio. Завершите процесс установки и перезапустите Visual Studio. ~ lnstolled " o~itne:·. ,,. '' • Visual Studio G•lfery ~ Controls ~ Templates • Tools !Seatc ~ults ~ Samples Gallery Р Updotes (2) Web E.ssentin ls 2015.3 Adds many us~ ul features to \/i<J ual Studio for we b develop•" · Requires Visu•I Studio 2015 Web Exton.slon Pack The ea~1est way to set up Visual Studto for tl't ultimtte we:b dNelopment experienc e. Web An;ilyzer Provid•s static analysis dir~ctly in Visual St udio for JavdScript, Type:Sc ript, JSX. CSS .Jnd mo re ..t;J lmng" Sprites 1 ~" ~1 ,,. Bundler & Minifier х• Create d Ьу: Mods Kristensen ~ Versio n: 2. 1.255 ~ Oo>Vnload s: 144S40 Rating: ., (42 Votes) ' More lnformat1on 1 Repo rt Exte n:-.ion to Mкrosoft ~,,, 1 ~ ~у Close ' ' ' ..:...~....._._.".:....._:....:;...~".-;....~~--·....;,._.-··-·· -·~·~·---,.,.:_ ... _;. .;. , '__ . .. . .. .. . .. .. . . . -: .. _'~..... "-..;.·-~.:;·-·:.;_,-..:.~:..~-~-·~-----' Рис. 6 .26 . Поиск расширения Visual Studio пакетирование и минификация файлов После установки расширения и перезапуска Visual Studio вы можете выбирать множество файлов одного типа, пакетировать их вместе и минифицировать их содер жимое. В качестве примера выберите файлы first. css и second. css в окне Solution Explorer, щелкните на них правой кнопкой мыши и выберите в контекстном меню пункт Bundler & MinifierqBundle and Minify Files (Упаковщик и минификаторqПакетировать и минифицировать файлы). как показано на рис . 6.27.
Solution Explorer • r:3Х О'IФ ~ ! Ф·~@!@ с+ Open . . Se6rch Solut• on Ex~or (Ctrl• :.! ._ Opo n With". Ф ~JWroot Hide from So lution Exp lorer . @] fors\.css. ·,_.,. ," ~ second.;;s " -.ijs rr fourth.js rr th iгd .js lib {} Cle•nup Selocted Code Coll.apse Recurs1vely - . - - ~ Rt1n Web Code: Analysi.s -· . --..~- . Bundler & Minifitr .. .. NEW Solution Explore.r V1e.;,v J(, Cut ~--------< Q1 Сору Х D•lete Rfnan1t 1' Properties Ctrl+X Ct rl+C Del Aft•Enter Глава 6. Работа с Visual Studio 167 Рис. 6.27. Пакетирование и минификация файлов CSS Сохраните выходной файл как bun dle. c ss и расширение обработает файлы CSS. В окне Solution Explorer отобразится новый элемент b undl e . cs s, который можно рас крыть и увидеть минифицированный файл по имени bund l e . min. css . Открыв ми нифицированный файл, вы заметите, что содержимое обоих отдельных файлов CSS было объединено, а все пробельные символы удалены. Работать с этим файлом на прямую вы вряд ли захотите, однако он меньше по размеру и требует только одного НТТР-подключенил для доставни стилей CSS клиенту. Повторите процесс с файлами third. j s и fo u rth . js , чтобы создать новые фай лыbundle.jsиbundle.min .jsвпапке wwwroot/js. Внимание! Удостоверьтесь, что выбираете файлы в порядке, в котором они загружаются браузером , для того чтобы предохранить порядок следования стилей или операторов кода в выходном файле. Таким образом, например, выбирайте файл thi rd. j s перед файлом four th. j s , чтобы обеспечить выполнение кода в правильном порядке. В листинге 6.24 показано представление I ndex . csht ml, в котором элементы link для отдельных файлов заменены одним таким элементом , запрашивающим пакетиро ванные и минифицированные файлы. Листинг 6.24 . Применение пакетированных и минифицированных файлов в файле Index. cshtml @model IEnumeraЫe<WorkingWithVisualStudio .Models. Product> @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name= "viewport" cont ent="wi dt h=devi ce- width" /> <title>Working with Visual Stud io</ti tl e> <link rel="stylesheet" href="css/bundle.min.css" /> <script src="js/bundle .min. js"></script> </head> <body> <hЗ>P r oducts</hЗ> <p>Request Time: @DateTime .Now.ToString("HH:mm:ss")</p>
168 Часть 1. Введение в инфраструктуру ASP.NET Core MVC <tаЫе> <thead> <tr><td>Name</td ><td >P ri ce</td></ tr> </thead> <tbody> @foreach (var р in Model) <tr> <td>@ p.Name</td> <td>@($ "{p. Price: C2 }" )</ td> </tr> </tbody> </tаЫе> </body> </html> После запуска приложения вы не заметите никаких визуальных отличий, но оно использует пакетированные и минифицированные файлы, пр едоставляя браузеру все стили и код, которые были определены в отдельных файлах. По мере выполнения оп ераций пакетирования и минификации расширени е в едет учет обработанных файлов в файле по имени b un d l econfig . j son, находяще мся в корневой папке проекта. Вот конфигурация. которая была сгенерирована для файлов в примере приложения: " outputFileName": " wwwroo t/c s s /bundle. c ss", "inp utFile s": [ " wwwroot/ c ss/first . css ", " wwwroo t /css/second . cs s" ] }1 { " outputFileName ": " wwwroot/ j s/bundl e .j s ", " inputFiles ": [ " wwwroo t / j s/third .js", "wwwroot/js/fourth.js" Расширение автоматически отслеживает входные файлы на предмет изменений и за ново генерирует выходные файлы, когда происходят изменения. гарантируя отражение в пакетированных и минифицированных файлах результатов любого редактирования. Для демонстрации в листинге 6.25 приведено изменение. внесенное в файл thi rd . j s. Листинг 6.25. Внесение изменения в файл third. j s docume nt . addEvent List ener ("DOMContentLoaded", f unct ion () { var el ement = document . createElement ("р") ; element.textContent = "This is the element from the (modified) third . js file"; document . querySelector( " body " ) . append Ch ild(e l ement) ; });
Глава 6. Работа с Visual .Studio 169 После того как файл сохранен, расширение заново сгенерирует файл bundle . min. j s. Перезагрузив страницу в браузере , вы увидите изменение (рис . 6.28). Name Price Lifejacket $48 .95 Socce r ball $ 19.50 Corne r flag $34.95 Рис. 6.28 . Обнаружение изменений в пакетированных и минифицированных файлах Резюме В этой глав е была рассмотрена структура проектов МVС, описаны две доступные исполняющие среды .NET и обсуждены средства, которые Visual Studio предлагает для разработки веб-приложений, включая автоматическую компиляцию классов, средство Browser Liпk, а также пакетирование и минификацию. В следующей главе объясняет ся. как проекты ASP.NET Core МVС подвергаются модульному тестированию .
ГЛАВА 7 Модульное тестирование приложений МVС в настоящей главе будет показано, как проводить модульное тестирование при ложений MVC. Модульное тестирование - это вид тестирования, при котором индивидуальные компоненты изолируются от остальной части приложения, позволяя тщательно проверить их поведение . Инфраструктура ASP.NET Core МVС была спроек тирована так, чтобы сделать легкой задачу создания модульных тестов, и среда Visua] Studio предлагает поддержку для широкого диапазона инфраструктур модульного тестирования. В главе объясняется , как настроить проект модульного тестирования, каким образом установить одну из самых популярных инфраструктур тестирования и что собой представляет процесс написания и прогона тестов. В табл. 7.1 приведена сводка по главе. Таблица 7.1. Сводка по главе Задача Создание модульного теста Изоляция компонентов для модульного тестирования Прогон тех же самых тестов xUnit.net с разными значениями данных Упрощение процесса создания фиктивных тестовых объектов Решение Листинг Создайте проект модульного тестирования, 7.1-7.8 установите тестовый пакет и добавьте клас- сы, которые содержат тесты Используйте интерфейсы для разделения 7.9 -7 .16 компонентов приложения и применяйте фиктивные реализации с ограниченными тестовыми данными в модульных тестах Используйте параметризованный модуль ный тест либо получайте тестовые данные из метода или свойства Используйте инфраструктуру имитации 7.17-7 .19 7.20-7 .22
Глава 7. Модульное тестирование приложений MVC 171 Проводить ли модульное тестирование? Наличие возможности легко выполнять модульное тестирование является одним из преиму ществ использования инфраструктуры ASP.NET Core MVC, но эта процедура не для всех, и я не намерен притворяться, что дело обстоит иначе. Мне нравится модульное тестирование, и я применяю его в своих проектах, но далеко не во всех и не настолько согласованно, как вы могли ожидать. Я предпочитаю концентрироваться на написании модульных тестов для средств и функций, о которых знаю , что они будут труд ны в реализации и, скорее всего, станут источником ошибок после развертывания. В таких ситуациях модульное тестирование помогает структурировать мои мысли о том , как лучше всего реализовать то, что необходимо. Я считаю, что одно лишь размышление о том, что нужно тестировать, способствует появлению идей относительно потенциальных проблем, причем до того, как придется иметь дело с действительными ошибками и дефектами. Тем не менее, модульное тестирование - это инструмент, а не догма, и только лично вы знаете, в каком объеме должно проводиться тестирование. Если вы не находите модульное тестирование полезным или располагаете другой методологией, которая вам больше под ходит, то не должны думать, что вы обязаны использовать модульное тестирование просто потому, что это модно. (Однако если вы не располагаете более эффективной методологией и вообще не выполняете тестирование, то вероятно даете возможность пользователям об наруживать имеющиеся ошибки, что редко оказывается идеальным решением . Вы не обяза ны применять модульное тестирование, но действительно должны проводить тестирование какого-то вида.) Если вы не сталкивались с модульным тестированием ранее, я предлагаю опробовать его и посмотреть, как оно работает. Если вы не являетесь поклонником модульного тестирования, то можете пропустить данную главу и перейти к чтению главы 8, где будет начато построе ние более реалистичного приложения MVC . П одготовка проекта для примера В этой главе мы продолжим пользоваться проектом Wor k i ngW i thVisu al S t udi o , который был создан в главе 6. Здесь мы добавим в него поддержку для создания но вых объ е ктов P r oduc t в хранилище. Включение встроенных дескрипторных вспомогательных классов В данной главе применяется один из встроенных дескрипторных вспомога тельных классов для установки атрибута href элемента а. Работа дескрипторных вспомогательных классов подробно объясняется в главах 23-25, а здесь мы просто должны включить их. Создайте файл импортирования представлений, щелкнув пра вой кнопкой мыши на папке Vi ews, выбрав в контекстном меню пункт Addi::>New ltem (Добавить~::> Новый элемент) и указав шаблон элемента MVC View lmports Page (Страница импортирования представлений МVС) из категории ASP.NET . Среда Visual Studio автоматически назначит файлу имя _ Vi e wi mp ort s. cs html, а щелчок на кнопке Add (Добавить) приведет к его созданию. Поместите в файл содержимое, по казанное в листинге 7. 1 . Листинг 7.1 . Содержимое файла_Viewimports. cshtml из папки Views @addTagHelper *, Mi crosoft.AspNetCore .Mvc .TagHelper s
172 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Этот оператор включает встроенные дескрипторные вспомогательные классы. в том числе тот, который вскоре будет использоваться в представлении Index . Можно было бы добавить операторы using для импортирования пространств имен из проек тов, но представления не являются важной частью рассматриваемого в данной главе примера приложения, так что ссылка на типы моделей с их пространствами имен не вызовет проблем. Добавление действий к контроллеру Первый шаг связан с добавлением действий к контроллеру Home, который будет визуализировать представление для ввода данных и для получения этих данных от браузера (ли стинг 7.2). Действия следуют тому же шаблону, который применялся в главе 2 и подробно обсуждается в главе l 7. Листинг 7.2. Добавление методов действий в файле HomeController. cs using Microsoft . AspNetCore . Mvc; using WorkingWithVisualStudio . Mode ls; using System.Linq; namespace WorkingWithVisua lStudio.Controllers puЫic class HomeController : Controller { SimpleRepository Repository = SimpleRepository.SharedRepository; puЬlic IActionResult Index () => View (Repository. Products .Where (p => p?.Price < 50)); [HttpGet] puЬlic IActionResult AddProduct () => View (new Product () ) ; [HttpPost] puЫic IActionResult AddProduct(Product р) Repository.AddProduct(p); return RedirectToAction("Index"); Создание формы для ввода данных Чтобы снабдить пользователя возможностью создания нового товара, мы создадим представление Razor по имени AddProduct . cshtml в папке Views/Home . Соглашения относительно имени файла и его местоположения соответствуют стандартному пред ставлению, которое визуализирует метод AddProduct () контроллера Home . В лис тинге 7 .3 приведено содержимое нового представления, которое полагается на пакет Boostrap, добавленный к проекту с использованием Bower в главе 6. Листинг 7.3. Содержимое файла AddProduct. cshtml из папки Views/Home @model WorkingWithVisualStudio.Models.Product @{ Layout = null; } < !D OCTYPE html> <html> <head>
Глава 7. Модульное тестирование приложений MVC 173 <meta name= " viewport " content = "width=devi ce - wi dth " /> <title>Working with Visual Studio</title> <link rel= " stylesheet " href= " /liЬ/bootstrap/dist/css/bootstrap . min . css " /> </head> <body class= " panel - body " > <hЗ>Create Product</hЗ> <form asp- action= "A ddProduct " method= "post " > <div c l ass= " form-group " > <label asp - for= " Name">Name : </label> <input asp - for= "N ame " class= " form -control " /> </div> <div class= " form - group " > <label asp -for= " Price " >Price:</label> <input asp - for= " Price " class= " form - control " /> </div> <button type="submit" class =" Ьtn Ьtn -pr imary " > Add </button> <а asp-action= " Index " class= " Ьtn Ьtn -de fault " >Cancel</a> </form> </body> </html> Представление содержит НТМL-форму, которая применяет НТТР-запрос POST для отправки значений Name и Price действию AddProduct контроллера Ноте . Содержимое стилизовано с использованием пакета CSS из Bootstrap. Обновление представления Index Последний подготовительный шаг предусматривает обновление представления Index , чтобы включить в него ссылку на новую форму (листинг 7 .4). Кроме того, поя вилась возможность удалить файлы JavaScript, используемые в предыдущей главе, и заменить специальные таблицы стилей CSS стилями Bootstrap, которые применяют ся к элементам HTML в представлении. Листинг 7.4 . Обновление содержимоrо в файле Index. cshtml @model IEnumeraЫe<WorkingWithVisualStudio . Models.Product> @{ Layout = null; } < ! DOCTYPE html> <html> <head> <meta name:o " viewport " content= " width=device - width " /> <title>Worki n g with Visua l Studio</title> <link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.min. css" /> </head> <body class="panel-body"> <hЗ class="text-center">Products</hЗ> <tаЫе class="taЫe taЬle-bordered taЫe-striped"> <thead> <tr><td>Name</td><td>Price</td></tr> </thead> <tbody> @foreach (var р in Model) {
174 Часть 1. Введение в инфраструктуру ASP.NET Core MVC <t r> < td>@p .N ame</td> <td >@($ " {p .Price :C2} " )</td> </t r> } </tbody> </tаЫе> <div class="text-center"> <а class="Ьtn Ьtn-primary" asp-action="AddProduct"> Add New Product </а> </div> </body> </ html> Запустив пример приложения, вы увидите по-новому стилизованное содержимое и кнопку Add New Product (Добавить новый товар) , щелчок на которой приводит к открытию формы для ввода данных. Отправка формы добавит в хранилище новый объект Produ c t и п е р енаправит браузер для отображения первоначального представ ления приложения (рис. 7 .1). Рис. 7 .1. Выполнение примера приложения Совет. Не забывайте, что в этом примере объекты хранятся только в памяти, т.е . любые со здаваемые товары при перезапуске приложения будут утеряны. Модульное тестирова н ие приложений MVC Модульные тесты используются для проверки поведения отдельных компонентов и функциональных средств в приложении, а инфраструктуры ASP.NET Core и ASP.NET Core MVC спроектированы так, чтобы максимально облегчить настройку и запуск модульных тестов для веб-приложений. В последующих разделах объясняется, как настроить модульное тестирование в Visual Studio, и демонстрируется написани е модульных тестов для приложений МVС. Кроме того, будут представлены пол езные инструменты , которые делают модульное тестирование проще и надежн ее .
Глава 7. Модульное тестирование приложений MVC 175 Доступен целый ряд разных пакетов для модульного тестирования. В книге при меня ется один из них, который называется xUnit.net; он выбран из-за его хорошей интеграции с Visual Studio, к тому же этот пакет использовался командой разработ чиков Microsoft при написании модульных тестов для ASP.NET. В табл. 7.2 приведена сводка, позволяющая поместить xUnit.net в контекст. Таблица 7.2. Помещение xUnit.net в контекст Вопрос Что это такое? Чем она полезна? Как она используется? Существуют ли какие-то скры тые ловушки или ограничения? Существуют ли альтернативы? Изменилась ли она по сравнению с версией MVC 5? Ответ xUnit.net - это инфраструктура модульного тестирования, которая мо жет применяться для тестирования приложений ASP.NET Core MVC xUпit.пet является хорошо написанной инфраструктурой модульного тестирования, которая легко интегрируется со средой Visual Studio Тесты определяются как методы, аннотированные с помощью атрибута Fa c t или Theo r y. Внутри тела метода используются методы класса Assert для сравнения ожидаемого результата теста с тем, что получи лось в действительности Главный просчет при модульном тестировании связан с недостаточной изоляцией тестируемого компонента . За дополнительными сведениями обращайтесь в раздел "Изолирование компонентов для модульного тестирования" далее в главе. Самой крупной проблемой, специфичной для xUпit.пet, является нехватка документации. Кое-какая базовая ин формация доступна на веб-сайте http: //xunit . gi thuЬ . i o , но рас ширенное применение требует метода проб и ошибок Доступно множество инфраструктур модульного тестирования. Двумя популярными альтернативами являются MSTest (производства Microsoft) и NUпit Инфраструктура ASP.NET Core MVC делает легким проведение модуль ного тестирования , но вовсе не требует и не навязывает его использо вание, а также не регламентирует применение конкретных инструмен тов тестирования . Вы вольны использовать любые инструменты или не выполнять тестирование вообще На заметку! В области модульного тестирования практически все является предметом пер сональных предпочтений и вопросом шумных разногласий . Некоторым разработчикам не нравится отделять модульные тесты от кода приложения, и они предпочитают определять тесты в том же самом проекте или даже в том же самом файле класса. Я описываю здесь распространенный подход , которому следую сам, но если он не кажется вам правильным, то вы должны поэкспериментировать с различными стилями тестирования, пока не под берете для себя подходящий. Создание проекта модульного тестирования Для приложения ASP.NEТ Core обычно создается отдельный проект Visual Studio, со держащий модульные тесты, каждый из которых определя ется как метод в классе С# . Применение отдельного проекта означает, что приложение можно развернуть, не раз вертывая одновременно тесты.
176 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC Соглашение предусматривает назначение проекту модульного тестирования име ни <ИмяПриложения> . Tests и создание его в папке под названием test, находящей ся на том же уровне, что и папка src. Для приложения WorkingWithVisualStudio именем проекта модульного тестирования будет WorkingWithVisualStudio . Tests . На рис. 7.2 показана обычная структура проекта ASP.NET Core в Visual Studio, кото рый содержит модульные тесты. Решение _____. f----+ Проект WorkingWithVisualStudio Папка src WorkingWithVisualStudio - Папка test ,_______.... Проект - WorkingWithVisualStudio . Tests Рис. 7.2. Обычная структура проектов при модульном тестировании Создание такой структуры требует небольшой работы из-за особенностей распре деления средой Visual Studio содержимого решения по файлам на диске. Первый шаг заключается в использовании проводника файлов или окна командной строки для создания папки test внутри папки решения WorkingWithVisualStudio, чтобы она оказалась на одном уровне с существующей папкой src. Совет. В Visual Studio имеется шаблон проекта Unit Test (Модульный тест), однако он не настроен для применения с .NET Саге и не поддерживает средства, подобные файлу proj ect . j son. Щелкните правой кнопкой мыши на элементе решения WorkingWi thVisua lStudio в окне Solutioп Explorer среды Visual Studio (элемент верхнего уровня, охватываю щий все остальное), выберите в контекстном меню пункт Addc::>New Solution Folder (Добавить~:::> Новая папка решения) и установите имя новой папки в test . (Вы не смо жете добавить папку решения при выполняющемся отладчике; выберите пункт Stop Debugging (Остановить отладку) в меню Debug (Отладка) и попробуйте заново.) Щешшите правой кнопкой мыши на элементе папки test в окне Solution Explorer и выберите в контекстном меню пункт Addc::>New Project (Добавитьс::>Новый проект). Выберите шаблон Class Library (.NET Core) (Библиотека классов (.NET Core)) из катего рии lnstalledc::>Visual C#c::>.NET Core [Установленныес::>Visuаl C#c::>.NET Core), установите имя проекта в WorkingWi thVisualStudio. Tests, а в поле Location (Местоположени е) введите путь к папке test (рис. 7.3). Щелкните на кнопке ОК, чтобы создать проект. В результате структура проектов, отображаемая в окне Solution Explorer, будет соответствовать структуре папок проек тов в файловой системе. Конфигурирование проекта модульного тестирования Чтобы подготовить проект модульного тестирования, откройте файл proj ect . j son из проекта WorkingWi thVisualStudio . Tests и измените его содержимое так , как показано в листинге 7.5.
Глава 7. Модульное тестирование прило ж ений MVC 177 i t; 1NindO\ЧS 1Sii \.\/еЬ Co n1ole App lication (.NЕТ Core) ·-·· ••• Г.- - ' " . ." •i;;·l.:= LSearc~1~ Туре: Vis~ дprojectt/ Visual (# Core clas s;- 1 ~ЕТС?.~ 1@} 1 ASP.NEТ Core Web App lication (.NЕТ Co re) Visual (:< 1 Android 1 Cloud E:<tensibllity 1.1; 1 iOS 1 1 Repor1ing 1 1 Silv~rlight 1 Test i WCF 1 1 ~ Online ! м,mе 1 f. Lo<:atio,ru Рис. 7.3. Создание проекта для тестов Внимание! Будьте внимательны, чтобы внести изменения в файл p r o j ect . j son внутри прое кта модульного тестирования, а не проекта главного приложения. Листин г 7.5. Содержимое файла project. json из проекта WorkingWithVisualStudio.Tests "version": "1 .0 .0 - *", "testRunner": "xunit", " dependenc i es ": { "Microsoft.NETCore.App": "type ": "platform", "version": "1.0.0" }' "xunit": "2 .1 .О" , "dotnet-test-xunit": "2.2.0 -preview2-build1029" ), "frameworks": { "ne tcoreappl.0": "imports ": [ "dotnet5. 6", "portaЫ e -net 4 5+win8"] Эта конфигурация сообщает Visual Studio о том, что требуются три пакета. Пакет Microsoft . NETCore . Арр пр е доставля ет АРl-инт е рфейс .N E T Core. Пакет xuni t содержит инфраструктуру тестирования , а пакет dotnettest - x un i t обеспечива -
178 Часть 1. Введение в инфраструктуру ASP.NET Core MVC ет интеграцию между xUnit .net и Visual Studio. На момент написания книги пакет dot n et - test - xuni t , подд е рживаемый для приложен ий .NET Core , был доступен в виде предварительно й верси и, и при чтении этой главы вы можете обнаружить более ПОЗДНЮЮ версию . Когда вы сохраните изменения , внесенные в файл proj ect . j son , среда Visual Studio загрузит и устан овит NuGеt-пакеты xUnit.net вм есте с их зави с имостями . Вс е это мож ет потребовать определенного времени, поскольку зависимостей очень много . Процесс создания проектов модульного тестирования для приложений ASP.NET Core МVС в будущих выпусках Visual Studio, скорее всего, будет упроще н , по этому описан ные здесь дополнительные шаги больше не понадобятся. Добавление ссылки на проект приложения Для того чтобы появилась возможность тестирования кла ссов в приложении, не обходимо добавить ссылку на проект приложения в файл proj ect . j son про екта мо дульного тестирования (листинг 7.6). Листинг 7.6 . Добавление ссылки на проект приложения в файле project. j son проекта модульного тестирования "version": "1.0 .0-*", " t estRunner ": "xunit", "dependenci es ": { }' " Microsoft . NETCore . App ": " type ": " platform ", "version": "1 .0 .0" }' "xunit": "2.1.О", "dotnet-test-xunit ": "2 .2 .0-preview2-buildl029", "WorkingWithVisualStudio": "1 . 0.0" " framewo r ks": { " netcoreappl . 0 ": " imports ": [ "dotnet5 . 6 ", " portaЬle - net45+win8 " ] Написание и выполнение модульных тестов Теперь, когда все подготовительны е шаги завершены, мо жно приступать к написа нию ряда тестов. Первым делом добавьте в проект Wo r kingWi thVisualStudio . Tests файл класса по имени ProductTes ts . c s с определением, предст авленны м в листин ге 7.7. Это простой класс , но он содерж ит все, что требуется для начала модульного те стирования. На заметку! В методе Ca n ChangeP r oductPrice () умышленно допущена ошибка, кото рая будет устранена позже в данном разделе .
Глава 7. Модульное тестирование приложений MVC 179 Листинг 7.7 . Содержимое файла ProductTests. cs using WorkingWithVisualStudio.Models ; using Xunit ; namespace WorkingWithVisualStudio .Tests puЫic class ProductTests { [Fact] puЫic void CanChangeProductName() 11 Организация var р = new Product { Name = "Test", Price 11 Действие p.Name = " New Name "; 11 Утверждение Assert .Equal ("Ne w Name ", p .Name) ; [Fact] puЫic void CanChangeProductPrice() 11 Организация var р = new Product { Name = "Test", Price 11 Действие р.Price = 200М; 11 Утверждение Assert . Equal(lOOM , p.Price) ; lOOM }; lOOM }; В классе ProductTests присутствуют два модульных теста, каждый из ко торых проверяет определенное поведение класса модели Product из проекта WorkingWi thVisualStudio . Проект тестирования может содержать много классов, которые способны включать много модульных тестов . По соглашению имя тестового метода описывает то, что делает тест, а имя клас са - то, что подвергается тестированию . Это облегчает структурирование тестов в проекте и упрощает понимание того, какими будут результаты всех тестов, когда они выполняются средой Visual Studio. Имя ProductTests у1шзывает, что класс содер жит тесты для класса Product, а имена методов говорят о том , что они проверяют возможность изменения названия и цены объекта Product. Атрибут Fact применяется к каждому методу, указывая на то , что метод является: тестом. Внутри тела метода модульный тест следует шаблону , который называется орган.изация/действие/утвержден.ие (arrange/act/assert -А/А/А). Организация от носится: к настройке условий для: теста , действие - к выполнению теста, а утверж дение - к пров е рк е того, что результат оказался тем, который ожидали. Разделы организации и действия этих тестов представляют собой обычный код С#, но раздел утверждения обрабатывается: инфраструктурой xUnit.net, которая пре доставляет класс по имени Assert , чьи методы используются для проверки , является ли результат действия тем, что ожидался. Совет. Атрибут Fact и класс Asset определены в пространстве имен Xuni t , для которого дол жен быть предусмотрен оператор using в каждом тестовом классе.
180 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Методы класса Assert определены как статичес1ше и применяются для выполн е ния разных видов сравнений меж,ду ожидаемыми и действительными р е зу л ьт атами . В табл. 7.3 описаны распространенные методы класса Assert . Таблица 7.3. Часто используемые методы класса Assert из инфраструктуры xUnit.net Имя Equal(expected , result) NotEqual(expected , result) True(result) False(result) IsType(expected, result) IsNotType(expected, resu l t) IsNull (result) IsNotNull(result) InRange(result, low , high) NotinRange(result, low ,high ) Throws(exception,expression) Описание Этот метод добавляет утверждение о том , что резуль тат равен ожидаемому исходу. Существуют перегру женные версии данного метода для сравнения различ ных типов и для сравнения коллекций. Имеется также версия, которая принимает дополнительный аргумент в форме объекта, который реализует интерфейс IEqua lityCornparer<T> для сравнения объектов Этот метод добавляет утверждение о том, что резуль тат не равен ожидаемому исх оду Этот метод добавляет утверждение о том, что резуль тат равен true Этот метод добавляет утверждение о том, что резуль тат равен false Этот метод добавляет утверждение о том, что резуль тат принадлежит указанному типу Этот метод добавляет утверждение о том, что резуль тат не принадле жит указанному типу Этот метод добавляет утверждение о том , что резуль тат равен null Этот метод добавляет утвер ждение о том , что резуль тат не равен null Этот метод добавляет утверждение о том , что резуль тат находится между l ow и high Этот метод добавляет утверждение о том, что резуль тат не находится между l ow и high Этот метод добавляет утверждение о том, что указан ное выражение генерирует исключение заданного типа Каждый метод класса Assert позволяет выполнять разные типы сравнения и генерирует исключение, если результат оказыва ется не тем, 1юторый ожидался. Исключение применяется для указания на то, что тест не прош ел. В тестах из лис тинга 7.7 метод Equal () используется для определения, корректно ли изменилось значение свойства: Assert .Equal ( " New Name " , p.Name) ; Прогон тестов с помощью окна Test Explorer Среда VisuaJ Studio предлагает поддержку для поиска и прогона мо,дульных тестов посредством окна Test Explorer (Проводник тестов), которое доступно через пункт меню TestqWiпdowsqTest Explorer (ТестqОкнаqПроводник тестов) и показано на рис. 7.4 .
Глава 7. Модульное тестирование прило ж ений MVC 181 Test Explorer Searcn Test Explorer П: ... а· Sea.:_:h Ruo А!! 1 Run". • 1 Pi•y11>t: All Test.< • ... Fai led Tests (1) Q T!!sts.Produc tTtsts .CгnCh.!11gePtoductPrict ... Passed Tests (1) (J Tests.ProductТest.<.CanC l1ongoProductNome Sun1mary Last Te:st Ru n Fail ed (Тotel Run Timl! О 00:04) О 1 Ttst Foiled О 1 Test Po"<d Рис. 7.4. Окно Test Explorer в Visual Studio • !:!х Р· l lms 3ms Совет. Если вы не видите модульные тесты в окне Test Explorer, тогда постройте решение. Компиляция запускает процесс, с помощью которого обнаруживаются модульные тесты. Выполните тесты, щелкнув на пункте Run All (Выполнить все) в окне Test Explorer. Ср еда Visual Studio прим енит xUnit.net для прогона тестов в проекте и отобразит ре зул ьтаты . Как отм ечалось, тест CanChangeProductPr i ce () содержит ошибку. кото рая приводит к тому , что тест не проходит. Проблема связан а с аргументом метода Assert . Equa l () , из-за чего прои сходит сравнение с исходным значением свойс тва Price, а не со значением , на которое оно изменилось. В листинге 7.8 проблема устранена. Совет. Когда тест не проходит, всегда полезно проверить правильность самого теста, прежде чем просматривать компонент, на который он нацелен, особенно если тест только что на писан или недавно модифицировался. Листинг 7.8 . Исправление теста в файле ProductTests. cs using WorkingWithVis u alStud i o . Mode l s ; using Xunit ; namespace Work i ngWithVisualStudio .Tests puЬlic class Produc t Tests { [Fact] puЫic void CanChangeProductName() 11 Организация var р = new Product {Name = "Test", Price 11 Действие p.Name = "New Name"; 11 Утверждение Assert . Equal( " New Name ", p . Name) ; lOOM };
182 Часть 1. Введение в инфраструк туру ASP.NET Соге MVC [Fac t] puЫic void CanChangeProductPrice () 11 Организация var р = new Product { Name = "Test", Price lOOM }; 11 Действие p .Price = 200 М; 11 Утверждение Assert.Equal(200M, p.Price); При наличии большого количества тестов выполнение их всех может занять неко торое время. Таким образом, чтобы можно было работать быстро и итеративно, в окне Test Explorer предлагаются различные варианты для выбора подмножества тестов, подлежащих прогону. Самым полезным подмножеством является набор тестов , кото рые не прошли (рис. 7.5). Запустите исправленный тест снова, и в окн е Test Explorer будет показано , что не прошедшие тесты отсутствуют. Test Explo re r ' _, . ~.- . (f: '"'1 ~Jt!-~~~~ Run All 1 Ruo". ? 1 Playl«t: All Te.t' ? г--~·---~~·----- . J. Failed Т( Run FAite d Tt<Sts ~ О Testsj Run Not Run Теsн " Р•s.М \ Run P•ssed Test• & Te~ts{ Repti.11L.ist Run Ctrl...R. l Su mm ary l a5t Test Run Fai led otJ I Run iin~e 0:00:04) f.З; 1 Test Fi!ifed $ 1 TostPaшd 11"Р Tosts (2) (j Tests.ProductTe-s1s.(.anChan9ePr~ductPrice: St1mma ry l ast T~t Ru n Passe:d (Tot.al Run Time 0:00:02) О 1 TestPaщd Рис . 7.5. Избирательный прогон тестов И золирование компонентов для модульного тестирова н и я Sms Написание модульных тестов для классов моделей вроде Prod uct особой сложнос тью не отличается. Класс Produ c t не только прост, он также самодостаточен , т.е . при вьmолнении какого-то действия над объектом P rodu c t можно иметь ув е ренность в том, что тестируется функциональность, предоставляемая классом Product . С другими компонентами в приложении МVС ситуация сложнее , потому что между ними есть зависимости. Следующий набор опр еделяемых тестов будет оп ерировать на контроллере, исследуя последовательность объектов P r odu c t, которая пер едается между контроллером и представлением .
Глава 7. Модульное тестирование прило жений MVC 183 При сравнении объектов, являющихся экземплярами специальных классов, пона добится использовать метод Assert. Equal () из xUnit.net, который принимает ар гумент, реализующий интерфейс IEquali tyComparer<T>, так что объекты можно сравнивать. Сначала необходимо добавить в проект модульного тестирования файл класса по имени Comparer . cs и поместить в него определения вспомогательных классов, приведенные в листинге 7.9. Листинг 7.9 . Содержимое файла Comparer. cs из проекта WorkingWithVisualStudio.Tests using System ; using System . Co llections.Generic ; namespace WorkingWithVisualStudio . Tests puЫic class Comparer { puЫic static Compa rer<U > Get<U>(Func<U, U , bool> func) { return new Comparer<U>(func) ; puЬlic class Comparer<T> : Comparer, IEqualityComparer<T> private Func<T , Т , bool> comparisonFunction; puЫic Comparer(Func<T , Т , bool> func) { comparisonFunction = func; puЫic bool Equa l s(T х , Ту) return comparisonFunction(x, у); puЫic int GetHashCode(T obj) return obj . GetHashCode() ; Показанные классы позволят создавать объекты IEquali tyComparer<T> с при менением лямбда - выражений вместо определения нового класса для каждого типа сравнения, которое нужно предпринимать . Это не является жизненно необходимым, но упростит код в классах модульных тестов и сделает его легче для понимания и сопровождения. Теперь, когда можно легко делать сравнения, давайте рассмотрим проблему зави симостей между компонентами в приложении. Добавим в проект WorkingWithVisualStudio. Tests новый файл класса по имени HomeControllerTests . cs и поместим в него определение модульного теста, пред ставленное в листинге 7.10. Листинг 7.10. Содержимое файла HomeControllerTests. cs из проекта WorkingWithVisualStudio.Tests using Microsoft . AspNetCore . Mvc; using System.Collections . Generic; using WorkingWithVisualStudio.Controllers; using WorkingWithVisualStudio . Models ; using Xunit;
184 Часть 1. Введение в инфраструктуру ASP.NET Core MVC narnespace WorkingWithVis ua lStudio.Tests puЫic class HomeControllerTests { [Fact] puЫic void IndexActionModelisCornplete() 11 Организация var control ler = new HorneController(); 11 Действие var rnode l = (controller.Index() as ViewResult)? . ViewData.Model as IE nurneraЫe <Pro duct > ; 11 Утверждение Assert . Equa l (S irn pleRepository . SharedRepos itory . Products , rnodel , Cornparer . Get<Prod uct>( (pl, р2) => pl.Narne == p2 .N arne && pl. Price == р2. Price)) ; Модульный тест в листинге 7.10 проверяет, что метод действия Index () переда ет представлению все объекты в хранилище. (Пока что не обращайте внимания на раздел действия: класс ViewResul t и роль, которую он играет в приложениях MVC, будут объясняться в главе 17. В настоящий момент достаточно знать. что здесь полу чаются данные модели, возвращаемые методом действия Index () .) Запустив тест, вы увидите, что он не проходит, указывая на отличие между набо ром объектов в хранилище и набором объектов, 1шторые возвратил метод Index () . Но когда дело доходит до выявления причины, из-за чего тест не прошел, возникает проблема: предполагается, что тест действует на контроллере Home , однако класс кон троллера зависит от класса SimpleRepository. Это затрудняет выяснение. отра зил ли тест проблему с 1шассом, на 1\Оторый он нацелен, или же проблему в другой части приложения. Пример приложения достаточно прост, чтобы можно было легко выявить пробле му, всего лишь взглянув на код классов HomeCont r o lle r и Simp leRepository. В ре альном приложении визуальный осмотр не настолько прост, т.к. цепочка зависимос тей способна затруднить понимание того, что привело к отказу в прохождении теста. Обычно хранилище будет полагаться на какую-то разновидность системы постоян ного хранения. подобную базе данных, а также библиотеку, которая предоставляет к ней доступ. Модульный тест может взаимодействовать со всей цепочкой сложных компонентов, любой из которых может вызвать проблему. Модульные тесты эффективны, когда они нацелены на небольшие части приложе ния, такие как отдельный метод или 1шасс. Нам необходима возможность изоляции контроллера Home от остальной части приложения, чтобы можно было ограничить область действия теста и исключить влияние со стороны хранилища . Изолирование компонента Ключом к изолированию компонентов является использование интерфейсов С# . Чтобы отделить контроллер от хранилища, добавьте в папку Mode l s новый файл класса по имени IReposi tory . cs с определением интерфейса, показанным в лис тинге 7.1 1.
Глава 7. Модульное тестирование приложений MVC 185 Листинг 7.11 . Содержимое файла IReposi tory. cs из папки Models us i ng System . Collect ions . Generi c ; namespace WorkingWithVisualStudio.Models puЬlic i nterface IRe pository { IEnurneraЫe<Product> Products { get ; } void AddProduct(Product р); С этим интерфейсом не связано ничего примечательного (за исключением того. что в нем не определен полный набор операций, который обычно будет нужен в веб-при ложении; более реалистичный и завершенный пример можно найти в главе 8). Тем не менее, добавление интерфейса вроде IReposi t ory позволяет легко изолировать ком понент для тестирования. Первым делом нужно обновить класс SimpleReposi tory, чтобы он реали зовывал новый интерфейс (листинг 7.12) . Листинг 7.12 . Реализация интерфейса в файле SimpleReposi tory. cs using Sys tern.Co lle ctions . Gen e ric ; narnespace WorkingWithVisualStudio . Model s puЫic class SimpleRepository : IRepository private static SirnpleRepository sharedRepository new SirnpleRepository(); privat e Dictionary<s tring , Product> products = new Dictionary<stri ng , Product>() ; puЬlic static SirnpleRepository SharedRepository = > sharedRepository ; puЫ i c SirnpleRepository() { var in itialiterns = new(J { }; new Prod uct { Name " Kayak ", Price = 275М }, new Product { Narne " Li fejacket ", Price 48 . 95М }, new Product { Narne " Soccer ball", Price 19.SOM }, new Product { Narne "C orner flag", Price 34 . 95М } foreach (var р i n i nitiali tern s) { AddProduct(p) ; products . Add( " Error ", null); puЫic IEnumeraЫe<Product> Products => products.Values; puЫic void Add Pr oduct(Product р) => pr oducts . Add(p.Name, р); Следующий шаг предусматривает модификацию контроллера, чтобы свойство, применяемое для ссылки на хранилище, использовало интерфейс, а не класс (лис тинг 7.13).
186 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Совет. Инфраструктура ASP.NET Core MVC поддерживает более элегантный подход к реше нию этой проблемы, который называется внедрением зависимостей и описан в главе 18. Внедрение зависимостей зачастую вызывает путаницу, поэтому в настоящей главе ком поненты изолируются более простым и ручным способом. Листинг 7.13. Добавление свойства Reposi tory в файле HomeController. cs using Microsoft . AspNetCore . Mvc ; using WorkingWithVisualStudio . Mode l s; using System .L inq ; namespace WorkingWithVisualStudio . Controllers puЬlic class HomeController : Controller ( puЫic IRepository Repository = SirnpleRepository.SharedRepository; puЫic IActionResult Index() = > View(Repository . Products . Where(р => р?.Price <50)); [HttpGet] puЫic IActionResult AddProduct () => View () ; [HttpPost] puЬlic IActionResult AddProduct(Product р) ( Reposito r y . AddProduct(p) ; return RedirectToAction("Index"); Это может выглядеть незначител ьной модификацией, но позволяет изменять хра нилище , которое контроллер применяет во время тестирования, что демонстри рует способ изоляции контроллера. В листинге 7 . 14 модульные тесты контроллера обнов лены. так что они используют специальную версию хранилища. Листинг 7 .14 . Изоляция контроллера в модульном тесте внутри файла HomeControllerTests.cs using Microsoft . AspNetCore.Mvc ; using System . Co l lections . Generic ; using WorkingWithVisualStudio . Controllers ; using WorkingWithVisualStudio . Models; using Xunit ; namespace WorkingWithVisualStudio .Tests puЫic class HomeControllerTests ( class ModelCornpleteFakeRepository : IRepository puЫic IEnurneraЫe<Product> Products { get; } = new Product [] { new Product { Narne = "Pl" , Price = 27SM }, new Product { Narne = "Р2", Price 48. 95М } , new Product { Narne "РЗ", Price = 19 . SOM}, new Product { Narne = "Р3", Price 34 . 95М } };
Глава 7. Модульное тестирование приложений MVC 187 puЫic void AddProduct (Product р) { / / ничего не делать - для теста не требуется [Fact] puЫic void IndexAct i onMode l isComple te ( ) 1 1 Организация var controll er = new HomeControll er () ; controller.Repository = new ModelCompleteFakeRepository(); 11 Действие var model = (controller . Index () as ViewResult) ?.ViewData .Model as IE nu meraЫe<Pro du ct> ; 11 Утверждение Assert.Equal(controller.Repository.Products, model, Comparer. Get<Product> ((pl, р2) => pl.Name == р2 .Name && pl.Price == p2 . Pri ce)) ; Мы определили фиктивную реализацию интерфейса IRe p osit ory, в которой при сутствует только свойство, необходимое для теста, и применяются всегда согласован ные данные (чего может не быть при работе с реальной базой данных, особенно в случа е е е совместного использования с другими разработчиками, вносящими собс твенные изменения) . Исправл енный модульный тест по-прежнему не проходит, указывая на то, что при чиной проблемы явля ется метод действия Index ( ) в классе Hom e Co n tro ller , а не компоненты , от которых он зависит. Метод действия, вызываемый модульным тес том, в достаточной степени прост для того, чтобы проблема стала очевидной в ре зул ьтате его осмотра: puЫic IActionRes ult I ndex () => View(Reposi tory .Products. Where(p => p . Pri ce < 50)) ; Проблема связана с применением м етода Wh e r e () из LINQ , который исполь зуется для фильтрации объектов Product со значениями свойства Pri ce , равным 50 или больше . Здесь уже есть серьезное указание на причину проблемы, но передовой опыт предполагает создание теста, который подтвердит наличие проблемы, прежде чем вносить корректирующую правку (листинг 7.15) . Совет. В показанных тестах присутствует много дублированного кода. В следующем разделе объясняется, как упростить тесты. Листинг 7.15. Добавление теста в файле HomeControllerTests. cs usi ng Microsoft.AspN etCore .Mvc; using System . Co ll ections . Generi c ; using WorkingWit hVisualStudi o . Co n troll e r s ; using WorkingWithVisualS t udio . Mod el s ; using Xunit ;
188 Часть 1. Введение в инфраструктуру ASP.NET Core MVC namespace WorkingWithVisualStudio. Te sts { puЫic class HomeControllerTests { class ModelCompleteFakeRepository : IRepository puЫic IEnumeraЫe<Product> Products { get ; } new Product { Name " Pl '', Price 275М }, new Product { Name "Р2", Price 48.95М }' new Product { Name "РЗ", Price 19.SOM } 1 new Product {Name "РЗ11, Price 34.95М } }; puЬlic void AddProduct(Product р) { //ничего не делать - для теста не требуется [Fact] puЫic void IndexActionModelisComplete() 11 Организация = new Product[] { var controller = new HomeController() ; controller . Repository = new ModelCompleteFakeRepository() ; 11 Действие var model = (controller.Index() as ViewResult)? . ViewData.Model as IEnumeraЫe<Product> ; //Утверждение Assert .Equal(controller .Repo sitory.Products , model , Comparer.Get<Product>( (pl, р2) => pl . Name == p2.Name && pl.Price == p2 . Price)) ; class ModelCompleteFakeRepositoryPricesUnderSO puЬlic IEnumeraЫe<Product> Products { get; } new Product { Name = 11 Pl 11 , Price SM }, new Product { Name 11Р211, Price 4В.95М}, new Product { Name = 11 РЗ 11 1 Price = 19.SOM }, new Product { Name 11РЗ11, Price 34.95М } }; puЬlic void AddProduct(Product р) //ничего не делать - для теста не требуется [Fact] IReposi tory { new Product [ ] puЬlic void IndexActionМodelisCompletePricesUnderSO() / / Организация var controller = new HomeController(); controller.Repository = newModelCompleteFakeRepositoryPricesUnderSO(); //Действие var model = (controller.Index() as ViewResult)?.ViewData.Model as IEnumeraЫe<Product>; //Утверждение Assert.Equal(controller.Repository.Products, model, Comparer. Get<Product> ( (pl, р2) => pl. Name == р2 . Name && pl. Price == р2. Price) ) ;
Глава 7. Модульное тестирование прило ж ений MVC 189 Мы определили фиктивное хранилище , которое содержит только объекты Product со значениями свойства Price меньше 50, и применили его в новом тесте. Запустив этот тест, вы увидите, что он проходит, подкрепляя идею о том , что пробл ема связана с применением метода Where () в методе действия Index () . В реальном проекте выяснение причины отказа теста означает необходимость со гласования цели теста со спецификацией для прилож ения. Вполне может оказаться, что метод Index () обязан фильтровать объекты Product по свойству Price , в случае чего тест нуждается в пересмотре. Это распространенный итог, и тест, который не прошел, вовсе не всегда указывает на присутствие реальной проблемы в приложении. С другой стороны, если метод действия Index () не должен фильтровать объекты мо дели, тогда потребуется внести корректирующую правку (листинг 7.16). Понятие разработки через тестирование В этой главе я придерживаюсь наиболее часто используемого стиля модульного тестиро вания, при котором мы пишем функцию приложения и затем тестируем ее, чтобы удосто вериться в том, что она работает требуемым образом. Такой стиль популярен, поскольку большинство разработчиков думают сначала о прикладном коде, а затем о его тестирова нии (я определенно подпадаю в эту категорию). Проблема такого подхода в том, что он склоняет к созданию модульных тестов только для того прикладного кода, который было трудно писать или серьезно отлаживать , оставляя ряд аспектов каких-то функций лишь частично протестированными или вообще без тестирования . Альтернативным подходом является разработка через тестирование (Test-Driveп Developmeпt - TDD) . Существует много вариаций TDD, но основная идея в том, что тесты для функции пишутся до того, как она реализуется. Написание тестов первыми заставля ет более тщательно думать о реализуемой спецификации и о том, как узнать, что функция была реализована корректно . Вместо погружения в детали реализации подход TDD застав ляет заранее продумывать, чем будет измеряться успех или неудача. Все создаваемые тесты изначально не будут проходить, т.к. новая функция еще не реали зована . Однако по мере добавления кода в приложение тесты постепенно будут переходить из красного состояния в зеленое , и ко времени завершения функции все тесты окажутся пройденными. Подход TDD требует дисциплины, но приводит к получению более полного набора тестов и может дать в результате более надежный и устойчивый код. Листинг 7. 16. Удаление фильтрации посредством LINQ в файле HomeController. cs using Microsoft . AspNe tC ore.Mvc; using WorkingWithVisualStudio . Models ; using System . Linq ; namespace WorkingWithVisualStudio . Controllers puЫic class HomeController : Controller { puЫic IRepository Repository = SimpleRepository .SharedRepository ; puЬlic IActionResul t Index () => View (Repository. Products) ; [HttpGet] puЫic IActionResult AddProduct() = > View( new Product() ) ; [HttpPostJ puЫic IActionResult AddProduct(Product р) { Repository . AddProduct(p) ; return RedirectToAction( " Index ");
190 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Запустив тесты снова, вы увидите, что все они проходят (рис . 7 .6). Run All 1 Run". • Playl ist : All Tests • А Passed Tests (4) О Tests.HomeControllerTests.lnd~xActio nModell sComplete О Tests .H omeControllerTe.sts.l r1de xAction Mod ell sComp lete.. . () Tests .ProductTests.CanChangeProductNome О Tests .ProductTests .Ca nCha ngeP ro duct Price Summary l ast Test Run Passed (Тo tal Run Time 0:00 :04) О 4 Tesls Passed Рис. 7.6. Прохождение всех тестов Р· 1ms 36 ms 1ms 9ms Может показаться, что мы проделали слишком много работы для устранения та кой простой проблемы, однако возможность протестировать отдельный компонент жизненно важна при построении реального приложения. Достижение точки, где вы идентифицировали проблему и написали тесты для проверки исправления, возможна только тогда, когда вы способны эффективно изолировать компоненты. Улучшение модульных тестов В предыдущем разделе был представлен базовый подход к написанию модульных тестов и прогону тестов в Visual Studio, а также акцентировано внимание на важ ности изоляции тестируемого компонента. В этом разделе рассматриваются расши ренные инструменты и средства, которые можно применять для написания тестов более согласованным и выразительным образом. Если вы сильно увлечетесь модуль ным тестированием, то можете в итоге получить настолько много тестового кода, что его ясность становится очень важной, особенно с учетом необходимости пересмотра тестов для отражения изменений в приложении, вносимых во время разработки и сопровождения. Параметризация модульного теста Тесты, написанные для класса Ho meC o n trol l er, выявили проблему, которая присутствовала только для определенных значений данных. Чтобы проверить это ус ловие , были созданы два похожих теста, каждый из которых содержал собственное фиктивное хранилище. При таком подходе появляется дублированный код. особен но учитывая то, что единственное отличие между двумя тестами связано с набором значений decimal, которые указываются для свойства Price объ ектов Product в фиктивных хранилищах.
Глава 7. Модульное тестирование nриложений MVC 191 Инфраструктура xUnit.net предлагает поддержку параметризованных тестов, когда данные, используемые в тесте, из него удаляются, так что единственный ме тод может применяться для множества тестов. В листинге 7.17 с помощью средс тва параметризованных тестов устраняется дублированный код в тестах для класса HorneController. Листинг 7.17 . Параметризация модульного теста в файле HorneControllerTests. cs using Microsoft . AspNetCore . Mvc ; using System.Collections .Gene ric; using WorkingWithVisualStudio.Controllers ; using WorkingWithVisualStudio . Models; using Xunit ; namespace WorkingWithVisualStudio . Tests puЫic class HomeControllerTests { class ModelCompleteFakeRepository : IRepository puЫic IEnumeraЫe<Product> Products { get; set; puЫic void AddProduct(Product р) { 11 ничего не делать - для теста не требуется [Theory] [InlineData(275, 48.95, 19.50, 24.95)] [InlineData(5, 48.95, 19.50, 24.95)] puЫic void IndexActionМodelisComplete(decimal pricel, decimal price2, decimal priceЗ, decimal price4) 11 Организация var controller = new HomeController(); controller.Repository = new ModelCompleteFakeRepository }; Products = new Product [] { new Product {Name "Pl", Price new Product {Name "Р2", Price new Product {Name new Product {Narne 11РЗ11, Price = "Р4", Price 11 Действие = pricel }, = price2 }, = priceЗ }, = price4 }, var mod el = (control ler.Index() as ViewResult)? . ViewData.Model as IEnurneraЫe<Product> ; 11 Утверждение Assert . Equal(controller . Repository .Products , model , Comparer .Get <Product>( (pl, р2) => pl.Name == p2.Name && pl . Price == р2 . Price)); Параметризованные модульные тесты помечаются атрибутом Theory вместо ат рибута Fact, который используется для стандартных тестов. Здесь таюке применя ется атрибут InlineData, который позволяет указывать значения для аргументов,
192 Часть 1. Введение в инфраструктуру ASP.NET Core MVC определяемых методом модульного теста. Язык С# ограничивает способ выражения значений данных в атрибутах, поэтому мы определили четыре аргумента decimal тестового метода и посредством атрибута InlineData предоставили для них значе ния. Значения decimal внутри тестового метода используются для генерации масси ва объектов Product, который применяется для установки свойства Products объ екта фиктивного хранилища. Каждый атрибут Inline определяет отдельный модульный тест, который пока зан как индивидуальный элемент в окне Test Explorer среды Visual Studio (рис . 7.7) . Запись в окне Test Explorer отображает значения. которые будут использоваться для аргументов метода модульного теста. T~t Expl ortr • 1:) х Р· PJJnAU 1 !Wn.• • 1 Playlis t : All Te;ts • А Passed Tбis (4) Summa ry last Tes:t Run Passed (Тot31 Run Т t) 4 Tests Ptssed О Tests.Pre>Cluct e-sts .Ca nCh.a1'9eProductNome $ Tests.Prod1.1ctT~tsC~ Cl'\an.g~ProductPricl!! 2 rns 8ms Рис. 7.7 . Параметризованные тесты в окне Test Explorer среды Visual Studio Получение тестовых данных из метода или свойства Ограничения, налагаемые на выражение данных в атрибутах, сокращает полез ность атрибута I n 1 i n е Da t а. Альтернативный подход предусматривает создание статического метода или свойства, возвращающего объект, который требуется для тестирования. В такой ситуации нет никаких ограничений относительно того, как определяются данные, и можно создавать более широкий диапазон тестовых значе ний. Чтобы продемонстрировать подход в работе, добавим в проект модульного тес тирования файл класса по имени ProductTestData . cs с определением , показанным в листинге 7.18. Листинг 7.18. Содержимое файла ProductTestData. cs из проекта WorkingWithVisualStudio.Tests using System . Collections; using System.Collections.Generic; using WorkingWithVisualStudio . Models; namespace WorkingWithVisualStudio.Tests puЫic class Produ c tTestData : IEnumeraЫe<object[]> puЫic IEnumerator<object[]> GetEnumerator() { yield return new object[] { GetPricesUnder50() }; yield return new object[] { GetPricesOverSO };
Глава 7. Модульное тестирование прило жений MVC 193 IEnumerator IEnumeraЫe . GetEnumerator() return this.GetEnumerator() ; private IEnumeraЫe<Product> GetPricesUnder50() decimal[] prices = new decimal[] { 275, 49 . 95М , 19.SOM, 24 . 95М } ; for (int i =О; i <prices.Length; i++) { yield return new Product { Name = $"P{i + 1}", Price = prices[i] }; private Product [ ] GetPricesOver50 => new Product[ ] new Product { Name "Pl" , Price 5 }, new Product { Name "Р2", Price 48 . 95М }, new Product { Name "РЗ", Price 19.SOM }, new Product { Name "Р4", Price 24 . 95М } }; Тестовые данные пр едоставляются через класс, который реализует интерфейс IEnumeraЫe < obj ect [] >, возвращающий последовательность массивов объектов. Каждый массив объектов в посл едовательн ости содержит один набор аргументов, которые будут передаваться тестовому методу. Мы пе реопределим тестовый метод, чтобы он принимал массив объектов Product, который добавит к тестовым данным еще один уровень - перечисление массивов объектов Product. Так ая глубина струк туры тестовых данных может сбивать с толку, но важно понимать, что инач е т есты не будут работать, когда количество аргументов, которые xUпit.net пытается пе редать тестовому методу, не соответствует сигнатуре метода. Мне нравится имеющаяся структура классов тестовых данных, когда закрытые методы или свойства определяют индивидуальные наборы тестовых данных, кото рые затем с помощью метода GetEnumerator () объединяются в последовател ьности массивов объектов. Для иллюстрации различных прием ов массивы объектов Product были созданы с применением и метода, и свойства, но я пр едпочитаю использовать в своих про ектах один подход (на его выбор влия ет вид данных, с ноторыми проводится тестирование). В листинге 7.19 показано, как можно прим енять класс тестовых дан ных с ат рибутом Theory для настройки тестов. Совет. Если вы хотите включить тестовые данные в тот же самый класс, который определяет модульные тесты, тогда можете использовать атрибут MemberData вместо ClassData. Атрибут MemЬerData конфигурируется с применением строки, указывающей имя стати ческого метода, который будет предоставлять реализацию IEnumeraЫe<object [ ] >, где каждый массив объектов в последовательности является набором аргументов для тес тового метода. Листинг 7.19. Использование класса тестовых данных в файле HomeControllerTests.cs using Microsoft . AspNetCore . Mvc ; using System . Collections . Generic ; using WorkingWithVisualStudio . Controllers ; using WorkingWithVisualStudio . Models; using Xunit ;
194 Ча сть 1. Введение в инфраструктуру ASP.NET Соге MVC namespace WorkingWithVisualStudio . Tests { puЬlic class HomeControllerTests { class ModelCompleteFakeRepository : IRepository puЫic IEnumeraЫe<Product> Products { get; set; puЫic void AddProduct(Product р) { 11 ничего не делать - для теста не требуется [Theory] [ClassData(typeof(ProductTestData))] puЫic void IndexActionМodelisComplete (Product [] products) { 11 Организация var controller = new HomeController(); controller . Repository = new ModelCompleteFakeRepository Products = products }; 11 Действие var model = (contro ller . Index() as ViewResu l t)? . ViewData . Model as IEnumeraЫe<Product> ; 11 Утверждение Assert.Equal(controller . Repository . Products , model , Comparer . Get<Product>( (pl , р2) => pl . Name = = p2 . Name && pl . Price = = p2.Price)) ; Атрибут ClassData конфигурируется с типом класса тестовых данных, которым в рассматриваемом случае является ProductTestData. Во время выполнения тестов ин фраструктура xUnit.net создаст новый экземпляр класса ProductTestData, после чего будет применять его для получения последовательности тестовых данных для теста. На заметку! Если вы посмотрите на список тестов в окне Test Explorer, то увидите, что для тестов IndexActi onModelisComp lete предусмотрена единственная запись, хотя класс ProductTestData предоставляет два набора тестовых данны х . Так происходит в ситуации , когда объекты тестовых данных не удается сериализировать, и проблему можно решить, применив к тестовым объектам атрибут SerializaЫe. Улучшение фиктивных реализаций Эффективная изоляция компонентов требует фиктивных реализаций классов, что бы предоставить тестовые данные или проверить, ведет ли себя компонент так, как должен. В предшествующих примерах создавался класс, реализующий интерфейс IReposi tory . Это мог быть эффективный подход, но он приводил к созданию клас сов реализаций для каждого вида теста, который требовалось запуск ать. В качестве примера в листинге 7.20 демонстрируется добавление теста, который проверяет, что метод действия Index () вызывает метод Products () в хранилище толыю один раз . (Такая разновидность теста распространена, когда имеется опасение , что компонент делает дублирующиеся запросы 1< хранилищу, становясь причиной множества запро сов к базе данных. )
Глава 7. Модульное тестирование прило ж ений MVC 195 Листинг 7.20. Доба вление модульного теста в файле HomeControllerTests. cs using Microsoft.AspNetCore . Mvc ; using System . Collections .Gener ic ; using WorkingWithVisualStudio . Contro llers; using WorkingWithVisualStudi o . Models ; using Xunit ; using System ; namespace WorkingWithVisualStudio . Tests puЫic class HomeControllerTests { class ModelCompleteFakeRepository : IRepository puЫic IEnumera Ы e<Product> Prod ucts { get ; set ; puЫic void AddP r oduct(Product р) { 11 ничего не делать - для теста не требуется [Theory] [ClassData(typeof(ProductTestData))] puЫic void IndexActionModelisComplete(Product[J products) { 11 Организация var controller = new HomeController(); controller . Repository = new ModelCompleteFakeRepository Products = products ); 11 Действие var model = (control ler.Inde x() as Vi ewResult)? . ViewData . Model as IEnumeraЫe<Product> ; 11 Утверждение Assert . Equal(control l er . Repository . Products , model, Comparer . Get<Product>( (pl , р2) => pl.Name = = p2 . Name && pl . Price == p2 .Price)) ; class PropertyOnceFakeRepository IRepository puЫic int PropertyCounter { get; set; } = О; puЫic IEnumeraЫe<Product> Products { get { PropertyCounter++; return new[] { new Product { Name 11 Pl11 , Price puЬlic void AddProduct(Product р) / / ничего не делать - для теста не требуется [Fact] puЬlic void RepositoryPropertyCalledOnce() { / / Организация var repo = new PropertyOnce FakeRepository(); 100}}; var controller = new HomeController { Repository = repo };
196 Часть 1. Введение в инфраструктуру ASP.NET Core MVC // Действие var resul t = controller. Index () ; / / Утверждение Assert.Equal(l, repo.PropertyCounter); } Фиктивные реализации не всегда являются простыми источниками данных; они также могут использоваться для оценки способа, которым компоненты выполняют свою работу. В этом случае было добавлено простое свойство счетчика, которое уве личивается каждый раз, когда читается свойство Products фиктивного хранилища, и с помощью метода Assert. Equal () выполнена проверка , что обращение 1< свойс тву производилось только один раз. Добавление инфраструктуры имитации Создание фиктивных объектов подобного рода выходит из-под контроля, и лучший способ вернуть себе контроль - применить инфраструктуру фиктивных объектов, также называемую ин.фраструктурой имитации. (Существует формальная разница между фиктивными и имитированными объектами, но для облегчения использования современные инструменты тестирования размывают ее , поэтому данные термины бу дут применяться взаимозаменяемо.) В настоящей главе используется инфраструктура Moq , которая описана в табл. 7.4 . Таблица 7 .4 . Помещение Moq в контекст Вопрос Что это такое? Чем она полезна? Как она используется? Существуют ли какие-то скрытые ловушки или ограничения? Существуют ли альтернативы? Изменилась ли она по сравнению с версией MVC 5? Ответ Moq - это программный пакет для создания фиктивных реали заций компонентов в приложении Инфраструктура имитации облегчает создание фиктивных ком понентов для изоляции частей приложения в целях модульного тестирования Moq применяет лямбда-выражения для определения функцио нальности фиктивных компонентов и требует определения толь ко тех средств, которые используются при тестировании Привыкание к синтаксису может потребовать некоторых усилий. Документация и примеры находятся по адресу https : //githuЬ.com/Moq/moq4 Доступно несколько альтернативны х инфраструктур, в том числе NSubstitute (h ttp : / /nsubstitute . githuЬ . io) и FakeltEasy (h ttp : / /fakeiteasy . github. io). Все эти инфраструктуры предлагают похожие возможности, и выбор между ними связан только с предпочитаемым вами синтаксисом Применение инфраструктуры имитации специфично для модуль ного тестирования и не является требованием ASP.NET Core MVC
Глава 7. Модульное тестирование приложений MVC 197 При написании этой главы платформа .NЕТ Core находилась на ранней стадии своей адаптации, и основные пакеты имитации пока еще не поддерживали ее. В Microsoft создали специальное ответвление проекта Moq и перенесли его для ра- боты с . NЕТ Core, так что именно этот пакет будет использоваться в книге . Тем не менее, поскольку это не главный выпуск Moq. требуется дополнительный шаг для конфигурирования NuGet с целью загрузки пакета Microsoft. Выберите в Visual Studio пункт меню Tools~Options (Сервис<=:>Параметры) и в от рывшемся диалоговом окн е перейдите в раздел NuGet Package Manager~ Package Sources (Диспетчер пакетов NuGеt<=:>Источники пакетов), как по1шзано на рис . 7.8. Здесь можно сконфигурировать источники пакетов . 1 Sear<h Optons (Ctrl • E) ~ En\rironment 1 ~ Projocts ond Solu tion< 1 t> Sошсе Control 1 ~ Тoxt Ed~or 1 t• Debugging ~ Pelfo1n11nco Тools ~ D•tabase Тools 1 1 1> Graphks Di~g nostic_'5 А NuGet Packoge M•noger . Generol ~ SQl Server Тools ~ Te>:t Temp lating 0 api.nuget.org https :/iopi .nuget .org/v3/ indexJ<on Ma chin e·wide pockog e 1 P.aclc_гge-S9u rCe:s- t> Tools for Apache Cordovв 0 Mic rosoft and . NЕТ 1 ~ Web https://v1Vм.nu9et.org/api/v2/cural<d·feeds/n1ic rosoftdotneV 1> W~b Forms Designe r J0 Microsoft Vtsual Studio Offline Packages ~ Web Performanco Тest Тools C:\Proarom Files (xSб)\Microsoft SOКs\NuGetPockoaes\ 1 1> Windows Forms Designeг 1 ~ 'Norkflow Designer !':{o mo: /nuget.org ~ XAML Oesig ntt ~ourc'"J~~~~~2_1_______l[J [Noр~ l----------------------------~~~ё;~_:~_,, Рис. 7 .8 . Конфигурирование источников пакетов NuGet Щелкните на кнопке с изображением знака "плюс" зеленого цвета и введите в по лях Name (Имя) и Source (Источник) детальную информацию, описанную в табл. 7.5. Таблица 7.5. Настройки, требуемые для источника пакетов NuGet Поле Name Source Значение ASP . NET Contrib https : //www . myget . org/F/aspnet - contriЬ/api/vЗ/index .j son Щелкните на кнопке Update (Обновить), чтобы задействовать значения, вв еденные в полях, и затем на Rнопке ОК для закрытия диалогового окна настроек. Совет. Применение версии Moq от Microsoft является краткосрочной мерой; вам не придется ее использовать после того, как в результате основных усилий по разработке добавится помержка .NET Core . Когда это произойдет, вы сможете следовать инструкциям по уста новке Moq, доступным по адресу h t tp : / / g i thub. com/moq/moq4.
198 Часть 1. Введение в инфраструктуру ASP.NEТ Саге MVC В листинге 7.21 в файл proj ec t. j so n проекта модульного тестирования до бавляется версия Moq от Microsoft (известн ая как moq . netcore) наряду с пакетом System . Diagn o s tics . Trace S o ur ce, от которого з ависит moq . netcore . Листинг 7 .21 . Добавление Moq в файл proj ect. j son проекта WorkingWithVisualStudio.Tests " ve r sion": "1.0 .0-*", " test Runner" : "xunit ", " dependencies ": { }1 "Microsoft.NETCore .App ": "type": "pl atform", "version": "1 .0 .0" }1 "xunit": "2 .1 .0", "dotnet-test-xunit ": "2 .2 .0 -preview2-build1029", "WorkingWithVisualStudio": "1.0 .0", "rnoq.netcore": "4. 4. О-Ьеtа8", "Systern.Diagnostics . TraceSource": "4 . 0.0" " f r ameworks": { " netcoreappl . 0 ": " i mports": [ "dotnetS. 6 ", "portaЬle-net45+win8"] Создание имитированного объекта Создание имитированного объ екта озн ачает необходимость сообщения Moq о том, какого вида объ ект вас интересует, конфигурирование его поведения и примен ение этого объекта к тестируемой сущности. В листинге 7 .22 инфрастру~<тура Moq исполь зуется для замены двух фиктивных хранилищ в тестах для класса HomeCon t r o ller. Листинг 7.22 . Применение имитированных объектов в файле HomeControllerTests.cs using Microsoft . AspNetCore . Mvc ; using System. Collections . Generic ; using WorkingWithVi s ua l St u dio . Controllers ; u sing WorkingWithVi sualSt ud io . Mode l s ; using Xunit ; using System ; using Moq; namespace WorkingWi thVisual Studi o .Tests puЫ i c class HomeControl l e r Tests { [Theory ] [Cl assDat a(t ypeof(ProductTestDat a ) )] puЫic void Ind e x ActionModelisComplete(Prod u ct[] p r oducts) { 11 Орга н изация var rnock = new Mock<IReposi tory> () ;
Глава 7. Модульное тестирование прило же ний MVC 199 mock.SetupGet(m => m.Products) .Returns(products) ; var controller = new HomeController { Repository = mock.Object }; 11 Действие var model = (controller.Index() as ViewResult)? . ViewData . Model as IEnumeraЬle<Produc t>; 11 Утверждение Assert.Equal(controller.Repository . Products , model, Comparer . Get<Product>( (pl, р2) => pl . Name == p2 . Name && p l. Price == р2. Price)) ; [Fact) puЫic void Reposito ryPropertyCalledOnce() 11 Организация var mock = new Mock<IRepository>(); mock.SetupGet(m => m.Products) . Returns (new [] { new Product { Name = "Pl", Price = 100 } }) ; var controller = new HomeController { Repository = mock.Object }; 11 Действие var result = controller.Index(); 11 Утверждение mock.VerifyGet(m => m.Products, Times.Once); Использование Moq позволило удалить фиктивные реализации интерфейса IRepository и заменить их всего несколькими строками кода. Здесь не будут рас сматриваться детальные сведения о разных поддерживаемых Moq средствах. но даны объяснения способа применения Moq в примерах. Примеры и документация по Moq доступны по адресу https : //github . com/Moq/moq4 . (При объяснении модульного тестирования различных типов компонентов МVС в оставшихся главах книги также будут приводиться соответствующие примеры . ) Первым делом создается новый объект Mock с указанием интерфейса, который должен быть реализован: var mock = new Mock< IRepository> () ; Созданный объект Mock будет имитировать интерфейс IReposi tory. Далее оп ределяется функциональность, которая требуется для теста. В отличие от обычной реализации интерфейса классом для имитированного объекта конфигурируется толь ко поведение, нужное для теста. В первом имитированном хранилище понадобится реализовать свойство Products, чтобы оно возвращало набор объектов Product, ко торый передается тестовому методу через атрибут ClassData: mock . SetupGet(m => m. Products) .Returns(products) ; Метод SetupGet () используется для реализации средства извлечения для свойс тва. Аргументом этого метода является лямбда-выражение, которое указывает подле-
200 Часть 1. Введение в инфраструктуру ASP.NET Саге MVC жащее реализации свойство (Products в данном примере). Метод Returns () вызы вается на результате, полученном из метода SetupGet (), чтобы указать результат, который будет возвращаться, I<огда читается значение свойства. Для второго имити рованного хранилища применяется тот же самый подход. но указывается фиксиро ванное значение: mock . SetupGet(m => m.Products) . Returns (new[] { new Product { Name = "Pl", Price = 100 } }) ; В классе Mock определено свойство Object, возвращающее объект, который ре ализует указанный интерфейс и обладает ранее определенным пов едением. В обоих модульных тестах свойство Obj ect используется, чтобы получить хранилище для конфигурирования контроллера: var controller = new HomeControl l er Repository = mock.Object } ; Последним примененным средством Moq была проверка того, что к свойству Products осуществлялось толыщ одно обращение: mock . VerifyGet(m => m.P roducts , Times . Once) ; Метод Veri f yGet () относится к тем методам класса Mock , которые инспекти руют состояние имитированного объекта, когда тест завершен. В этом случае ме тод VerifyGet () позволяет проверить, сколько раз читалось свойство Products . Значение Times . Once указывает, что метод VerifyGet () должен генерировать ис ключение, если свойство читалось не в точности один раз, и это приведет к тому, что тест не пройдет. (Методы класса Assert , обычно используемые в тестах, генерируют исключение, когда тест не проходит, и потому при работе с имитированными объ ек тами метод VerifyGet (} можно применять для замены метода класса Assert .) Резюме Большая часть этой главы была сконцентрирована на модульном тестировании, которое может оказаться мощным инструментом для повышения качества кода. Модульное тестирование не подойдет абсолютно каждому разработчику, но с ним по лезно поэксперим ентировать, и оно может быть поле з ным, даже есл и используется только для сложных функций или обнаружения проблем. Бьшо описано прим енени е инфраструктуры тестирования xUnit.net, объяснена важность изоляции компонентов для целей тестирования и продемонстрированы некоторые инструменты и при е мы, позволяющие упростить 1юд модульных тестов. В следующей главе начнется разра ботка более реалистичного приложения МVС, чтобы показать, как работают вместе различные функциональные компоненты , прежде чем погружаться в характерные де тали в части II настоящей книги .
ГЛАВА 8 SportsStore: реальное приложение в предшествующих главах мы создавали очень простое приложение МVС. Был описан паттерн MVC, основные средства языка С#, а также инструменты, не обходимые профессиональным разработчикам приложений MVC. Наступило время собрать все вместе и построить несложное, но реалистичное приложение электрон ной коммерции. Наше приложение под названием SportsStore будет следовать классическому под ходу, который повсеместно используется в онлайновых магазинах . Мы создадим он лайновый каталог товаров, который потребители могут просматривать по категори ям и страницам, корзину для покупок, куда пользователи могут добавлять и удалять товары, и форму оплаты, где потребители могут вводить сведения, связанные с до ставкой. Кроме того, мы создадим административную область, которая включает в себя средства создания, чтения, обновления и удаления (create, read, update, delete - CRUD) для управления каталогом товаров, и защитим ее так, чтобы изменения могли вносить только зарегистрированны е администраторы. Цел ь этой и последУющих глав - дать вам возможность увидеть, на что похожа реальная разработка приложений MVC, за счет создания примера приложения, кото рый максимально приближен к реальности. Разумеется, мы будем ориентироваться на ASP.NET Core MVC, поэтому интеграция с внешними системами, такими как база данных, предельно упрощена, а определенные части приложения, например, обработ ка платежей , вообще отброшены. Построение всех уровней необходимой инфраструктуры может показаться не сколько медленным, но первоначальные трудозатраты при разработке приложения MVC окупаются, обеспечивая удобный в сопровожде нии, расширяемый и хорошо структурированный код с великолепной поддержкой модУльного тестирования. Модульное тестирование Я уже достаточно много говорил о легкости проведения модульного тестирования в MVC, а та кже о том , что модульное тестирование м оже т быть важной и полезной частью процесса разр аботки . Это будет демонстрироваться на протя жении данной части книги, поскольку я описываю нюансы и приемы модульного тестирования в их взаимосвязи с основными средствами MVC.
202 Часть 1. Введение в инфраструктуру ASP.NET Саге MVC Я понимаю, что мое мнение не является единственно верным. Если вы не хотите прибе гать к модульному тестированию, то меня это вполне устроит. Таким образом, когда что то относится исключительно к тестированию, оно будет помещаться во врезку, подобную настоящей. Если модульное тестирование вас не интересует, то можете смело пропускать эти врезки, и приложение SportsStore будет работать не менее успешно. Чтобы восполь зоваться преимуществами технологии ASP.NET Саге MVC, вовсе не обязательно проводить какое-либо модульное тестирование, хотя поддержка тестирования, конечно же, является основной причиной перехода на ASP.NET Саге MVC. Большинству средств MVC, используемых в приложении SportsStore, посвящены отдельные главы далее в книге. Вместо того чтобы повторять весь материал , я сооб щаю ровно столько, сколько требуется для понимания этого примера приложения, и указываю главу, в которой можно почерпнуть более подробные сведения. Каждый шаг, необходимый для построения приложения, будет выделен, что поз волит понять, каким образом средства MVC сочетаются друг с другом. Вы должны уделить особое внимание созданию представлений . Если вы не будете в точности сле довать примерам, то получите несколько странные результаты. На заметку! В Microsoft заявили , что в следующей версии Visual Studio изменят инстру ментарий, применяемый для создания приложений ASP.NET Core MVC. Проверяйте веб-сайт издательства на предмет обновлений, которые появятся после выпуска новых инструментов. Начало работы Если вы планируете писать код приложения SportsStore на своем компьютере во время изучения материала этой части книги, то вам придется установить Visual Studio и удостовериться в том, что установлен вариант LocalDB, который требуе тся для постоянного хранения данных. На заметку! Если вы просто хотите работать с проектом, не воссоздавая его, тогда може те загрузить готовый проект SportsStore как часть загружаемого кода примеров для на стоящей книги, который доступен на веб-сайте издательства. Разумеется, вы вовсе не обязаны повторять все действия. Я старался делать снимки экрана и листинги кода мак симально простыми в отслеживании на тот случай, если вы читаете эту книгу в поезде , кафе или где-то еще. Создание проекта MVC Мы будем следовать тому же самому базовому подходу, который использовал ся в предшествующих главах и заключается в том, чтобы начать с пустого проек та и добавлять в него все необходимые конфигурационные файлы и компоненты . Выберите в меню File (Файл) среды Visual Studio пункт NewqProject (СоздатьqПроект) и укажите шаблон проекта ASP .NET Саге Web Application (.NET Саге) (Веб-приложение ASP.NET Core (.NET Core)), как показано на рис. 8.1 . В качестве имени проекта введите Spo r ts Stor e и щелкните на кнопке ОК.
Глава 8. SportsStore: реальное приложение 203 "' 1emp14tts "' Vi~ual С'* "W indo ~ i.'! •l>• . NfТ Core дndroid Cloud f!. Online Solu tion name: 4 mASP.NEТ V/eb Applicdtion (.NfТ FramбVork) 11 @ ASP.NEТ Core Wtb Applk ati on (.N ЕТ fra meowork} SportsS-tore ~c:\p_r~~ct~ ~. ,Sport sSl:o re Рис. 8. 1. Выбор типа проекта Visu.:i l (# ТУР": ; Pr oj« Corea: , ondO~ 9Appl~ 0 Ad~ Opt t uУф Выберите шаблон Empty (Пустой), как проиллюстрировано на рис. 8.2, и щелкните на кнопке ОК, чтобы создать проект Sp orts Store . S•l•d • l •mplat<: г~;;:;C:reT;,;;i" ------·-··------1 li ~m 1 j1 Web API App~::i;on j 1i 1 1 An empty project temp late for crtating "" ASP.NП Со1е 1 ~pp lic.5tfo n . This; template does not h~e any content in J ~ 1 1! 1 1i ~ г~~~~-~~~c;titJ~J 1 11_ ------ ------- ____________" ___11 Authentication: NoAuthentkatlon 1 1 1 ~ Microsoft Azu re G О Host in the cJoud 1 !,Арр Se~;, ...._. .:] 1 Рис. 8.2. Выбор шаблона проекта Добавление пакетов NuGet 1 [ ОК ']j_C..nceJ 1j --- -----· ------ Шаблон проекта Empty устанавливает базовые средства ASP.NET Core , но требу ются дополнительные пакеты, чтобы предоставить функциональность, обязатель ную для приложений МVС. В листинге 8.1 приведены изменения, внесенные в файл p r oj ect . j son, которые добавляют пакеты, необходимые для того , чтобы начать раз работку приложения SportsStore.
204 Часть 1. Введение в инфраструктуру ASP.NET Co re MVC Листинг 8.1 . Добавление пакетов NuGet в файле proj ect. j son " dependencies ": { " Microsoft . NETCore . App ": "version": "1 .0 .0", " type ": " platform " }' " Microsoft .AspNetCore . Diagnostics " : " 1 . О . 0 ", " Microsoft . AspNetCore . Server .I IS i ntegration ": "1. 0 . 0 ", " Microsoft . AspNetCore . Server . Kestrel ": " 1 . 0 . О ", " Microsoft.Extensions . Logging . Console ": " 1.0 . 0 ", "Мicrosoft. AspNetCore. Razor. Tools": { }' "version": 11 1. О . 0-preview2-final", "type": "Ьuild" "Мicrosoft. AspNetCore. StaticFiles": "1. О. О", "Мicrosoft.AspNetCor e.Mvc": "1. О. 0" )' "tools": { "Мicrosoft.AspNetCore.Razor.Tools": "1.0 . 0 -preview2-final 11 , " Microsoft . AspNetCore . Server .I ISintegration.Tools": " 1.0 .0 -preview2-final " )' " frameworks ": { " netcoreappl . 0 ": " imports ": [ "dotnetS . 6 ", "portaЫe - net45+win8 " ] )' " buildOptions ": }' " emitEntryPoint ": true , " preserveCompilationContext ": true " runtimeOptions ": { " configProperties ": " System.GC .Server" : true }' " puЬlishOptions ": " include ": ["wwwroot ", " web .config " ] )' " scripts ": { " postpuЫish ": [ " dotnet puЫish - iis -- puЫish - folder % puЬlish : OutputPath % -- framework % puЫish : FullTargetFramework % " } Пакеты, добавленные в раздел dependencies файла proj ect . j son, предостав ляют самую базовую функциональность, требующуюся для начала разработки при ложения MVC. По мере разработки приложения SportsStore будут добавляться и дру гие пакеты, но пакеты в разделе dependencies являются хорошей отправной точкой (табл. 8.1).
Глава 8. SportsStore: реальное приложение 205 Кроме пакетов в разделе dependencies в листинге 8.1 показано добавление в раздел tools файла proj ect . j son, которое конфигурирует пакет Microsof t. AspNetCore . Razor . Tools для применения в Visual Studio. Он включает средство IntelliSense для встроенных дескрипторных вспомогательных классов, используемых для создания НТМL-содержимого, которое приспособлено под конфигурацию прило жения MVC. Таблица 8.1 . Дополнительные пакеты NuGet в файле proj ect. j son Имя Описание Microsoft . AspNetCore. Mvc Этот пакет содержит инфраструктуру ASP. NET Core MVC и предоставляет доступ к основным средс твам , таким как контроллеры и представления Razor Microsoft .AspNetCo re. StaticFiles Этот пакет обеспечивает поддержку для обслужи вания статических файлов, таких как файлы изоб ражений , JavaScript и CSS, из папки wwwroot Microsoft . AspNetCore. Razor . Tools Этот пакет предлагает инструментальную подде ржку для представлений Razor, в том числе средс тво lntelliSense для встроенных дескриnторных вспомогательных классов, которые применяются в представлениях внутри приложения SportsStore Создание структуры папок Следующий шаг заключается в добавлении папок, которые будут содержать ком поненты, требуемые для приложения MVC: модели, контроллеры и представления. Чтобы создать папки , описанные в табл. 8.2, щелкните правой кнопкой мыши на эле менте проекта SportsStore в окне Solution Explorer (элемент внутри папки src), вы берите в контекстном меню пункт Addi:::>New Folder (Добавить~:::> Новая папка) и введите имя папки. Позже потребуются дополнительные папки, но создаваемые сейчас папки отражают главные части приложения МVС и для начала их вполне достаточно. Таблица 8.2. Папки, требующиеся для проекта SportsStore Имя Models Contro llers Views Описание Эта папка будет содержать классы моделей Эта папка будет содержать классы контроллеров Эта папка будет содержать все, что относится к представлениям, в том числе индивидуальные файлы Razor, файл запуска представле ния и файл импортирования представлений Конфигурирование приложения Приложение ASP.NET Core МVС полагается на несколько конфигурационных фай лов . Имея установл енные пакеты NuGet, понадобится отредактировать класс Startup, чтобы сообщить ASP.NET о том. что они должны использоваться (листинг 8 .2).
206 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC Листинг 8.2. Включение средств в файле Startup. cs using Microsoft . AspNetCore . Builder ; using Microsoft . AspNetCore . Hosting ; using Microsoft.AspNetCore.Http; using Microsoft . Extensions . Dependencyinjection ; using Microsoft .Extensions.Logging ; namespace SportsStore { puЫic class Startup { puЫic void ConfigureServices(IServiceCollection services) { services.AddМvc(); puЬl i c void Configure(IApplicationBuilder арр , IHostingEnvironment env , ILoggerFactory loggerFactory) { app.UseDeveloperExceptionPage(); app.UseStatusCodePages(); app.UseStaticFiles(); app.UseМvcWithDefaultRoute(); Метод ConfigureServices применяется для настройки разделяемых объектов , которые могут использоваться повсеместно в приложении через средство внедрения зависимостей, которое рассматривается в главе 18 . Метод AddMvc (), вызываемый внутри метода ConfigureServices (), является расширяющим методом, который настраивает разделяемые объекты, применяемые в приложении MVC. Метод Configu re () используется для настройки средств, которые получают и обрабатывают НТГР-запросы . Каждый метод. вызываемый в методе Configure (), представляет собой расширяющий метод, который настраивает средство обработки НТГР-запросов (табл. 8.3). Таблица 8.3 . Начальные методы для настройки средств, вызываемые в классе Startup Метод UseDeveloperExceptionPage() UseStatusCodePages() UseStat i cFiles() UseMvcWithDefaultRoute() Описание Этот расширяющий метод отображает детали исключе ния , которое произошло в прило жении , что полезно во время процесса разработки . Он не дол жен быть вклю чен в развернутых приложениях; в главе 12 будет пока зано , как отключить данное средство Этот расширяющий метод добавляет простое сообще ние в НТТР-ответы, которые иначе бы не имели тела, такие как ответы 404 - Not Found (404 - не найдено) Этот расширяющий метод включает поддержку для обслуживания статического содержимого из папки wwwroot Этот расширяющий метод включает инфраструктуру ASP.NET Core MVC со стандартной конфигурацией (которая позже в процессе разработки будет изменена)
Глава 8. SportsStore : реальное прило ж ение 207 На заметку! Класс Startup - важное средство ASP.NEТ Саге . Он будет подробно описан в главе 14. Дале е понадобится подготовить приложение для представлений Razor. Щелкните правой кнопкой мыши на папке Views, выберите в конт екстном меню пyнкт AddqNew ltem (Добавить q Новый элемент) и укажите шаблон MVC View lmports Page (Страница импортирования представлений MVC ) из категории ASP.NET (рис. 8 .3). AS!'.NEТ 1 1 Cl1enH ide Code '1 Online l!il" MVC V.<w l•yovt P•ge r:s: 12.1 MVC View Stм Page ASP.NEТ ASP.NE1 1 1 ~·~ ~z.crТ119 Hrlpe1 ASP.NEТ ~·fq Mi ddlew11re (iass ASP.NП ~·!ti Startup c l11~s ASP.NE1 vГJ ASP.NEТ Conligura tion File д\Р.NП 1 ы.-~ (li<> he<tlo go 011!one •nd l•ndtempl1tr1 c-~---v~~-~=:_~~------------- Se.шhlnS1all. ~ • Туре: ASP~ MVCView~ 1 ~ f ( " "1 f J Рис . 8.3. Создание файла импортирования представлений Щелкните на кнопке Add (Добавить), чтобы создать файл _Vi ew i mp orts . c s html и приведите его содержимое в соответствие с листинго м 8 .3 . Листинг 8.3. Содержимое файла_Viewimports. cshtml из папки Views @using Sports S tore . Models @addTagHelper * , Microsoft . AspNetCo r e . Mvc .T agHe l pers Оператор @using позволит применять типы и з пространства имен Sports S t ore . Model s в представлениях, не ссылаясь на это пространство имен. Оператор @addTagHelper включает встроенные дескрипторны е вспомогательные классы , ко торые будут использоваться позже для создания элементов HTML, отражающих кон фигурацию прил ожения SportsStore. Создание проекта модул ьного тестирования Создание про екта модульного тестирования требует выполнения процесса, кото рый был описан в главе 7. С помощью проводника файлов создайте папку по име ни te s t на том же уровне, что и существующая папка s r c, внутри папки решения SportsStore . Возвратит есь в Visual Studio , щелкните правой кнопкой мыши на элементе ре шения SportsStore (элемент верхнего уровня в окне Solution Explorer), выберите в контекстно м меню пункт Add q New Solution Folder (Добавить q Новая папка решения) и установите имя новой папки в t est .
208 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC Щелкните правой кнопкой мыши на папке test в окне Solution Explorer и выбери те в контекстном меню пункт Add~New Project (Добавить~Новый проект). Выберите шаблон Class Library (.NET Core) (Библиотека классов (.NET Core)) из категории lnstalled~Visual C#~.NET Core (УстановленныеqVisuаl C#q .NET Core), как показано на рис. 8.4, и установите имя проекта в SportsStore. Tests . \Neb .N_Er.~le; Android C1oud E.(tensiЬility iOS Reporti ng Silverlight Test WCF t- Online Name: Loc~tion: Console Applic'1 tion (.NЕТ Core} Visua\ C:: ASP.NEТ Core WebApplit.: ! tion (.N П Core) VisuaJ (:; S_po 11 sSto 1 ~Tбts !c:~roj!d,!~P~~~~l~~t,.~~~t,.,.~ ~:~.~ Рис. 8.4. Создание проекта модульного тестирования Щелкните на кнопке Browse (Обзор) и перейдите в папку test. Щелкните на кноп ке ОК, чтобы выбрать эту папку, и затем щелкните на ОК, чтобы создать проект мо дульного тестирования. После создания проекта модульного тестирования отредактируйте содержащийся в нем файл proj ее t . j son согласно листингу 8.4, и добавьте пакеты, необходимые для тестирования и создания имитированных объектов. На заметку! Пакет rnoq. ne tcore, который применяется в листинге 8.4, требует изменения конфигурации Visual Studio , как было описано в разделе "Добавление инфраструктуры имитации" главы 7. Если вы не прорабатывали примеры из главы 7, то должны внести это изменение в конфигурацию прямо сейчас. Листинг 8.4 . Содержимое файла proj ect. j son из проекта модульного тестирования "version ": "1.0.0- * ", " testRunner ": "xunit", "dependencies": { " Microso ft.NETCore.App": "type ": "platform ", "ver sion ": "1.0.0" }, " xunit": "2. 1. О ", " dotnet -test-xunit ": " 2 . 2 .0 -previ ew2 -build l02 9", "moq.net core ": "4.4. 0 -beta8 ",
Глава 8. SportsStore : реальное приложение 209 "S ystem .Diagnostics . TraceSource" : "4.0 .0", "SportsStore ": "1. 0 .0" )1 "frameworks ": { "ne t coreappl.O": "imports": ["dotnet5.6", "portaЫe-net 4 5+win8"] Проверка и запуск приложения Проекты приложения и модульного тестирова ния созданы, сконфигурированы и готовы к раз работке. Окно Solution Explorer должно содержать элементы, показанные на рис. 8.5. Если вы види те другие элементы или элементы расположены не в тех позициях, то возникнут проблемы, поэ тому уделите время проверке, что все элементы присутствуют и находятся на своих местах. Выбрав в меню Debug (Отладка) пункт Start Debugging (Запустить отладку) или Start Without Debugging (Запустить без отладки) , если вы пред почитаете итеративный стиль разработки, опи санный в главе 6, вы увидите страницу ошибки (рис. 8.6). Сообщение об ошибке отображается из-за того, что в настоящий момент в приложе нии нет контроллеров для обработки запросов; эту проблему мы вскоре решим. Solution bplortt ~Q ~ j Ф·:< iJ' jj''"- "~~~~1.~t~~!~:~f~~_!:~>- - "' ~~ Solution lttms lJ glob•l.j •on " ._ ~1( • iJ Sport<Store ~ }' Propert;e. ~ • ·8 Referenc6 0 1 ~V\W/fOOt • ·• Depмd tncie :-;; ,_ Controllers ~ Models " ~ Vit'\ 'JS (21 _Vie"нtmports .c-;h tn1I с• Progrмц s ~ 6J prcject.json ,!) Project_Readme.html с• Startup.cs v') v1eb.config .jf !i.. tt.st " ~S Sport.sStore.Tms flc /' Propertie\ i. • ·1 Referencб с- Clг~s1.cs. ~ /J project.j•on Р· ~~--·-·. ... . -· ~-· _... _ __,__ Начало работы с моделью предметной области Рис. 8.5. Окно Solution Explorer для проектов приложения SportsStore и модульного тестирования Все проекты начинаются с модели предмет- ной области, которая является центральной частью приложения МVС. Поскольку мы строим приложение электронной коммерции, то наиболее очевидная модель, кото рая необходима, касается товара. Добавьте в папку Models файл класса по имени Product . cs с определением, приведенным в листинге 8.5 . Листинг 8.5. Содержимое файла Product. cs из папки Models namespace SportsStore . Models { puЫic class Product { puЫic int ProductID { get ; set; puЫic string Name { get ; set; } puЫic string Description { get ; set; puЬlic decimal Price { get; set; } puЫic string Category { get ; set ; }
210 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC lnt~rn"1 S erv~r Error Х ----~~ : 1 Status Code : 404; Not Fou nd____ ----, ________J Рис. 8.6. Выполнение приложения SportsStore Создание хранилища Нам нужен какой-то способ получения объектов Product из базы данных. Как объяснялось в главе 3, модель включает в себя логику для сохранения и извлечения данных из постоянного хранилища. Пока можно не беспокоиться о том, как будет реализовано постоянство данных, но необходимо начать процесс определения ин терфейса для него . Добавьте в папку Model s новый файл интерфейса С# по имени I ProductRepository . cs и поместите в него определение, показанное в листинге 8.6. Листинг 8.6. Содержимое файла IProductReposi tory. cs из папки Models using Systern. Col lecti ons .Generic; narnespac~ SportsStore . Mod els { puЫ ic interface IProduc t Repos i tory IEnurnera Ы e<Product> Product s { get; Этот инте рфейс использует I E n urneraЫe < T > , чтобы позволить вызывающе му коду получать последовательность объектов Product , нич его не сообщая о том, как или где хранятся либо извлекаются данные. Класс, зависящий от интерфейса IProdu ctRepos i tory, может получать объекты Produ ct, ничего не зн ая о том, от куда они поступают или каким образом класс реализации будет их доставлять. В про цессе разработки мы еще будем возвращаться к интерфейсу IProduc tReposito r y , чтобы добавлять в него нужные средства . Создание фиктивного хранилища Теперь, когда определен интерфейс, можно было бы реализовать механизм пос тоянства и привязать его к базе данных, но сначала необходимо добавить ряд дру гих частей приложения. Для этого мы создадим фиктивную реализацию интерф ейса IProdu ctRepos i t o ry, которая будет замещать хранилище данных до тех пор, пока мы им не займемся . Чтобы со здать фиктивное хранилище , доб авьте в папку Models файл класса по имени FakeProductRepo si tor y . cs и поместите в него определени е, представленное в листинге 8.7 . Листинг 8. 7 . Содержимое файла FakeProductRepository. cs из папки Models us i ng System. Collections . Ge neri c ; namespace SportsStore . Model s { puЬl i c class FakeProductRepository I Pr oduc t Repos i tory {
Глава 8. SportsStore : реальное приложение 211 puЫic IEnumeraЫe<Produc t> Products => new List<Product> new Product { Name "Football", Price = 25 }, new Product { Name "Surf board", Price = 179 } , new Product { Name "Runn ing shoes", Price = 95 } }; Класс FakeProductReposi tory реализует интерфейс IProductReposi tory, воз вращая фиксированную коллекцию объектов Product в качестве значения свойства Products. Регистрация службы хранилища В МVС делается особый акцент на применении слабо связанных компонентов, что означает возможность вносить изменения в одну часть приложения, не внося соответс твующие изменения где-то в другом месте. Такой подход трактует части приложения как службы, которые предоставляют функциональные средства, используемые другими частями приложения. Класс, предоставляющий службу, впоследствии может быть мо дифицирован или заменен, не требуя внесения изменений в классы , которые его задейс твуют. Более подробно это объясняется в главе 18, а в случае приложения SportsStore необходимо создать слу;н:бу хранилища, которая позволит контроллерам получать ре ализующие интерфейс I ProductRep os i tory объекты, не зная, какой класс применя ется. В итоге появится возможность начать разработку приложения с использованием простого класса FakeProductRepository, созданного в предыдущем разделе, и позже заменить его реальным хранилищем, не внося изменения во все классы, которым ну;кен доступ в хранилище. Слу;кбы регистрируются в методе ConfigureServices () класса Startup; в листинге 8.8 определена новая службы ДJIЯ хранилища. Листинг В.В. Создание службы хранилища в файле Startup. cs using Microsoft . AspNetCore . Builder; using Microsoft.AspNetCore . Hosting; using Microsoft . AspNetCore . Http ; using Microsoft . Extensions.Dependencyinjection ; using Microsoft . Extensions.Logging; using SportsStore.Models; namespace SportsStore { puЫic class Startup { puЫic void ConfigureServi ces(IServiceCo llection services) { services.AddTransient<IProductRepository, FakeProductRepository>(); services . AddMvc() ; puЫic void Configure(IApplicationB uilder арр , IHostingEnvironment env, ILoggerFa ctory loggerFactory) { app . UseDeveloperExceptionPage(); app.UseStatusCodePages() ; app .Us eStaticFiles() ; app . UseMvcWithDefaultRoute();
212 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC Добавленный в метод Confi g ure Servi ce s ( ) оператор сообщает инфраструкту ре ASP.NET о том, что когда компоненту наподобие контроллера н е обходима р е али зация интерфейса IProductRepository , она должна получить экземпляр класса FakeProdu ctReposi tory . Метод Ad d Transie nt () указывает, что каждый раз. когда требуется реализация интерфейса IProd uctRepository, должен создаваться новый объект FakeProductRepos i tory. Не беспокойт е сь, есл и смысл код а пока не особен но понятен; вскоре вы увидите, как он вписывается в приложение. а детали того, что происходит, будут представлены в главе 18. Отображение списка товаров Оставшуюся часть главы можно было бы посвятить построению модели предм ет ной области и хранилища, вообще не касаясь других частей приложения. Однако та кой подход выглядит довольно скучным, поэтому мы пойдем другим путем и присту пим к интенсивному применению MVC, вернувшись к добавлению средств модели и хранилища , когда они понадобятся . В этом разделе нам предстоит создать контроллер и метод действия , который мо жет отображать сведения о товарах в хранилище. Пока ими будут только данные из фиктивного хранилища, но позже мы решим эту задачу. Мы также подготовим на чальную конфигурацию маршрутизации, чтобы инфраструктур а MVC з нал а , каким образом сопоставлять запросы в приложении с контроллером , который будет создан. Использование формирования шаблонов MVC в Visual Studio Везде в книге контроллеры и представления MVC создаются за счет щелчка правой кнопкой мыши на папке в окне Solution Explorer, выбора в контекстном меню пункта Add<=:>New ltem (Добавить~Новый элемент) и указания шаблона элемента в открывшемся диалоговом окне Add New ltem (Добавление нового элемента). Существует альтернативный прием , называ емый формированием шаблонов (scaffolding), при котором среда Visual Studio предлагает в контекстном меню Add (Добавить) пункты, специально предназначенные для создания контроллеров и представлений . Выбор таких пунктов меню способствует выбору сценария для создаваемого компонента, такого как контроллер с действиями , допускающими чтение / запись, или представление, которое содержит форму, применяемую для создания специфи ческого объекта модели. В настоящей книге формирование шаблонов не используется . Код и разметка , генериру емые средством формирования шаблонов, являются настолько общими, что едва ли не бесполезны , в то время как набор поддерживаемых сценариев ограничен и не решает рас пространенные задачи разработки . Цель настоящей книги - не только донести до вас зна ния, каким образом создавать приложения MVC, но также объяснить, как все работает "за кулисами" , и сделать это гораздо труднее, когда ответственность за создание компонентов возлагается на средство формирования шаблонов. Тем не менее, это еще одна ситуация, когда ваш стиль разработки может отличаться от мое го , и вы вполне можете предпочесть работу со средством формирования шаблонов. В таком случае можете включить его, сделав ряд добавлений в файле p r oj ect . j son. Во-первых, в разделе dependencies потребуется указать два новых пакета: " dependen cies ": { "Microsoft.NETCore .App ": " version": "1.0 .0", " type": "pl atform" }'
}, Глава 8. SportsStore : реальное nрило ж ен и е 213 " Microsoft . AspNetCore . Di agnostics ": "1. 0 . 0 ", " Microsoft .AspNetCore . Server .IISintegr ati on": "1. 0 . О ", " Microsof t. AspNetCore . Server . Kes t rel ": "1.О.О", " Microsoft . Extensions . Logging . Console ": " 1 . 0.0 ", "Microsoft.AspNet Core .Razor .Tool s ": { }1 "version": "l.0 .0-preview2-final", " type ": " build " "Microsoft.AspNetCore . Stati cFiles ": "1 .О . О", " Microsof t. AspNetCore . Mvc ": "1.О.О", "Microsoft.VisualStudio.Web.CodeGeneration.Tools": "version" : 11 1 . О. 0-preview2-final", 11 type 11 : 11 build11 }, 11 Мicrosoft.VisualStudio.Web.CodeGenerators.Mvc 11 : 11 version 11 : 11 1. О. O-preview2-final 11 , "type": "build" Во-вторых, эти пакеты должны быть зарегистрированы в разделе tools : "tools": { }, 11 Microsoft .AspNetCore .Razor . Tool s ": "l.0 .0 - preview2- final 11 , "Microsoft .AspNetCore . Server . IISintegr ation . Too l s ": " l . 0 . 0 - preview2 - final", "Microsoft. VisualStudio. Web. CodeGeneration. Tools 11 : { "version 11 : 11 1 . О . O-preview2-final 11 , "imports 11 : [ 11 portaЫe-net45+win8+dnxcore50 11 , 11 portaЫe - net45+win8" После сохранения изменений и установки пакетов средой Visual Studio вы увидите новые пункты в контекстных меню, открываемых по щелчку правой кнопкой мыши на папках в окне Solution Explorer. Выбор этих пунктов меню будет приводить к появлению диалоговых окон, позволяющих выбирать сценарии , которые должны применяться для создания контроллера или представления. Добавление контролле ра Чтобы создать п ервый контроллер в приложении, добавьте в папку Con t r ol lers файл класса по им ени ProductController . c s с опр едел е нием, показанным в лис тинге 8.9. Листинг 8.9. Содержимое файла ProductController. cs из папки Controllers using Microsoft . AspNetCore . Mvc ; using SportsStore .Mode l s ; namespace SportsStore . Controllers
214 Часть 1. Введение в инфраструктуру ASP.NET Core MVC puЫic class ProductController : Controller private IProductRepository repository; puЫic ProductController(IProductRepository repo) { repository = repo; Когда инфраструктуре MVC необходимо создать новый экземпляр клас са ProductController для обработки НТГР-запроса, она проинспектирует конструктор и выяснит, что он требует объекта, который реализует интерфейс IProductRepository. Чтобы определить. какой класс реализации должен исполь зоваться, инфраструктура МVС обращается к конфигурации в классе Startup, ко торая сообщает о том, что необходимо применять класс FakeRepository, а также о том, что каждый раз должен создаваться его новый экземпляр. Инфраструктура MVC создает новый объект FakeReposi tory и использует его для вызова конструктора ProductController с целью создания объекта контроллера, который будет обраба тывать НТТР-запрос. Такой подход известен под названием внедрение зависимостей и позволяет объек ту ProductController получать доступ к хранилищу приложения через интерфейс IProductRepository без необходимости в знании того, какой класс реализации был сконфигурирован. Позже мы заменим фиктивное хранилище реальным, а благодаря внедрению зависимостей контроллер продолжит работать безо всяких изменений. На заметку! Некоторым разработчикам не нравится внедрение зависимостей, поскольку они считают, что оно усложняет приложения. Я не придерживаюсь такой точки зрения, но если вы не знакомы с внедрением зависимо стей, то рекомендую вам дождаться главы 18, пре жде чем п ринимать решение. Далее добавьте метод действия по имени List (),который будет визуализировать представление, отображающее полный список товаров из хранилища (листинг 8.1 О). Листинг 8.10. Добавление метода действия в файле ProductController. cs using Microsoft . AspNetCore . Mvc; using SportsStore . Models; namespace SportsStore.Controllers puЫic class ProductController : Controller private IProductRepository repository; puЫic ProductController(IProductRepository repo) { repository = repo; puЬlic ViewResult List() => View(repository.Products); Вызов метода View () подобного рода (без указания имени представления) указы вает инфраструктуре MVC о том, что нужно визуализировать стандартное представ ление для метода действия. Передача методу View () экземпляра List<Product> (списка объектов Product) снабжает инфраструктуру данными, которыми необходи мо заполнить объект Model в строго типизированном представлении.
Глава 8. SportsStore: реальное приложение 215 Добавление и конфигурирование представления Нам нужно создать представление для отображения содержимого пользователю, но потребуется проделать ряд подготовительных шагов, чтобы упростить написание представления. Сначала необходимо создать разделя емую компоновку, в которой бу дет определено общее содержимое, включаемое во все отправляемые клиентам НТМL ответы . Разделяемые компоновки - удобный способ обеспечить согласованность представлений и наличие в них важных файлов JavaScript и таблиц стилей CSS; их работа объяснялась в главе 5. Создайте папку Views /Shared и добавьте в нее новую страницу компоновки пред ставлений MVC по имени Layou t. cshtml, которое является стандартным именем, назначаемым средой Visual Studio элементу такого типа. В листинге 8. 11 приведено содержимое файла_Layout . cshtml. В стандартное содержимое внесено одно изме нение , связанное с установкой внутренностей элемента ti tle в SportsStore . Листинг 8.11. Содержимое файла _Layout. cshtml из папки Views/Shared < ! DOCTYPE html> <html> <11 ead> <meta name= " viewport " content= " width=d evice-width " /> <title>SportsStore</title> </head> <body> <div> @RenderBody () </div> </body> </html> Далее пон адобится сконфигурировать прилож ение, чтобы файл _L ayout . cshtml прим енялся по ум олчанию . Это делается добавлением в папку Views файла с шаблоном MVC View Start Page (Ф айл запуска представления MVC) и именем ViewStar t . cshtml. Стандартное содержимое, добавляемое Visual Studio (листинг 8.12), выбирает ком поновку по имени _Layout . cshtml , которая соответствует файлу из листинга 8 . 11 . Листинг 8.12. Содержимое файла_ViewStart. cshtml из папки Views @{ Layout " Layout "; Теперь необходимо добавить представлени е, 1юторое будет отображаться, 1\ОГ да для обработки запроса используется метод действия List () . Создайте папку Views/Product и добавьте в нее файл представления Razor по имени Li st. cshtml. Поместите в него разметку. показанную в листинге 8 . 13 .
21 б Часть 1. Введение в инфраструктуру ASP.NET Соге MVC Листинг 8.13. Содержимое файла List. cshtml из папки Views/Product @model IEnumeraЬle<Product> @foreach (var р in Model) { <div> <hЗ>@p . Name</hЗ> @p .D escription <h4>@p .P rice .T oS tr ing( " c ") </h4> </div> Выражение @rnodel в начале файла указывает, что представл ение будет получать от метода действия последовательность объектов Product в качестве данных модели. С помощью выражения @foreach осуществля ет ся проход по этой последовательнос ти и генерация простого набора НТМL-элементов для 1шждого полученного объекта Product . Представлению не известно, откуда поступили объекты Product, как они были получены или охватывают ли они все товары, известные приложению. Взамен пред ставление имеет дело только с тем , как отображать детали каждого объекта Product с применением НТМL-элементов, что согласуется с принципом разделения обязаннос тей, который был описан в главе 3. Совет. Значение свойства Р r i се преобразуется в строку с использованием метода ToString ( " с "), который визуализирует числовые значения в виде денежных зна чений в соответствии с настройками культуры, действующими на сервере . Например , если в качестве настройки культуры сервера установлено en -u s, тогда вызов (1002 . 3) . ToString ( " с " ) возвратит значение $1 , 002. 30, но если для сервера ус тановлена культура en -GB , то этот же вызов возвратит значение f. 1 , 002 . 30 . Установка стандартного маршрута Инфраструктуре MVC понадобится сообщить, что она должна отправлять запросы, поступающи е для корневого URL приложения (h t tp: //мой-сайт/ ), методу действия List () класса ProductController. Это делается путем редактирования оператора в классе Startup, который настр аивает iuraccы МVС, обрабатыв аю щие НТТР-запросы (листинг 8.14). Листинг 8.14. Изменение стандартного маршрута в файле Startup. cs using Microsoft . AspNetCore . Builder ; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions . Dependencylnjection ; using Microsoft . Extensions . Logging ; using SportsS tore . Models ; namespace SportsStore { puЫic class Startup { puЬlic void ConfigureServices( I ServiceCollection services) { services.AddTransient<IProductRepository, FakeProductRepository>() ; services.AddMvc() ;
Глава 8. SportsStore: реальное прило же ние 217 puЫic void Configure(IApplicationBuilder а рр, IHost ingEnvironment env, ILoggerFa ctory loggerFactory) { app .Us e Develop erExc ept i on Pag e() ; app . UseStatusCodePages(); app . UseSta t icFiles( ) ; арр. UseМvc (routes => { routes .мapRoute ( name: "default", template: "{controller=Product}/{action=List}/{id?}"); }); Метод Configure ( ) класса Startup применяется для настройки 1юнвейера за просов, состоящего из классов (известных как промежуточное программное обеспе чение), которые будут инспектировать НТГР-запросы и генерировать ответы. Метод UseMvc () настраивает пром ежуточное программное обеспечение МVС, причем одним из параметров конфигурации является схема, которая будет использоваться для со поставления URL с контроллерами и методами действий. Система маршрутизации подробно рассматривается в главах 15 и 16, а пока просто следует знать, что измене ния, выделенные в листинге 8 . 14 . указывают инфраструктуре MVC на необходимость отправки запросов методу действия List () контроллера Product, если только в URL запроса не указано иное. Совет. Обратите внимание, что в листинге 8.14 имя контроллера указано как Product , а не ProductController , являющееся именем класса. Это часть соглашения об именова нии MVC, в рамках которого имена классов обычно заканчиваются словом Controller , но при ссылке на класс данная часть имени опускается. Соглашение об именовании и его влияние объясняются в главе 31. Запуск приложения Все основные компоненты в наличии. Мы имеем контроллер с методом действия, который MVC будет применять, когда запрашивается стандартный URL для прило жения. Инфраструктура MVC создаст экземпляр класса FakeRepository, после чего будет использовать его для создания нового объекта контроллера, обрабатывающего запрос. Фиктивное хранилище снабдит контроллер простыми тестовыми данными, которые его метод действия передаст представлению Razor, так что НТМL-ответ для браузера будет включать детали каждого товара. При генерации НТМL-ответа инфра структура МVС объединит данные из представления, выбранные методом действия. с содержимым разделяемой компоновки, порождая завершенный НТМL-документ, который браузер в состоянии разобрать и отобразить. Запустив приложение, можно увидеть результат, показанный на рис. 8. 7 . Это типовой шаблон разработки для инфраструктуры ASP.NET Core MVC. Начальные затраты времени на необходимую настройку являются обязательными. но затем базовые средства приложения будут собир аться очень быстро.
218 Часть 1. Введение в инфраструктуру ASP.NET Core MVC 1·. SportsStor~ Х 1 f- ' С [Ф lo~host:бOOOO --------===----==::::::=::=.:::=--_-:::::::::::.:.. -~ Football S25.00 JI Su1·f boa1·d Sl79.00 lR1шnlng slloes $95.00 - -- -- -- -- - ---- --- ---- --- ---- -·- --- ---- ---- Рис. 8.7. Просмотр основной функциональности приложения Подготовка базы данных Мы способны отображать простое представление, содержащее сведения о товарах, но применяем тестовые данные, которые находятся в фиктивном хранилище. Прежде чем можно будет реализовать хранилище с реальными данными, необходимо настро ить базу данных и заполнить ее данными. Для базы данных будет использоваться SQL Server, а доступ к ней будет осущест вляться с применением Entity Framework Core (EF Core) - инфраструктуры объект но-реляционного отображения (object-relational mapplng - ORM) для Microsoft .NET . Инфраструктура ОRМ представляет таблицы, столбцы и строки реляционной базы данных посредством обычных объектов С#. На заметку! Это область, где можно выбирать из широкого диапазона инструментальных средств и технологий. Доступны не только различные реляционные базы данных, но мож но также работать с хранилищами объектов, хранилищами документов и рядом экзотичес ких альтернатив. Кроме того, существуют другие инфраструктуры ORM для .NET, каждая из которых использует слегка отличающийся подход; такие вариации позволяют выбрать то, что лучше всего подойдет для ваших проектов. Инфраструктура Entity Framework Core применяется по нескольким причинам: ее просто запустить в работу, она прекрасно интегрирована с LINQ (мне нравится ис пользовать LINQ) и она хорошо работает с ASP.NET Core MVC. Ранним выпускам этой инфраструктуры были присущи мелкие недостатки, но текущие версии элегантны и богаты возможностями. Продукты Visual Studio и SQL Server располагают удобным средством LocalDB, ко торое представляет собой не требующую администрирования реализацию основной функциональности SQL Server, специально спроектированную для разработчиков. Благодаря этому средству можно пропускать процесс настройки базы данных на вре мя построения проекта , а развертывание проводить в полном экземпляре SQL Server позже. Большинство приложений MVC развертываются в размещаемых средах, ко торые обслуживаются профессиональными администраторами, и наличие средства Loca!DB означает, что конфигурирование баз данных остается в руках администрато ров баз данных, а разработчики приложений могут заниматься написанием кода.
Глава 8. SportsStore: реальное прило же ние 219 Совет. Если при установке Visual Studio вы не выбрали LocalDB , то придется сделать это сейчас. Средство представляет собой часть инструментов для работы с данными или же его можно установить как часть SQL Server. Установка Entity Framework Core Инфраструктура Entity Framework Core устанавливается с применением NuGet, а в листинге 8.15 показаны требуемые добавления в раздел dependencies файла proj ect . j son в проекте SportsStore. Листинг 8.15. Добавление Entity Framework Core в файле proj ect. j son внутри проекта SportsStore "dependencies ": )' " Microsoft . NETCore . App ": "version": "1.0 . 0 ", " type ": " platform " )' " Microsoft . AspNetCore.Diagnostics ": "1. 0 . 0 ", " Microsoft.AspNetCore . Server . I ISintegration": " 1 . 0 . 0 ", " Microsoft . AspNetCore .Server. Kestrel ": " 1.0 . 0 ", " Microsoft . Extensions . Logging . Con s ole ": "1. 0.0 ", " Microsoft . AspNetCore . Razor . Tools" : ( )' " version ": "l.0. 0 - preview2 -final ", " type ": " build" " Microsoft . AspNetCore . StaticFiles ": " 1. О. О ", " Microsoft . AspNetCore . Mvc ": "1. 0 .0", "Мicrosoft.EntityFrameworkCore. SqlServer" : "1. О. 0", "Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final" Базы данных управляются с использованием инструментов командной строки. ко торые настраиваются в разделе tools файла project . j son (листинг 8.16). Листинг 8.16. Регистрация инструментов EF Core в файле proj ect. j son внутри проекта SportsStore " tools ": )' " Microsoft . AspNetCore . Razor . Tools": "1. 0 . 0-preview2-final ", "Mi crosoft . AspNetCo re.Server. IISintegration.Tools ": "1. 0 .0 -preview2- final", "Мicrosoft . EntityFrameworkCore. Tools": { "version": "1.0 .0 -preview2-final", "imports": [ "portaЫe-net45+win8+dnxcore50", "portaЬle-net45+win8" ]
220 Часть 1. Введение в инфраструктуру ASP.NET Саге MVC После сохранения файла proj ect. j son среда Visual Studio загрузит и установит инфраструктуру EF Core, а также добавит ее в проект. Создание классов базы данных Класс контекста базы данных является шлюзом между приложением и EF Core, обеспечивая доступ к данным приложения с применением объектов моделей. Чтобы создать класс контекста базы данных для приложения SportsStore, добавьте в папку Models файл класса по имени ApplicationDbContext. cs с определением, приве денным в листинге 8 . 17. Листинг 8.17 . Содержимое файла ApplicationDЬContext. cs из папки Models using Microsoft .EntityFrameworkCore ; namespace SportsStore.Models { puЫic class Applicat i onDbContext : DbContext { puЫic ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base (options) {} puЫic DbSet<Product> Products { get; set; } Базовый класс DbContext предоставляет доступ к лежащей в основе функцио налыюсти Entity Fraшework Core, а свойство Products обеспечивает доступ к объек там Product в базе данных. Для наполнения базы данных добавьте в папку Models файл 1-шасса по имени SeedData. cs и поместите в него определение, показанное в листинге 8.18. Листинг 8.18. Содержимое файла SeedDa ta. cs из папки Model s using System .Linq; using Microsoft .AspNetCore.Builder; using Microsoft . Extensions .Dependencyinjection; namespace SportsStore.Models { puЫic static class SeedData { puЫic static void EnsurePopula ted(IApplicationBuilde r арр) ApplicationDbContex t context = app.ApplicationServices .GetRequiredService<ApplicationDbCon text>(); if (!context .Products .Any()) { context.Products.AddRange( new Product { Name = "Kayak", Description = "А boat for one person", Category = " Watersports ", Price = 275 }, new Product { Name = " Lifejacket ", Description = "Protect ive and fashionaЫe", Category = "Wat ersports ", Price = 48. 95m } , new Product { Name = "Soccer Ball ", Description = "FIFA-approved size and weight", Category = "Soccer", Price = 19. 50m },
Глава 8. SportsStore: реальное прило ж ение 221 new Product { Name = " Corne r Flags ", Description = "Give your pl aying field а professional touch", Categor y = " Soccer", Price = 34. 95m }, new Product { Name = " Stadium", Description = " Fl at- packed 35, 000 - seat stadi um ", Category = " Soccer", Price = 79500 }, new Product { Name = " Thinking С ар ", Description = "Improve brain ef ficien cy Ьу 75%", Category = "Chess", Price = 16 }, new Product { Name = "Unst eady Chair", Descri ption = "Secr etly give your oppone nt а disadvantage", Category = " Chess", Price = 29.95m }, new Product { Name = " Human Chess Board", Description = "А fun game for t he family", Category = "Chess", Price = 75 }, new Product { } Name = "Bling-Bling King", Des c r ipt ion = "Gold-plated, diamond-s t udd ed King", Ca tegory = "Che ss", Price = 1200 ); conte x t . SaveChanges (} ; Статический метод Ens ureP op ul ated () получает аргумент типа IApp licat i o n Bu i lder, который является нлассом, используемым в методе Con figure () нласса Startup при регистрации классов промежуточного программного обеспечения для обработки Н1ТР-запросов; именно здесь будет обеспечиваться наличие содержимого в базе данных. Метод The EnsurePopu l a t ed () получает объект Appl ica ti o n DbCon t ex t пос редством интерфейса IApp lica tionBu i lder и применяет его для проверки , при сутствуют ли в базе данных какие-нибудь объекты Produc t. Если объектов нет, то база данных наполняется с использованием коллекции объектов Product и метода AddRange () ,после чего сохраняется с помощью метода Sav eCha n ge s () . Создание класса хранилища Хотя в настоящий момент может показаться иначе , но большая часть работы, тре буемой }J)IЯ настройки базы данных , завершена. Следующий шаг заключается в со здании класса, который реализует интерфейс IProd u c t Reposi tory и получает дан ные с применением инфраструктуры Entity Framework Core. Добавьте в папку Mo d els файл класса по имени EFProductRe p os i to ry. cs с определением класса хранилища, представленным в листинге 8.19.
222 Часть 1. Введение в инфраструктуру ASP. NET Core MVC Листинг 6.19. Содержимое файла EFProductReposi tory. cs из папки Models using System. Collecti ons .Generic; n a mespace Spo rts Store . Models { puЫic clas s EFPr o d uctRepos i to r y : IProductRepos i tory { pr i vat e App licationDbContext context ; puЫic EFP r oductRepository(Appl icat i o n DbContext ctx) { context = ctx; puЫ i c IEnumeraЫe<Produc t > Produ ct s => context . Products ; По мере добавления ср едств к приложению ~тасс будет дополняться новой фун кциональностью, но пока что ре ализация хранилища просто отображает сво йство Products, определенно е в инт е рфейсе IPr odu ctRepo s i tory, на свойство Pro ducts , которое определено в классе Appl i cati onDbContext . Определение строки подключения Строка подключения указывает местоположени е и имя базы данных , а также предоставля ет конфигурационные настройки, с которыми приложени е долж но под ключаться к серверу базы данных. Строки подключ ения хранятся в файле JSON под названием apps etti n g s. j son, который создан в проекте SportsStor e с исполь зова ни ем шаблона элемента ASP.NET Configuration File (Конф и гурационный файл ASP.NET) из раздела ASP.NET диалогового окна Add New ltem. При создании файл а appset t ings . j son среда Visual Studlo добавля ет в н е го за полнитель для строки подключения, который необходимо привести в соотв етствие с ЛИСТИНГОМ 8 .20. Листинг 8.20. Редактирование строки подключения в файле appsettings. j son "Data": { " Spor tStoreProd ucts ": " Con n ectionStr i ng ": " Server=( l ocaldb)\\MSSQLLoca l DB; Database=SportsStore ; Trusted_Connection=True ; MultipleAct i v e ResultSets =true " } Вну три раздела Da ta конфигурационного файла имя строки подключ е ния уста навлива ется в SportsStoreProdu cts . Значени е элемента Conn ectionString ука зывает, что для базы данных по имени Sp ort s Store должно прим еняться ср едство Loca!DB . Совет. Строка подключения должна быть выражена в виде единственной неразрывной строки кода, что нормально для редактора Visual Studio , но не умещается на печатной странице и приводит к неуклю жему форматированию в листинге 8 .20 . При определе нии строки подключения в собственном проекте удостоверьтесь, что значение элемента Connect i onStri ng на ходится в единственной строке кода.
Глава 8. SportsStore : реальное прило ж ение 223 Конфигурирование приложения Дал ее понадобит ся прочитать строку подключения и сконфигурировать приложе ние для ее использования при подключении к базе данных. Для чтения строки под ключения из файла appsettings . j son требуется еще один пакет NuGet. В листин ге 8.21 показано изменение раздела dependencies внутри файле proj ect . j son . Листинг 8.21. Добавление пакета в файле project. json проекта SportsStore " dependencies ": }' " Microsoft . NETCore . App ": "version": "1.0 .0 ", " type ": " platform " }' " Microsoft . AspNetCore . Diagnostics ": "1. О . О ", "Microsoft .AspNetCore . Server .I ISintegrati on ": "1 . 0 .0", "Microsoft.AspNetCore . Server. Kestrel": "1 .О . О", "Microsoft.Extensions . Logging .Console": "1 .0 .0 ", " Microsof t. AspNetCore . Razo r. Tools ": { }' " version ": " l.0 .0 -preview2-final", " type" : "build" " Microsoft . AspNetCore. Sta t icFi l es " : "1. О. О ", " Microsoft . AspNetCore. Mv c": "1 .О.О", " Microsoft . EntityFramewor kCore . SqlServer": " 1.0 . 0 ", " Microsoft . EntityFrameworkCore . Tools ": " l . 0 . 0 - preview2 - final ", "Мicrosoft.Extensions.Configuration.Json": "1.0 .0" Этот пакет делает возможным чтени е конфигурационных данных из файлов JSON, таких как appsett i ngs . j son . Чтобы применить фун к циональность , предлагаемую новы м пакетом , для чтения строки подключения из конфигурационного файла и что бы настроить EF Core , в класс Startup н е обходимо внести соответствующее измене ние (листинг 8.22). Листинг 8.22 . Конфигурирование приложения в файле Startup. cs using Microsoft.AspNetCore . Builder ; using Mic r osoft . AspNetCore . Host i ng ; using Microsoft . AspNetCore . Http ; using Microsof t . Extensions . Dependencyinj ect i o n; using Microsoft . Extensions . Logging ; using Spor tsStore . Mode l s ; using Мicrosoft.Extensions.Configuration; using Мicrosoft . EntityFrameworkCore; namespace SportsStore { puЫic clas s Startup { IConfigurationRoot Configuration; puЬ1ic Startup(IHostingEnvironment env) { Configuration = new ConfigurationBuilder()
224 Часть 1. Введение в инфраструктуру ASP.NET Core MVC .SetBasePath(env.ContentRootPath) .Ad dJsonFile("appsettings.json") .Build(); puЫic void Conf igu reServices(IServiceCollect ion services) services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer( Configuration["Data:SportStoreProducts:ConnectionString"])); services.AddTransient<IProductRepository, EFProductRepository>(); services.AddMvc(); puЬlic void Configure(IApplication Builder арр , IHostingEnvironment env , ILogger F actory loggerFactory) { app . UseDeve l operExceptionPage() ; app.UseStatusCodePages(); app.UseStaticFiles(); app .U seMvc(routes => { routes.MapRoute( name : " default ", template : "{ controller = Product)/ {action =Li st}/{id?} " ) ; }); SeedData . EnsurePopulated(app); Добавленный в класс Startup конструктор загружает конфигурационные на стройки из файла appsettings . j s on и делает их доступными через свойство по имени Configuration. Особенности чтения и доступа к конфигурационным данным рассматриваются в главе 14. В метод ConfigureServices () добавл е на последовательность обращений к мето дам, которая настраивает инфраструктуру Entity Framework Core . services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration["Data:SportStoreProducts:ConnectionString"])); Расширяющий метод AddDbContext () настраивает службы, предоставляемые инфраструктурой Entity Framework Core для класса контекста базы данных. кото рый был создан в листинге 8.17. Как объяснялось в главе 14, многие методы, ис пользуемые в классе Startup, позволяют конфигурировать службы и средства про межуточного программного обеспечения с применением аргументов с параметр ами . Аргументом метода AddDbContext () является лямбда-выражение , которое получает объект options, конфигурирующий базу данных для класса контекста. В этом случае база данных конфигурируется с помощью метода UseSqlServer (} и указания стро ки подключения, которая получена из свойства Configuration. Еще одно изменение, внесенное в класс Startup, было связано с заменой фиктив ного хранилища реальным: services . AddTransient<IProductRep ository , EFProductRepository>();
Глава 8. SportsStore: реальное приложение 225 Компоненты в приложении, использующие интерфейс IProductRepository, к которым в настоящий момент относится только контроллер Product, при создании будут получать объект EFProductReposi tory, предоставляющий им доступ к инфор мации в базу данных. Подробные объяснения будут даны в главе 18, а пока просто знайте, что результатом будет гладкая замена фиктивных данных реальными из базы данных без необходимости в изменении класса ProductController. Финальное изменение в 1тассе Startup касается вызова метода SeedDa ta . EnsurePopul ated (). который гарантирует наличие в базе данных определенной тестовой информации и вызывается внутри метода Configure () класса Startup. При запуске приложения метод Startup. ConfigureServices () вызывается пе ред методом Startup . Configure (). т. е. ко времени вызова метода SeedData . EnsurePopulated () можно иметь уверенность в том, что службы Entity Framework Core уже установлены и сконфигурированы. Создание и применение миграции базы данных Инфраструктура Entity Framework Core способна генерировать схему для базы данных, используя классы моделей, с помощью средства, которое называется мигра циями. При подготовке миграции инфраструктура EF Core создает класс С#, содер жащий команды SQL, которые нужны для подготовки базы данных. Если необходимо модифицировать классы моделей, тогда вы можете создать новую миграцию, которая содержит команды SQL, требуемые для отражения изменений. Таким образом, вам не придется беспокоиться о написании вручную и тестировании команд SQL, и вы можете сосредоточиться на классах модели С# в приложении. Команды EF Core выполняются с применением консоли диспетчера пакетов (Package Manager Console), которая открывается через пункт меню Toolsq NuGet Package Manager (СервисqДиспетчер пакетов NuGet) в Visual Studio. Запустите в консоли диспетчера пакетов следующие команды, чтобы создать класс миграции, 1юторый подготовит базу данных к первому использованию: Add-Migration Initial Когда команда завершит свое выполнение, вы увидите в окне Solution Exp lorer сре ды Visual Studio папку Migrations. Именно в ней инфраструктура Entity Framework Core хранит классы миграции. Одно из имен файлов будет выглядеть как длинное число с _ Initial. cs после него, и это класс, который будет применяться для созда ния начальной схемы базы данных. Просмотрев содержимое такого файла , вы увиди те, как класс модели Product использовался для создания схемы. Запустите приведенную ниже команду, чтобы создать базу данных и выполнить команды миграции: Update-Database Создание базы данных займет какое-то время, но после завершения работы ко манды вы сможете увидеть результат, запустив приложение. Когда браузер запраши вает стандартный URL для приложения, !{онфигурация приложения сообщает МVС о необходимости создания контроллера Product для обработки запроса. Создание кон троллера Product означает вызов конструктора класса ProductController, которо му требуется объект, реализующий интерфейс I Р roduct Repo s i tor у, и новая конфи гурация указывает MVC о том, что для этого должен быть создан и применен объект EFProductReposi tory. Объект EFProductRep osi tory обращается к функциональ ности EF Core , которая загружает реляционные данные из SQL Server и преобразует
226 Часть 1. Введение в инфраструктуру ASP.NET Core MVC их в объекты Product. Вся упомянутая работа скрыта от класса ProductController, который просто получает объект, реализующий интерфейс IProductRepository, и пользуется данными, которые он предоставляет. В итоге окно браузера отображает тестовую информацию из базы данных (рис. 8.8). 1·. \po. - ts\to« х С @i-1oc_ a _ lh-os-t.600-0- = 0===- - - ·- -- !· -- . =-- --=::-=-=-=-----=---===--··--- 1~ ' 1 Kayak 1 А b~ar for оп< p<rso11 1 5275.00 1 LlfejAckN Prot~ Ct1\·~ n11d f(lsluo111\ЬI~ 1 1 S4S.95 1 \ SoccпBnll ' LElfЛ:<"1oe_ro ~Oя.z.~_fI04..!~~i~1t_________________________~-- · --- "' Рис. В.В. Использование хранилища в виде базы данных Такой подход с применением Entity Framework Core для представления базы дан ных SQL Server в виде последовательности объектов моделей отличается простотой и легкостью, позволяя сосредоточить все внимание на инфраструктуре ASP.NET Core MVC. Я опустил множество деталей , связанных с функционированием EF Core, и большое количество доступных конфигурационных параметров . Мне нравится инф раструктура Entity Framework Core, и я рекомендую уделить время на ее изучение. Хорошей отправной точкой послужит веб-сайт Microsoft для Entity Framework Core, находящийся по адресу http: / /ef. readthedocs. io . Добавление поддержки разбиения на страницы На рис. 8.8 видно, что представление List . cshtml отображает сведения о товарах в базе данных на одной странице. В этом разделе мы добавим поддержку разбиения на страницы, чтобы представление отображало на странице меньшее число товаров, а пользователь мог переходить со страницы на страницу для просмотра всего каталог. В метод действия List () контроллера Product необходимо добавить параметр, как показано в листинге 8.23. Листинг 8.23. Добавление поддержки разбиения на страницы в метод действия List () в файле ProductController. cs using Microsoft.AspNetCore . Mvc ; using SportsStore.Models ; using System.Linq; namespace SportsStore . Controllers puЬlic class ProductController : Controller private IProductRepository repository;
Глава 8. SportsStore : реальное прило же ние 227 puЬlic int PageSize = 4; puЫic ProductController(IProdu c t Repository repo) { repository = repo ; puЬlic ViewResult Li s t ( int page = 1) => View(repository.Products .OrderBy(p => p.ProductID) . Skip((page - 1) * PageSize) .Take(PageSize)) ; Поле PageSize указывает, что на одной странице должны отображаться сведения о четырех товарах. Позже мы заменим его более совершенным механизмом. В метод List () добавлен необязательный параметр. Это означает, что в случае вызова метода без параметра (List () )вызов обрабатывается так, словно ему было передано значение, указанное в определении параметра (Li st ( 1) ). В результате метод действия отобража ет первую страницу сведений о товарах, когда инфраструктура МVС вызьmает его без аргумента. Внутри тела метода действия мы получаем объекты Product, упорядочи ваем их по первичному ключу , пропускаем товары, которые распол агаются до начала текущей страницы, и выбираем количество товаров , указанное в пол е PageSi ze. Модульное тестирование: разбиение на страницы Модульное тестирование средства разбиения на страницы можно провести , создав имитированное х ранилище, внедрив его в конструктор класса ProductController и вызвав метод List () , чтобы запрашивать конкретную страницу. Затем полученные объе кты Product можно сравнить с теми, которые ожидались от тестовы х данны х в ими тированной реализации. Написание модульных тестов обсуждалось в главе 7. Ни же пока зан созданный для этой цели модульный тест, который находится в файле класса по имени ProductControl l erTes ts . cs, добавленном в проект Spor tsSt ore . Tests: using System.Collections . Generic ; using System . Linq ; using Moq ; using SportsStore.Controllers ; using SportsStore .M odels ; using Xuni t ; namespace SportsStore . Tests puЬlic class ProductControllerTests [Fact] puЫic void Can Paginate() 11 Организация Mock<IProductRepository> mock = new Mock<IProdu ctRepository>() ; mock . Setup(m => m. Products) .Returns(new Product[] { new Product {ProductID 1, Name "Pl"}, new Product new Product new Product new Product }); {ProductID = 2 , {ProductID = З , {ProductID 4 ' {ProductID = 5 , Name Name Name Name = "Р2"}, "РЗ"), "Р4"), " PS"} ProductController cont roller = new ProductController(mock . Object);
228 Часть 1. Введение в инфраструктуру ASP.NET Core MVC controller . PageSize = З ; 11 Действие IEnumeraЫe<Product> result controller .Li st(2) . ViewData . Mode l as IEnumeraЫe<Product> ; 11 Утверждение Product[] prodArray = result.ToArray(); Assert . True(prodArray .Length == 2); Assert.Equal("Р4", prodArray[О] .Name) ; Assert . Equal( " PS ", prodArray[l] . Name) ; Получение данных, возвращаемых из метода действия, выглядит несколько неуклюже. Результатом является объект ViewResul t, и значение его свойства ViewData . Model должно быть приведено к ожидаемому типу данных . В главе 17 рассматриваются разнооб разные результирующие типы, которые могут возвращать методы действий, а также спосо бы работы с ними. Отображение ссылок на страницы Запустив приложение, вы увидите, что теперь на странице отображаются четыр е позиции каталога. Если нужно просмотреть другую страницу, в конец URL можно до бавить параметр строки запроса, например: http : //localhost : 60000/ ?page=2 Вы должны указать в URL номер порта, который был назначен вашему проекту. Используя такие строки запросов, можно перемещаться по каталогу товаров. У пользователей нет какого-либо способа выяснить о существовании этих парамет ров строки запроса, но даже если бы он был , то вряд ли бы они захотели иметь дело с навигацией подобного вида. Взамен мы должны визуализировать в нижней части каждого списка товаров ссылки на страницы, чтобы пользователи могли переходить между страницами. Для этого мы реализуем дескрипторный вспомогательный класс, который будет генерировать НТМL-разметку для требуемых ссылок. Добавление модели представления Чтобы обеспечить поддержку дескрипторного вспомогательного класса, мы соби раемся передавать представлению информацию о количестве доступных страниц, текущей странице и общем числе товаров в хранилище. Проще всего это сделать, создав класс модели представления, который специально прим ен яется для пер еда чи данных между контроллером и представлением. Создайте в проекте SportsStore папку Models/ViewModels и добавьте в нее файл класса с содержимым, приведен ным в листинге 8.24 . Листинг 8.24. Содержимое файла Paginginfo. cs из папки Models/ViewModels using System ; namespace SportsStore . Models . ViewModels puЫic class Paginginfo { puЫic int Totalitems { get; set ; }
Глава 8. SportsStore : реальное прило ж ение 229 puЫic int ItemsPerPage { get ; s et ; } puЫic int CurrentPage ( get; set ; } puЫic int TotalPages = > (int)Math . Cei l ing((decima l )Tot alitems / I t e msPe r Page) ; Добавление дескрипторного вспомогательного класса Те п е рь , имея мод ел ь пр едставл е ния, можно создать дес1<рипторный вспомогатель ный класс . Создайте в проекте Sport sS tore папку I nfrastr u cture и добавьте в нее файл класса по им ени P ageLinkTagHel p er . cs с определением, показанным в лис тинг е 8.25. Де скрипторны е вспомогательные классы являются крупной частью инф раструктуры ASP.NET Core MVC, и в главах 23-25 объясняется , как они работают и каким образом их создавать. Совет. В папку Infrastructure будут помещаться классы , которые предоставляют прило жению связующий код, но не имеют отношения к предметной области приложения . Листинг 8.25. Содержимое файла PageLinkTagHelper. cs из папки Infrastructure using Microsoft . AspNetCore . Mvc ; using Microsoft . AspNetCore . Mvc .Rende r ing ; using Microsoft . Asp NetCo r e . Mvc.Rout ing ; using Microsoft . AspNetCore . Mvc . Vi e wFeatures ; u sing Microsoft . AspNetCore . Ra z or . Ta gHelp ers ; using SportsStore . Mode l s . ViewMode l s ; namespace SportsStore . Infrastructure [HtmlTargetElement( " div ", Attribu t es = " page -model " )] puЫic class PageLinkTagHelper : TagHe l per { pr i vate IUrlHelperFactory url Helpe rFactory ; puЫic PageLinkTagHelper(IUr l He l perFactory helperFactory) u rlHel perFactor y = help erFactor y; [ViewContext] [HtmlAttributeNotBound] puЬlic ViewContext ViewContex t { get ; set ; puЫic Paginginfo PageMode l { get ; set ; } puЫic string PageAction { get ; set ; } puЫic override v oid Process( TagHelp e r Co n t e xt co n text , TagHelperOutput output) ( IUrlHelper urlHelper = ur l HelperFactory.GetUrlHelper(ViewCont ext) ; TagBuilder res ul t = new TagBuilder( " div " ) ; for (int i = 1; i <= PageModel .Tot alPages; i++) { TagBuilder tag = new TagBuilder ( " а " ) ; tag .Attributes ["href"] = url Hel per .Action (PageAction, new { page = i }) ; tag . Inn erHtml .Append (i.ToSt ring() ); result . InnerHtml . AppendHtml(tag) ; output . Content . AppendHtml(re s u l t . InnerHtml ) ;
230 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Этот дескрипторный вспомогательный класс заполняет элемент di v элементами а , которые соответствуют страницам товаров. Сейчас мы не будем вдаваться в ка кие-либо детали относительно дескрипторных вспомогательных классов; достаточно знать, что они предлагают один из наиболее удобных способов помещения логики С# в представления. Код для дескрипторного вспомогательного класса может выглядеть запутанным, потому что смешивать С# и HTML непросто. Но использование деСJ{рип торных вспомогательных классов является предпочтительным способом включения блоков кода С# в представление, поскольку дескрипторный вспомогательный класс можно легко подвергать модульному тестированию. Большинство компонентов MVC, таких как контроллеры и представления , об наруживаются автоматически, но дескрипторные вспомогательные классы долж ны быть зарегистрированы. В листинге 8.26 приведено содержимое файла _ Viewirnports . cshtrn l из папки Views с добавленным оператором, который сооб щает MVC о том, что дескрипторные вспомогательные классы следует искать в про странстве имен SportsStore. Infrastructure. Кроме того, добавлено также вы ражение @using, чтобы на классы моделей представлений можно было ссылаться в представлениях, не указывая пространство имен . Листинг 8.26. Регистрация дескрипторного вспомоi-ательного класса в файле_Viewimports. cshtml @using SportsStore . Models @using SportsStore.Models.ViewModels @addTagHelper * , Microsoft . AspNetCore . Mvc . TagHelpers @addTagHelper SportsStore.Infrastructure .*, SportsStore Модульное тестирование: создание ссылок на страницы Чтобы протестировать дескрипторный вспомогательный класс PageLinkTagHelper, вызывается метод Process () с тестовыми данными и предоставляется объект TagHelperOutput , который инспектируется на предмет сгенерированной НТМL разметки . Тест определен в новом файле PageLinkTagHelperTests. cs внутри проекта SportsStore. Tests: using System.Collections.Generic; using System . Threading . Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore . Mvc . Routing; using Microsoft.AspNetCore . Razor . TagHelpers; using Moq; using SportsStore.Infrastructure; using SportsStore.Models . ViewModels ; using Xunit ; namespace SportsStore . Tests { puЫic class PageLinkTagHelperTests [Fact] puЬlic void Can_Generate Page Links() 11 Организация var urlHelper = new Mock< IUrlH e lper>() ;
Глава 8. SportsStore : реальное прило ж ение 231 urlHelper.SetupSequence(x => x . Action(It . IsAny<UrlActionContext>())) . Returns( 11 Test/Pagel 11 ) . Returns( 11 Test/Page2 11 ) . Returns( 11 Test/PageЗ 11 ); var urlHelperFactory = new Mock<IUrlHelperFactory>() ; urlHelperFactory . Setup(f => f . GetUrlHelper(It . IsAny<ActionContext>() )) . Returns(urlHelper.Object) ; PageLi nkTagHelper helper = new PageLinkTagHelper(urlHelperFactory . Object) PageModel = new Paginginf o { CurrentPage = 2 , Totalitems = 28 , I t emsPerPage = 10 }, PageAction = 11 Test 11 }; TagHelperContext ctx = new TagH e lperContext( new TagHelperAttributeList() , new Dictionary<object , object>(), 1111 ); var content = new Mock<TagHelperContent>() ; TagHelperOutput output = new TagHe l perOutput( 11 div 11 , new TagHelperAttributeList() , (cache , encoder) => Task.FromResult(content.Object)); 11 Действие helper . Process(ctx , output) ; 11 Утверждение Assert . Equal(@ 11 <a href= 11 " Test/Pagel "" >l</a> 11 + @11<а href= 1111 Test/Page2 1111 >2</a> 11 + @ 11 <а href= "" Test/Page3 1111 >З</a> ", output . Content . GetCon t ent() ) ; Сложность этого теста связана с созданием объектов, которые требуются для создания и применения дескрипторного вспомогательного класса . Дескрипторные вспомогательные классы используют объекты IUr lHelperFactory для генерации URL, которые указывают на разные части приложения, и для создания реализаций этого интерфейса и связанного с ним интерфейса IUrlHelper , предоставляющего тестовые данные , используется инф раструктура Moq. Основная часть теста проверяет вывод дескрипторного вспомогательного класса с приме нением литерального строкового значения, которое содержит двойные кавычки. Язык С# позволяет работать с такими строками при условии, что строка предварена символом @, а вместо одной двойной кавычки внутри строки используется набор из двух двойных кавычек ( 1111 ) . Вы должны помнить о том , что разносить литеральную строку по нескольким строкам файла нельзя , если только строка , с которой производится сравнение, не разнесена ана логичным образом . Например, литерал, применяемый в тестовом методе, был размещен в нескольки х строках из-за недостаточной ширины печатной страницы . Символ новой строки не добавлялся, иначе тест не прошел бы.
232 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC Добавление данных модели представления Мы пока не готовы использовать дес1(рипторный вспомогательный класс, т. к. пред ставление еще не снабжено экзе мпляром класса модели представления Paginginfo. Это можно было бы сделать с применением объекта Vi ewBag, но лучше пом е стить вс е данные , подлежащие пер едаче из контроллера в представление , внутрь единствен ного класса мод ели предст авления. Добавьте в папку Mode l s/ViewMo d e l s про е кта Sp ortsS t o r e ф айл класса по имени P r oductsListViewModel . cs с содержимым . приведенным в листинг е 8 .27. Листинг 8.27 . Содержимое файла ProductsListViewModel. cs из папки Models/ViewModels using System.Collections.Generic; using SportsStore.Model s; n a mespace SportsStore . Mode l s . Vi ewMode l s p uЫic c l ass Products ListViewModel { puЫic IEnumeraЫe<Product> Products get ; set ; } puЫic Paginginfo Paginginfo { get ; set; } Теперь можно обновить метод действия Li s t () кл асса Produc t Co n troller так. чтобы он использовал ю~асс Pr odu cts Li s tVi ewMo d el для снабжения представления сведениями о товарах, отображаемых на страницах. и информацией о разбиении на страницы (листинг 8.28). Листинг 8.28. Обновление метода действия List() в файле ProductController.cs using Mi crosoft . Asp NetCore . Mvc ; u sing SportsStore . Mode l s ; using System.Li nq; using SportsStore.Models.ViewModels; n amespace SportsSt ore . Cont roll ers { puЬlic cl ass ProductControll er : Controll er p rivate IProd u c t Repository repository ; puЫic int PageSize = 4; puЫic ProductController(I Produ ctReposit ory re p o) { r eposi tory = repo; puЫic ViewRes ult List(int page = 1) => View(new ProductsListViewModel Products = repository.Products . OrderBy(p => p.ProductID) . Skip ( (page - 1) * PageSize) . Take(PageSize), Paginginfo = new Paginginfo CurrentPage = page, ItemsPerPage = PageSize, } }); Totalitems = repository.Products . Count()
Глава 8. SportsStore : реальное прило ж ение 23 3 Внесенные изменения обеспечивают передачу представлению объекта Products ListViewModel как данных м одели . М одульное тестиро вание: данные разбиен и я н а страницы для модели пр едставления Нам необходимо удостовериться в то м , что контроллер отправляет представлению коррек тную инфор м ацию о разбиении на страницы . Ни же по казан модульный тест, добавленный в класс ProductControllerTests внутри тестового прое кта , который обеспечивает та кую проверку : [Fact] puЫic void Can_Send_ Pagination_View_Model() { 11 Организация Mock<IProductRepository> mock = new Mock<IProduc t Repository>() ; mock.Setup(m => m. Products) . Returns(new Product[] { new Product {ProductID 1 , Name "Pl"), new Product {Product ID 2, Name "Р2"} , new Product {ProductID new Pr oduct { Product I D new Product {Product I D )); 3, Name 4, Name 5, Name 11 Организация ProductControl l er controlle r = "РЗ"), "Р4"}, "Р5") new ProductCon troller(mock . Object) { PageSize 3); 11 Действие ProductsListVi ewModel result = controller . List(2) . ViewData.Model as Products ListViewModel ; 11 Утверждение Paginginfo pageinfo = result . Pagingin f o ; Assert . Equal(2 , pageinfo . CurrentPage) ; Assert . Equal(З , pageinfo .I temsPerPage) ; Assert . Equal(5 , pageinfo . Tota l items) ; Assert . Equal(2 , pageinfo.TotalPages) ; Потребуется также модифицировать ранее созданный модульный тест для проверки раз биен и я на страницы, содер жащийся в методе Can_Paginate () .Он полагается на метод действия List () , возвращающий объе кт ViewRe s ul t , свойством Model которого явля ется последовательность объе ктов Product, но мы поместили эти данные внутрь еще од ного типа м одели представления. Вот переделанный тест: [E'act ] puЫic void Can_Paginate() { 11 Организация Mock<IProductRepository> mock = new Mock<IProduct Repository>() ; mock . Setup(m => m. Products) . Re t urns(new Product[J { new Product {Product I D 1 , Name "Pl"), new Product { ProductID = 2 , Name = "Р 2 "),
234 Часть 1. Введение в инфраструктуру ASP.NET Core MVC new Product {ProductID = 3 , Name new Product {ProductID = 4 , Name new Product {ProductID = 5 , Name }); "Р3"}, "Р4"}, " PS"} ProductControl ler controller = new ProductController(mock . Object) ; controller.PageSize = 3 ; 11 Действие ProductsListViewModel result = controller.List(2) .ViewData.Model as ProductsListViewModel; //У твержде ние Product[] prodArray = result.Products.ToArray(); Assert . True(prodArray . Length == 2); As sert.Equa l("P4 ", prodArray(OJ . Name) ; Assert . Equal("P5 ", prodArray[l] . Name) ; Учитывая объем дублированного кода в двух тестовых методах, обычно следовало бы со здать общий метод начальной установки. Однако, поскольку модульные тесты описаны в индивидуальных врезках вроде этой, их код представлен по отдельности, чтобы с каждым тестом можно было ознакомиться независимо от других. В настоящий момент представление ожидает последовательность объектов Product , поэтому файл List . cshtml необходимо обновить, как показано в листин ге 8.29, чтобы иметь дело с новым типом модели представления. Листинг 8.29. Обновление файла List. cshtml @model ProductsListViewModel @foreach (var р in Model.Products) <div> <h3>@p .N ame</ h3> @p . Description <h4>@p . Price.ToStr ing( " c " )</h4> </div> Выражение @mo de l было изменено для указания механизму Razor на то, что те перь мы работаем с другим типом данных. Цикл foreach был обновлен, чтобы источ ником данных стало свойство Products объекта модели. Отображение ссылок на страницы Наконец, все готово для добавления в представление List ссылок на страницы. Мы создали модель представления, которая содержит информацию о разбиении на страницы. обновили контроллер, чтобы эта информация передавалась представле нию, и модифицировали выражение @model , приведя его в соответствие с новым типом модели представления . Осталось только добавить НТМL- элемент, который де скрипторный вспомогательный класс будет обрабатывать для создания ссылок на страницы (листинг 8.30).
Глава 8. SportsStore: реальное приложение 235 Листинг 8.30 . Добавление ссылок на страницы в файле List. cshtml @mode l Prod u cts ListVi ewMode l @foreach (var р i n Model .P r odu cts) <div> <h3>@p . Name</h3> @p . Description <h 4>@p . Pri ce .ToStri ng( "c" )</h4> </div> <div page-model="@Model.Paginginfo" page-action="List"></div> Запустив приложение, вы увидите новые ссылки на страницы (рис. 8.9). Стиль отображения довольно примитивен, но далее в главе он будет улучшен. Пока важно то, что ссыл ки позволяют перемещаться по страницам каталога и знакомиться с то в а рам и, выставл е нными на продажу. Когда механизм Razor обнаруживает атрибут page - model в элементе d i v , он обращается к классу PageLinkTagHelper для пр е образования элемента, что и дает набор ссьmок, показанных на рис. 8.9 . 1 Produш )( 1 Produщ Х 1 1 Products Х ... ,. . -- ------ ------ \ +- -' С D localhost:60000/'pag _ -- -· ·------ +- . . , С С1 localhost:бOOOO/?page•З '{;;1= · f- ----- +- -" С D locall1os t 60000/>page• ------------------ ! Koyok 1----~- -- '- --- -"' -' -- - Bll11g-Bli11g Кlug StadillШ 1 А boat fot онео pcr~o11 ! Gold -pla.tcd duiшoш1-snIOdcd Кшg 1 5275 • .ОО IFlat-packcd ЗS.000-~c<ct !iiti'ldшш SI ,20 0.00 1 1 S79 ,500 .00 1 Lifфckel lli Tbluki11g Сор j Prot ectt\'C' анd t"n11>!1ioщ1ЬJ 1 1 1 Iщр1·0 \·е bri'\bl cfficict1cy Ь S4S.9S j Slб.00 Soccr1· Bo ll 1 1 UnsleodY Chзi1· FIFA-npproп•d si z tшd \\'tigltt 1 . Stc rC'tly giп· ущ1r 1 Sl 9.SO j S29.95 Cш·nei·Fl " s 1 Huшnn Cl1 )ii'IY111? , fiC 'ld а prof~.<.:s tOJl~I to\j А fш1 gаш or tl1 e fa1шly 1 575 .00 -- ·-L :::J 1 -·- Рис. 8.9 . Отображение ссылок для навигации по страницам На заметку! Если вы запустите приложение с применением пункта меню Start Debuggiпg (Запустить отладку), то можете столкнуться с сообщением об ошибке, предупреждающим о том, что коллекция была модифицирована. Это дефект EF Core, который дол ж ен быть исправлен к моменту выхода настоящей книги, но если он все же останется, тогда про стая перезагрузка окна браузера решит проблему и приведет к отображению содержимо го, представленного на рис . 8.9 .
236 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Почему бы просто не воспользоваться элементом управления GridView? Если вы ранее работали с ASP.NET, то вам может показаться , что такой большой объем ра боты привел к довольно скромным результатам. Пришлось написать немало кода лишь для того, чтобы получить список товаров с разбиением на страницы. Если бы мы прибегли к услугам инфраструктуры Web Forms, то такого же результата можно было бы достичь с при менением готового элемента управления Gr i dV i ew или Li s tVi ew из ASP.NET Web Forms, привязав его напрямую к таблице Pr o d ucts базы данных. То, что было сделано в главе, выглядит не слишком впечатляюще, но серьезно отличается от процесса перетаскивания какого-то элемента управления на поверхность визуального проектирования. Во-первых, мы строим приложение с надежной и удобной в сопровожде нии архитектурой, которая задействует подходящее разделение обязанностей . В отличие от простейшего использования Lis t View мы не связываем напрямую пользовательский интерфейс и базу данных, что представляет собой подход, который дает быстрые резуль таты, но вызовет проблемы и сложности в будущем. Во-вторых, по ходу дела мы создаем модульные тесты, что позволяет проверять поведение приложения естественным образом, который практически невозможен в случае применения сложного элемента управления Web Forms . Наконец, в-третьих, не забывайте, что значительная часть главы посвящена созда нию инфраструктуры, на основе которой строится приложение. Например, определить и реализовать хранилище нужно только один раз, и теперь, когда оно имеется в нашем рас поряжении, новые функциональные средства можно строить и тестировать легко и быстро, как будет продемонстрировано в последующих главах. Разумеется, ничто из сказанного отнюдь не умаляет значимость безотлагательных результа тов, которые способна предложить инфраструктура Web Forms, но, как объяснялось в главе 3, такая безотлагательность имеет свою цену, которая может оказаться высокой и болезнен ной в крупных и сложных проектах. Улучшение URL Ссылки на страницы работают, но для передачи информации серверу они по-пре жнему используют строку запроса, подобную следующей: http : //localhost/?page=2 Чтобы получить бол е е привлекательные URL, необходимо создать схему. которая следует шаблону компонуемых URL. Компонуемый URL - это URL, имеющий с мысл для пользователя, такой как показанный ниже: http : // l ocalhost/Page2 Инфрастру1пура МVС позволяет легко изменять схему URL в приложении, потому что применяет средство мapшpymuзa4uuASP. NET, которо е отвеча ет за обработку URL для выяснения, на какую часть приложения они указывают. Понадобится лишь доба вить новый маршрут при регистрации промежуточного программного обеспеч ения МVС в методе Configure () класса Start up (листинг 8.31). Листинг 8.31. Добавление нового маршрута в файле Startup. cs puЫic void Confi gur e(IAppl icationBuil der арр, I HostingEnvir onment env , ILogg e r Factory l oggerFactor y) { app .UseDevel operExcept ionPage(); app .U seStatusCod ePages() ;
app.UseSta ticFi l es() ; app .U seMvc(routes => { routes .мapRoute ( name: "pagination", template: "Products/Page{page}", Глава 8. SportsStore : реальное приложение 237 defaults: new { Controller = "Product", action = "List" }) ; routes.MapRoute( name: "default", template : " {control l er=Product}/{act ion=List}/{id?} " ) ; }); SeedData.EnsurePopulated(app); Важно поместить этот маршрут перед стандартным маршрутом (по имени defaul t ). который уже присутствует в методе. Как будет показано в главе 15 , система маршрутизации обрабатывает маршруты в порядке их перечисления, а нам нужно , чтобы новый маршрут имел преимущество перед существующим. Это единственное изменение, которое потребуется внести, чтобы изменить схему URL для разбиения на страницы списка товаров. Инфраструктура MVC тесно интег рирована с функцией маршрутизации, поэтому приложение автоматически отражает изменение подобного рода в URL, используемых приложением , что касается и URL, которые генерируются дескрипторными вспомогательными классами вроде применя емых для гене рации ссылок на страницы . Не беспокойтесь, если маршрутизация пока не особенно понятна - она будет подробно рассматриваться в главах 15 и 16. Запустив приложение и щелкнув на ссылке для какой-нибудь страницы, вы уви дите новую схему URL в действии (рис. 8.10). Sta(liШll Flat-pnc ke<I 35.000 -seat stacl iщ11 1 ~"0.00 . ~· ~ " ....,. !11! .... ,.,,,....- ~..-..;...,..., _"'-___,,,. Рис . 8.10. Новая схема URL , отображаемая в браузере Стилизация содержимого Мы построили значительную часть инфраструктуры и базовые средства прило жения готовы к сборке всего воедино, но пока совершенно не уделяли внимание его внешнему виду. Хотя дан ная книга не посвящена веб-дизайну или CSS, внешний вид приложения SportsStore настолько примитивен, что это умаляет его технические до стоинства. В настоящем разделе мы постараемся исправить ситуацию. Мы собираем ся реализовать классическую компоновку, содержащую два столбца и заголовок, как показано на рис. 8.11 .
238 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC Home • Product 1 ·• Watersports • Product 2 • Socce.r • • Chess , (main body) •... .,,· Рис. 8. 11 . Целевой дизайн для приложения SportsStore Установка пакета Bootstrap Для предоставления стилей CSS, которые будут применены к приложению, мы планируем использовать пакет Bootstrap. При установке пакета Bootstrap мы будем полагаться на встроенную в Visual Studio поддержку инструмента Bower. Выберите шаблон элемента Bower Configuration File (Файл конфигурации Bower) из категории Client-side (Клиентская сторона) в диалоговом окне Add New ltem , чтобы создать в проекте SportsStore файл по имени bower. j son, как демонстрировалось в главе 6. Добавьте в раздел dependencies созданного файла пакет Bootstrap, как показано в листинге 8.32. Листинг 8.32. Добавление пакета Bootstrap в файле bower. j son внутри проекта SportsStore " name ": " asp.net", "private": true, "dependencie s": { "bootstrap": "3. 3. 6 11 После сохранения внесенных в файл bower. j son изменений среда Visual Studio применяет инструмент Bower для загрузки пакета Bootstrap в папку wwwroot/liЬ/ bootstrap. Пакет Bootstrap зависит от пакетаjQuегу. который также автоматически добавится в проект. Применение стилей Bootstrap к компоновке В главе 5 было показано, как работают представления Razor, как они используют ся и как встраивать в них компоновки. Файл запуска представления, добавленный в начале главы , указывает, что файл по имени_Layout. cshtrnl должен выступать в качестве стандартной компоновки, так что начальную стилизацию Bootstrap мы при меним именно к нему (листинг 8.33).
Глава 8. SportsStore : реальное приложение 239 Листинг 8.33. Применение СSS-файла Bootstrap к файлу _Layout. cshtml из папки Views/Shared <!DOCTYPE htrnl> <htrnl> <head> <rneta narne ="viewport " content= " width=devi ce - width " /> <link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.min.css" /> <title>Spo rtsStore </t itle> </head> <body> <div class="navbar navbar-inverse" role="navigation"> <а class="navbar-brand" href="#">SPORTS STORE</a> </div> <div class="row panel"> <div id="categories" class="col-xs -3"> Put something useful here later </div> <div class="col-xs-8"> @RenderBody () </div> </div> </body> </html> Элемент l in k имеет атрибут asp - href - include, который представляет собой пример и спользования встроенного дескрипторного вспомогательного класса. В дан ном случае дескрипторный вспомогательный класс просматривает значение атрибута и генерирует элементы link для всех файлов по указанному пути, который может содержать групповы е символы. Это удобное средство гарантирования того, что вы можете добавлять и удалять файлы из структуры папок wwwroot , не нарушая работу приложения , но , как объясняется в главе 25, указание групповых символов требует особого внимания , чтобы они соответствовали нужным файлам . Добавление в компоновку таблицы стилей CSS из Bootstrap означает, что оп ределенные в ней стили можно применять в любом представлении, которое пола га ется на данную компоновку. В листинге 8.34 стилизация используется в файле List . cshtml. Листинг 8.34. Стилизация содержимого в файле List. cshtml @model Products ListViewMod e l @foreach (var р in Model . Products) <div class="well"> <hЗ> <strong>@p.Name</strong> <span class="pull-right laЬel laЬel-primary"> @р. Price. ToString ("с") </span> </hЗ> <span class="lead">@p.Description</span> </div>
240 Часть 1. Введение в инфраструктуру ASP.NET Core MVC <div page-model="@Model.Paginginfo" page-action="List" page-classes-enabled="true" page-class="Ьtn" page-class-normal="ьtn-default" page-class-selected= 11 Ьtn-primary 11 class= 11 Ьtn-group pull-right"> </div> Нам необходимо стилизовать кнопки, которые генерирует класс PageLin kTagHe 1ре r , но жестко встраивать классы Bootstrap в код нежелательно , т.к. это затруднит пов торное использование дескрипторного вспомогательного класса где-то в другом месте приложения либо изменение внешнего вида кнопок. Взамен мы определяем специ альные атрибуты в элементе di v, которые указывают требуемые классы. Данные ат рибуты соответствуют свойствам, добавленным в дескрипторный вспомогательный класс, которые затем применяются для стилизации создаваемых эл ементов а (лис тинг 8.35). Листинг 8.35. Добавление классов к генерируемым элементам в файле PageLinkTagHelper.cs using Microsoft . AspNetCore . Mvc ; using Microsoft . AspNetCore . Mvc.Rendering ; using Microsoft . AspNetCore . Mvc . Routing ; using Microsoft.AspNetCore.Mvc . ViewFeatures; using Microsoft . AspNetCore . Razor .T agHe l pers ; using SportsStore.Models . ViewMode ls; namespace SportsStore.Infrastructure [HtmlTargetElement("div", Attributes = " page -model " )] puЫic c l ass Page LinkTagHe l per : TagHelper { private IUrlHelperFactory ur lH e lperFactory ; puЫic PageLinkTagHelper(IUrlHelperFactory helperFactory) urlHelperFactory = he lperFact ory ; [ViewContext] [HtmlAttributeNotBound] puЬlic ViewContext ViewCo n text { get ; set ; puЫic Paginginfo PageModel { get; set ; puЫic string PageAction { get ; set; } puЫic Ьооl PageClassesEnaЬled { get; set; puЬlic string PageClass { get; set; } puЬlic string PageClassNormal { get; set; } puЬlic string PageClassSelected { get; set; false; puЫic override void Process(TagHelperContext con t ext , TagHelperOutput output) { IUrlHelper urlHelper = urlHelperFactory.GetUrlHelper(ViewContext); TagBuilder result = new TagBuilder("div"); for (int i = 1 ; i <= PageModel . TotalPages ; i++) { TagBuilder tag = new TagBuilder("a"); tag.Attributes[ " href "J = ur lHelper.Action(PageAction , new{page= i});
Глава 8. SportsStore: реальное nриложение 241 if (PageClassesEnaЫed) { tag.AddCssClass(PageClass); tag.AddCssClass(i == PageМodel.CurrentPage ? PageClassSelected : PageClassNormal) ; tag .Inne r Html. Ap p end(i .T oS tring () ); r esul t .InnerHtml .AppendHtml(tag ) ; o utput. Co n tent . Appe ndHtml (result.In n erHtml); Значения атрибутов автоматически используются для установки значений свойств в дескрипторном вспомогательном классе, учитывая отображение между форматом имени атрибута HTML (p age - clas s-norma l) и форматом имени свойства С# (PageClassNormal) . Это позволяет дескрипторным вспомогательным классам по разному реагировать в зависимости от атрибутов НТМL-элемента, обеспечивая более гибкий способ генерации содержимого в приложении МVС. Запустив приложение , вы увидите , что его внешний вид улучшился - во всяком случае, в како й-то степени (рис. 8.12). !) Spo~•Stolf х с Cc?..~~.:i~·_:~~Qip;;,~~![P,1g~~--=~-=.=-===·===·=:~~-~J SPOПTS STOHE . jPut something uselul jt1 eie later Blin g-Bl ing Кing Gold·pl ated, diamond -studded Кing 1 1 2 ll l· ~Щ···-~~~~~~~~!~':~. ' Рис. 8.12. Приложение SportsStore с улучшенным дизайном Создание части чного представления В качестве завершающего штриха в данной главе мы проведем рефакторинг при ложения , чтобы упростить пр едставление Li s t . c s html. Мы создадим частичное представление, являющееся фрагментом содержимого , которое можно внедрять в другое представление подобно шаблону. Частичные представления подробно рас сматриваются в главе 21. Они помогают сократить дублирование , когда одно и то же содержимое должно появляться в разных местах приложения. Вместо того чтобы ко пировать и вставлять одинаковую разметку Razor во множество представлений, мож но определить ее единожды в частичном представлении. Чтобы создать частичное представление, добавьте в папку Vi ews/Sh a red файл представления Razor по имени Produ ctSumm ary . cs h tml и поместите в него разметку, показанную в листинге 8 .36 .
242 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Листинг 8.36 . Содержимое файла ProductSummary. cshtml из папки Views/Shared @model Product <div class= " well " > <hЗ> <strong>@Model.Name</strong> <span c l ass= " pull-right label label - primary"> @Model . Price . ToString( "c" ) </span> </hЗ> <span class="lead " >@Model . Description</span> </div> Теперь необходимо модифицировать файл List. csh tml из папки Views/ Products, чтобы в нем применялось частичное представление (листинг 8 .37). Листинг 8.37. Использование частичного представления в файле List. cshtml @model ProductsListViewModel @foreach (var р in Model . Products) @Html. Partial {"ProductSununary", р) <div page - model="@Model . Paginginfo" page-action="List " page - classes-enaЬled="true " page-class="Ьtn " page - class -normal = "Ьtn- default " page-class - selected= " Ьtn - primary"class="Ьtn-group pull-right " > </div> Мы взяли код разметки, который ранее размещался в цикле foreach в представ лении List. cshtml, и перенесли его в новое частичное представление. Обращение к частичному представлению производится с помощью вспомогательного метода Html . Partial () , которому в аргументах передаются имя представления и объект модели представления . Подобного рода переход на частичное представление является рекомендуемым приемом, поскольку он позволяет вставлять одну и ту же разметку в любое представление, которое нуждается в отображении сводки о товаре. Как видно на рис. 8 . 13, добавление частичного представления не изменяет внешний вид при ложения; оно просто меняет место, где механизм Razor находит содержимое, которое применяет для генерации ответа, отправляемого браузеру. Резюме В настоящей главе была построена основная инфраструктура для приложения SportsStore. Пока что она не содержит достаточного набора функциональных средств, чтобы их прямо сейчас можно было продемонстрировать пользователю , но "за кули сами" уже имеются зачатки модели предметной области с хранилищем товаров , кото рое поддерживается SQL Server и Entity Framework Core. В приложении присутствует единственный контроллер ProductController, который может создавать разбитые на страницы списки товаров, а также настроена ясная и дружественная к пользо ва телям схема URL.
Глава 8. SportsStore: реальное nриложение 243 С Spo1tsStore Х ~ с i_~-'~::'h_?:~6:_~~p:~~~?~~9~~~~~-~~-~==~===~~---------~_:_--=_~- -= ~~ .::€J SPORTS SТOilE · 1 IPut sornething LJseful lhere later Kayak А boat ror 0 11е person Рис. 8.13. Использование частичного представления В этой главе могло показаться , что для получения весьма незначительных выгод пришлось провести излишне трудоемкую начальную подготовку, но в следующей главе равновесие будет восстановлено. Теперь, когда фундаментальная структура на месте, можно двигаться дальше и добавлять все средства, ориентированные на поль зователя: навигацию по категориям, корзину для покупок и начальную форму для оплаты.
ГЛАВА 9 SportsStore: навигация в этой главе мы продолжим построение примера приложения SportsStore. В пре дыдущей главе была добавлена поддержка для навигации по приложению и для начала создания корзины для покупок. Добавление навигационных элементов управления Приложение SportsStore станет намного удобнее , если пользователи получат воз можность навигации среди товаров по категории. Это будет делаться в три этапа. • Расширение метода действия List () в классе ProductControl l er , чтобы он был способен фильтровать объекты Product в хранилище. • Пересмотр и расширение схемы URL. • Создание списка иатегорий. иоторый будет находиться в боковой панели сай та, подсвечивая текущую категорию и предоставляя ссылки на остальные категории. Фильтрация списка товаров Мы начнем с расширения класса модели представления ProductsListViewModel, который был добавлен в проект Spo rtsS tore в предыдущей глав е . Нам нужно обес печить взаимодействие текущей категории с представлением, чтобы визуализировать боковую панель. и это хорошая отправная точка. В листинге 9.1 показаны изменения, внесенные в файл ProductsListViewModel . cs из папки Models/ViewModels . Листинг 9.1. Добавление свойства в файле ProductsListViewModel. cs using System.Collections .G eneric ; using SportsStore.Models ; namespace SportsStore . Models .V iewModels puЫic class ProductsListViewModel { } puЫic IEnumeraЬle<Product> Products get ; set ; } puЫ i c Paginginfo Paginginfo { get; set ; puЫic string CurrentCategory { get; set; } В класс ProductsListViewModel добавлено свойство по имени CurrentCategory. Следующий шаг заключается в обновлении класса ProductController, чтобы метод
Глава 9. SportsStore: навигация 245 действия List () фильтровал объекты Produ ct по к атегории и использовал только что добавленное в модель представления свойство дл я указания категории, выбран ной в текущи й момент. Изменения приведены в листинге 9.2. Листинг 9.2 . Добавление поддержки категорий к методу действия List () в файле ProductController. cs using Microsoft . AspNetCore . Mvc ; using SportsStore . Models ; using System . Linq ; us i ng SportsStore . Models . ViewMode l s ; namespace SportsStore . Co ntrolle r s ( puЫic class ProductController : Contro l ler private IProductRepository repo s itory ; puЫic int PageSize = 4 ; puЫic ProductControl l er(IProductRep ository repo) { repository = repo ; puЫic ViewResult List(string category, int page = 1) => View(new Prod uctsListVi e wMo de l { Products = repository . Products . Where (р => са tegory == null 1 1 р. Са tegory == са tegory) . OrderBy(p => p . Produc t ID) . Skip( (page - 1) * PageSi ze) . Take ( PageSize) , Paginginfo = new Pagingi nfo Current Page = page, ItemsPerPage = PageSize , Total i t ems = reposit ory. Products. Count() }' CurrentCategory = category }); В этот метод действия вн есены три изменения . Первое - добавлен новый пара метр по им е ни category. Он применяет ся вторым изменением , которое представ ляет собой расширение з апроса LINQ. Если значение category не равно null , тогд а выбираются только объекты P r o du ct с соответствующим значени е м в свойс тве Category. Последнее, третье , изменение касается устан овки значения свойс тв а Cur r entCategory, которое было добавлено в класс ProductsListViewModel . Однако в результате таких изменений значение Paginglnfo . Totalltems вычи сля ется некорректно , потому что оно не принимает во внимани е фильтр по категории. Со временем мы все исправим . Модульное тестирование: обновление существующих модульных тестов Мы изменили сигнатуру метода действия List () , поэтому некоторые существующие ме тоды модульного тестирования перестали компилироваться . Чтобы решить возникшую про блему, в модульных тестах, которые работают с контроллером, методу действия Li s t ()
246 Часть 1. Введение в инфраструктуру ASP.NET Core MVC понадобится передавать в первом параметре значение null. Например, раздел действия тестового метода Can_ Paginate () в файле ProductControllerTests . cs дол же н выглядеть следующим образом : [Fact] puЫic void Can_Paginate() 11 Организация Mock<IProductRepository> mock = new Mock<IProductRepository>() ; mock.Setup(m => m.Products) .Returns(new Product[] new Product {ProductID = 1, Name Pl} , new Product {ProductID 2, Name Р2} , new Product {ProductID 3 , Name РЗ}, new Product {ProductID 4 , Name Р4} , new Product {ProductID 5 , Name Р5) }); ProductController controller controller . PageSize = 3 ; 11 Действие ProductsListViewModel result new ProductController(mock . Object) ; controller.List(null, 2) .ViewData.Model as ProductsListViewModel; 11 Утверждение Product[J prodArray = result . Products .T oArray(); Assert.True(prodArray.Length == 2) ; Assert . Equal(P4 , prodArray[O ] .Name); Assert.Equal(P5 , prodArray[l] .Name) ; Указывая null для category, мы получаем все объекты Product, которые контрол лер извлекает из хранилища, что воспроизводит ситуацию перед добавлением ново го параметра. Такого же рода изменение необходимо внести также в тестовый метод Can_Send_Pagination_View_Model(): [ Fact] p u Ыic void Can Send Pagination View Model() { 11 Организация Mock<IProductRepository> mock = new Mock<IProductRepository>(); mock . Setup(m => m.Products) .Returns(new Product[ ] { new Product {ProductID 1 , Name Pl} , new Product {ProductID 2 , Name Р2}, new Product {ProductID З , Name РЗ} , new Product {ProductID 4 , Name Р4} , new Product {ProductID 5 , Name Р5} }); 11 Организация ProductController controller = new ProductController(mock . Object) { PageSize 3 }; 11 Действие ProductsListViewModel result = controller.List(null, 2) .ViewData.Model as ProductsListViewModel;
11 Утверждение Paginginfo pageinfo = resu lt. Paging l nfo ; Assert . Equal(2, pageinfo . CurrentPage) ; Assert.Equal(З , pageinfo.ItemsPerPage) ; Assert . Equal(5 , pageinfo .T otalitems ) ; Assert . Equal(2 , pageinfo .T ota l Pages) ; Глава 9. SportsStore: навигация 247 Когда вы примете образ мышления, ориентированный на тестирование, поддержание мо дульных тестов в синхронизированном состоянии с изменениями кода быстро станет вашей второй натурой. Чтобы увидеть результат фильтрации по категории , запустите приложение и вы берите категорию с помощью показанной ниже строки запроса, заменив номер порта тем, который был назначен вашему проекту средой Visual Studio (позаботившись о том, что Soccer начинается с прописной буквы S): http://localhost : 60000/?category=Soccer Вы увидите только товары из категории Soccer (рис. 9 . 1) . Очевидно, что пользователи не захотят переходить по категориям с прим енением URL , но здесь было показано, что незначительные изменения в приложении МVС мо гут оказывать крупное влияние, если базовая структура на месте. Put something us eful here later Soccer Ball FIFA-approved size and VJeight Corner Flags Give your playing field а professlonal touch Stadium Flat-packed 35,000 -seat stadium 11" j Рис. 9.1. Использование строки запроса для фильтрации по категории
248 Часть 1. Введение в инфраструктуру ASP.NET Core MV C Модульное тестирование: фильтрациs~ по категории ------- Нам необходим модульный тес т для проверки функциональности фильтрации по кате гории, чтобы удостовериться в том , что фильтр может корректно генерировать сведения о товарах указ анной категории. Ниже приведен тестовый метод, добавленный в класс ProductControllerTests: [Fact] puЬlic void Can Filter Products() { 11 Ор ган изация - создание имитирова н ного хранилища Mock<IProductRepository> mock = new Mock<IProductRepository>() ; mock . Setup(m => m.Products) .Returns(new Product[] { new Product {ProductID 1 , Name Pl , Catego r y = Catl} , new Produ ct {ProductID 2 , Name Р2, Category Cat2} , new Product {Prod uctID 3 , Na me РЗ, Category = Catl}, new Product {ProductID 4, Name Р4 , Category = Cat2} , new Product {ProductID 5 , Name Р5 , Category = СаtЗ} }); 11 Организация - создание контроллера и установка размера // страницы в три элемента ProductController controller = new ProductController(mock . Ob ject} ; controller . PageSize = З ; 11 Действие Product[ ] resu l t = (control l er . List(Cat2, 1) . ViewData .M odel as ProductsListViewModel) . Products.ToArray(); //Утверждение Assert . Equal(2 , result.Length) ; Assert . True(result[O] . Name Р2 && result[O] . Category Assert . True(result[l ] . Name == Р4 && result[l] . Category Cat2); Cat2); Этот тест создает имитированное хранилище, содержащее объекты Product , которые от носятся к диапазону категорий . С использованием метода действия List () запрашивается одна специфическая категория, а результаты проверяются на предмет наличия корректных объектов в пр авильном поряд ке. Улучшение схемы URL Мало кому понравится видеть либо иметь дело с неуклюжими URL вроде /?category=Soccer . Для реш ения данной проблемы мы намерены изменить схему маршрутизации в методе Configure () класса Startup, чтобы создать более удоб ный набор URL (листинг 9.3) . Внимание ! Новые маршруты в листинге 9 .3 ва ж но добавлять в показанном порядке . Маршруты при меняются в поряд ке, в котором они определены, поэтому изменение по рядка может привести к нежелательным эффектам.
Глава 9 . SportsStore : навигация 249 Листинг 9.3 . Изменение схемы маршрутизации в файле Startup. cs puЬlic void Configure(IApplicationBuilder арр , IHostingEnvironment env , IL ogger Fa ctory l oggerFactory) { app.Use De ve l operExceptionPag e() ; app . UseStatusCodePages() ; app.UseStaticFiles() ; app . UseMvc(routes = > { routes .МapRoute ( name: null, template: {category}/Page{page:int}, defaults: new { controller = Product, action = List } ); routes.MapRoute( name: null, template: Page{page:int}, defaul ts: new { controller = Product, action = List, page = 1 } ); routes.МapRoute( name: null, template: {category}, defaults: new { controller = Product, action = List, page = 1 } ); routes .MapRoute ( name: null, template: , defaul.ts : new { controller = Product, action = List, page = 1 } ); routes.МapRoute(name: null, template: {controller}/{action}/{id?}); )); SeedData . EnsurePopu l ated(app) ; В табл. 9 . 1 описана схема URL, которую представляют эти маршруты. Система маршрутизации подробно объясняется в главах 15 и 16. Таблица 9.1. Сводка по маршрутам URL / /Page2 /Soccer /Soccer/Page2 Что делает Выводит первую стран и цу списка товаров всех категорий Выводит указанную страницу (в данном случае страницу 2 ), отображая товары всех категорий Выводит первую страницу товаров указанной категории (Soccer) Выводит указанную страницу (страницу 2 ) товаров заданной категории (Soccer)
250 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Система маршрутизации ASP.NET используется инфраструктурой MVC для об работки входящих запросов от пользователей, но также генерирует исходящие URL, которые соответствуют схеме URL и потому могут встраиваться в веб-страницы. Применение системы маршрутизации для обработки входящих запросов и генерации исходящих URL позволяет гарантировать согласованность всех URL в приложении. Интерфейс IUrlHelper предоставляет доступ к функциональности генерации URL. Мы использовали этот интерфейс и определяемый им метод Action () в де скрипторном вспомогательном классе, созданном в предыдущей главе. Теперь. когда нужно генерировать более сложные URL, необходим способ получения дополнитель ной информации от представления. не добавляя дополнительные свойства к дескрип торному вспомогательному классу. К счастью, дескрипторные вспомогательные клас сы обладают удобной возможностью, которая позволяет получать в одной коллекции все свойства с общим префиксом (листинг 9.4). Листинг 9.4. Получение значений атрибутов, снабженных префиксом, в файле PageLinkTagHelper. cs using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; using SportsStore.Models.ViewModels; using System.Collections.Generic; namespace SportsStore.Infrastructure [HtmlTargetElement(div, Attributes = page-model)] puЫic class PageLinkTagHelper : TagHelper { private IUrlHelperFactory urlHelperFactory; puЫic PageLinkTagHelper(IUrlHelperFactory helperFactory) urlHelperFactory = helperFactory; [ViewContext] [HtmlAttributeNotBound] puЫic ViewContext ViewContext { get; set; puЫic Paginginfo PageModel { get; set; puЫic string PageAction { get; set; ) [HtmlAttributeName(DictionaryAttributePrefix = page-url-)] puЬlic Dictionary<string, object> PageUrlValues { get; set; = new Dictionary<string, object>(); puЫic bool PageClassesEnaЫed { get; set; } = false; puЬlic string PageClass { get; set; } puЫic string PageClassNormal { get; set; } puЬlic string PageClassSelected { get; set; puЬlic override void Process(TagHelperContext context, TagHelperOutput output) { IUrlHelper urlHelper = urlHelperFactory.GetUrlHelper(ViewContext); TagBuilder result = new TagBuilder(div); for (int i = l; i <= PageModel.TotalPages; i++) { TagBuilder tag = new TagBuilder(a);
Глава 9. SportsStore: навигация 251 PageUrlValues[page] = i; tag.Attributes[href] = urlHelper.Action(PageAction, PageUrlValues); i f ( PageClas sesEnaЬled) { tag . AddCssClass(PageClass); tag . AddCssClass(i == PageModel.CurrentPage ? PageCl assSe l ected : PageClassNormal); tag.InnerHtml . Append(i .T oString()); result .InnerHtml . AppendHtml(tag); output . Content . AppendHtml(result .I nnerHtml); Декорирование свойства дескрипторного вспомогательного класса посредс твом атрибута HtmlAttributeName позволяет указывать префикс для имен атри бутов элемента, которым в данном случае будет page - url-. Значение любого ат рибута, имя которого начинается с такого префикса, будет добавлено в словарь, присваиваемый свойству Р age Ur 1Vа1 ue s . Затем это свойство передается методу IUrlHelper . Action () с целью генерации URL для атрибута href элементов а , ко торые производит дескрипторный вспомогательный класс. В листинге 9.5 к элементу d i v добавлен новый атрибут, который обрабатывается дескрипторным вспомогательным классом и указывает категорию, применяемую для генерации URL. В представление был добавлен только один атрибут, но в словарь до бавился бы любой атрибут с тем же самым префиксом. Листинг 9.5 . Добавление нового атрибута в файле List. cshtml @mode l ProductsListViewModel @foreach (var р in Mode l. Products) @Html . Partial(ProductSummary , р) <div page-mode l =@Mode l.Paginginfo page - action=List page-clas ses-enaЫed=true page-class=btn page - class -no rmal=btn - defau lt page-class-selected=btn-primarypage-url-category=@Model.CurrentCategory class=btn -group pu ll- right> </div> До внесения этого изменения ссылки для перехода на страницы генерировались так: http : //<cepвep> : <пopт>/Page l Если пользователь щелкнет на страничной ссылке подобного типа, то фильтр по категории утрачивается и приложение отобразит страницу, содержащую товары всех категорий. За счет добавления текущей категории, получаемой из модели представления, вза мен генерируются URL следующего вида: http : //<cepвep> : <пopт>/Chess/Pagel
252 Часть 1. Введение в инфраструктуру ASP. NEТ Соге MVC Когда пользователь щелкает на ссьmке такого рода, текущая категория передается методу действия List () и фильтрация сохраняется. После внесения данного измене ния можно посещать URL вроде /Chess или /Soccer и наблюдать, что страничные ссылки, расположенные внизу, корре1пно включают категорию. Построение меню навигации по категориям Нам необходимо предложить пользователям способ выбора категории, который не предусматривает ввод URL. Это означает, что мы должны отобразить список доступ ных категорий с отмеченной текущей 1<атегорией, если она есть. По мере построения приложения список категорий будет задействован в более чем одном контроллере, по этому он должен быть самодостаточным и многократно используемым. В инфраструктуре ASP.NET Core MVC поддерживается концепция компонентов представлений, которые идеально подходят для создания единиц вроде много1<ратно используемого навигационного элемента управления. Компонент представления - это 1шасс С#, который предоставляет небольшой объем многократно используемой приrтадной логики с возможностью выбора и отображения частичных представле ний Razor. Компоненты представлений подробно рассматриваются в главе 22. В данном случае мы создадим компонент представления, который визуализиру ет навигационное меню и интегрирует его в приложение за счет обращения к этому компоненту из разделяемой компоновки. Такой подход дает нам обычный класс С#, который может содержать любую необходимую прикладную логику и который можно подвергать модульному тестированию подобно любому другому классу. Это удобный способ создания небольших сегментов приложения, сохраняя общий подход МVС. Создание навигационного компонента представления Создайте папку по имени Cornponents , которая по соглашению является местом хранения компонентов представлений, и добавьте в нее файл класса под названием NavigationMenuViewCornponent. cs с определением, показанным в листинге 9.6. Листинг 9.6. Содержимое файла NavigationМenuViewComponen t. cs из папки Components using Microsoft.AspNetCore . Mvc ; namespace SportsStore . Components puЫic class NavigationMenuViewComponent : ViewComponent puЫic string Invoke() { re tu rn Hello from the Nav View Component ; Метод Invoke () компонента представления вызывается, когда компонент приме няется в представлении Razor. а результат, возвращаемый методом Invoke (), встав ляется в НТМL-разметку, отправляемую браузеру. Мы начали с простого компонента представления, который возвращает строку, но вскоре заменим его динамическим НТМL-содержимым. Список категорий должен присутствовать на всех страницах, поэтому мы собира емся использовать компонент пр едставления в разделяемой компоновке. а не в отде-
Глава 9. SportsStore: навигация 253 льном представлении. Внутри компоновки компоненты представлений применяются через выражение @awai t Component . InvokeAsync (),как показано в листинге 9.7 . Листинг 9.7. Использование компонента представления в файле _Layout.cshtml < ! DOCTYP E html> <html> <head> <meta name=viewport content=wi dth=device-width /> <l ink r el=styleshe et asp -hr ef - include=liЬ/bootstrap/dist/css/* . min . css /> <title>SportsStore</title> </head> <body> <div class=navbar navbar-inverse role=navigation> <а class =navbar -brand href=#>SPORTS STORE</a> </div> <div class=row panel> <div id=categories clas s=col -x s-3> @await Component.InvokeAsync(NavigationМenu) </div> <div class=col - xs -8> @Render Body () </div> </div> </body> </html> Текст заполнителя заменен вызовом метода Component . InvokeAsync () . Аргументом этого метода является имя класса компонента без части ViewComponent, т.е. с помощью NavigationMenu указывается класс NavigationMenuViewComponent . Запустив приложение, вы увидите, что вывод из метода InvokeAsync () включен в НТМL-разметку, отправляемую браузеру (рис. 9.2). SPORTS STORE 1 Hello from the NavView Component Kayak А boat fог one person lfifiфl Рис . 9.2 . Применение компонента представления
254 Часть 1. Введение в инфраструктуру ASP.NET Core MVC Генерация списков категорий Теперь можно возвратиться к навиг