Вы находитесь на странице: 1из 994

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, Berkeley, СА.
Authorized translatioп from the Eпglish laпguage editioп puЬlished Ьу APress, !пс " Copyright
© 2016 Ьу Adam Free maп.
All rights reserved. No part of this work тау Ье reproduced or traпsmitted lп апу form or Ьу апу
meaпs, electroпic or mechaпical , lпcludiпg photocopylпg, recordlng, or Ьу any lnformatioп storage
or retrieval 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 the п 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 (рус.) © Компьютерное издательство "Диалентина", 2017
ISBN 978-1-484-20398-9 (англ.) © Ьу 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. Использование других встроенных дескрипторных
вспомогательных классов 766
Глава 26. Привязка моделей 793
Глава 27. Проверка достоверности моделей 828
Глава 28. Введение в ASP.NET Core ldeпtity 861
Глава 29. Применение ASP.NET Core ldentity 898
Глава 30. Расширенные средства ASP.NET Core ldentity 926
Глава 31. Соглашения по модели и ограничения действий 958

Предметный указатель 987


Содержание

Об авторе 18

Часть 1. Введение в инфраструктуру ASP.NET Core MVC 19

Глава 1. Основы ASP.NET Core MVC 20


История развития ASP. NЕТ Core МVС 20
ASP.NEТWeb Foгms 21
Первоначальная инфраструктура МVС Fгamewoгk 22
Обзор ASP.NET Core 23
Основные преимущества ASP.NET Core МVС 24
Архитектура МVС 24
Расширяемость 24
Жесткий контроль над HTML и НТТР 25
Тестируемость 25
Мощная система маршрутизации 25
Современный АРI-интерфейс 26
Межплатформенная природа 26
Инфраструктура ASP. NEТ Core МVС имеет открытый код 27
Что необходимо знать? 27
Канава структура книги? 27
Часть I. Введение в инфраструктуру ASP.NEТ Соге МVС 27
Часть П . Подробные сведения об инфраструктуре ASP.NEТ Core МVС 28
Что нового в этом издании? 28
Iде можно получить код примеров? 28
Резюме 28

Глава 2. Ваше первое приложение MVC 29


Установка Visual Studio 29
Создание нового проекта ASP. NЕТ Саге МVС 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 -условной операции и операции объединения с nu ll 89
Использование автоматически реализуемых свойств 90
Использование инициализаторов автоматически реализуемых свойств 91
Со здание автоматически реализуемых свойств только для чтения 92
Использование интерполяции строк 93
Использование инициализаторов объектов и коллекций 94
Использование инициализатора индексированной коллекции 96
Использование расширяющих методов 97
Применение расширяющих методов к интерфейсу 98
Создание фильтрующих расширяющих методов l 00
Использование лямбда- выражений 101
Определени е функций l 03
Использование методов и свойств в форме лямбда-выражений 105
Использование автоматического выведения типа и анонимных типов l 07
Использование анонимных типов l 08
Использование асинхронных методов l 09
Работа с задачами напрямую 110
Применение ключевых слов a sync и awa i t 111
Получение имен 113
Резюме 114

Глава 5. Работа с Razor 115


Подготовка проекта для примера 116
Определение модели 11 7
Создание контроллера 11 7
Создание представления 118
Работа с объектом модели 119
Использование файла импортирования представлений 121
Работа с компоновками 123
Создание компоновки 123
8 Содержание

Применение компоновки 125


Использование файла запуска представления 126
Использование выражений Razor 128
Вставка значений данных 129
Установка значений атрибутов 130
Использование условных операторов 131
Проход по содержимому массивов и коллекций 133
Резюме 135

Глава 6. Работа с Visual Studio 136


Подготовка проекта для примера 137
Создание модели 137
Создание контроллера и представления 139
Управление программными пакетами 141
Инструмент NuGet 141
Инструмент Bower 143
Итеративная разработка 147
Внесение изменений в представления Razoг 147
Внесение изменений в классы С# 148
Использование средства Вгоwsег Link 156
Подготовка файлов JavaScript и CSS для развертывания 161
Включение доставки статического содержимого 161
Добавление в прое1tт статического содержимого 162
Обновление представления 164
Пакетирование и минификация в приложениях МVС 166
Резюме 169

Глава 7. Модульное тестирование приложений MVC 170


Подготовка проекта для примера 171
Включение встроенных дескрипторных вспомогательных классов 171
Добавление действий к контроллеру 172
Создание формы для ввода данных 172
Обновление представления Index 173
Модульное тестирование приложений МVС 174
Создание проекта модульного тестирования 175
Изолирование компонентов для модульного тестирования 182
Улучшение модульных тестов 190
Параметризация модульного теста 190
Улучшение фиктивных реализаций 194
Резюме 200

Глава 8. SportsStore: реальное приложение 201


Начало работы 202
Создание проекта МVС 202
Создание проекга модульного тестирования 207
Проверка и запуск приложения 209
Начало работы с моделью предметной области 209
Создание хранилища 210
Создание фиктивного хранилища 210
Регистрация службы хранилища 211
Содер жа ние 9
Отображение списка товаров 212
Добавление контроллера 213
Добавление и конфигурирование представления 215
Установка стандартного маршрута 216
Запуск приложения 217
Подготовка базы данных 218
Установка Entity Fгarnework Core 219
Создание классов базы данных 220
Создание юшсса хранилища 221
Определение строки подключения 222
Конфигурирование приложения 223
Создание и применение миграции базы данных 225
Добавление поддержки разбиения на страницы 226
Отображение ссьmок на страницы 228
Улучшение URL 236
Стилизация содержимого 237
Установка пакета Bootstrap 238
Применение стилей Bootstrap к компоновке 238
Создание частичного представления 241
Резюме 242

Глава 9. SportsStore: навигация 244


Добавление навигационных элементов управления 244
Фильтрация списка товаров 244
Улучшение схемы URL 248
Построение меню навигации по категориям 252
Корректировка счетчика страниц 259
Построение корзины для покупок 262
Определение модели корзины 262
Создание кнопок добавления в корзину 266
Включение поддержки сеансов 267
Реализация контроллера для корзины 269
Отображение содержимого корзины 272
Резюме 275

Глава 1О. SportsStore: завершение построения корзины для покупок 276


Усовершенствование модели корзины с помощью службы 276
Создание класса корзины, осведомленного о хранилище 276
Регистрация службы 277
Упрощение контроллера Cart 278
Завершение функциональности корзины 279
Удаление элементов из корзины 279
Добавление виджета с итоговой информацией по корзине 281
Отправка заказов 284
Создание класса модели 284
Добавление реализации процесса оплаты 285
Реализация обработки заказов 287
Завершение построения контроллера Order 291
Отображение сообщений об ошибках проверки достоверности 295
Отображение итоговой страницы 296
Резюме 297
1О Содержание

Глава 11. SportsStore: администрирование 298


Управление заказами 298
Расширение модели 298
Добавление действий и представления 299
Добавление средств управления каталогом 302
Создание контроллера CRUD 303
Реализация представления списка 304
Редактирование сведений о товарах 306
Создание новых товаров 320
Удаление товаров 321
Резюме 324
Глава 12. SportsStore: защита и развертывание 325
Защита средств администрирования 325
Добавление в проект пакета Jdentity 325
Создание базы данных Identity 326
Применение базовой политики авторизации 330
Создание контроллера Accoun t и представлений 332
Тестирование политики безопасности 336
Развертывание приложения 336
Создание баз данных 337
Подготовка приложения 338
Применение миграций баз данных 343
Процесс развертывания приложения 343
Резюме 346
Глава 13. Работа с Visual Studio Code 348
Настройка среды разработки 348
Установка Node.js 349
Проверка установки Node 350
Установка Git 350
Проверка установки Git 351
УстановкаYeoman, Bower и Gulp 351
Установка .NETCore 351
Проверка установки .NЕТ Core 352
Установr\а Visual Studio Code 353
Проверка установки Visual Studio Code 353
Установка расширения С# для Visual Studio Code 353
Создание проекта ASP. NET Core 355
Подготовка проекта с помощью Visual Studio Code 356
Добавление в проект пакетов NuGet 357
Добавление в проект пакетов клиентской стороны 358
Конфигурирование приложения 359
Построение и запуск проекта 359
Воссоздание приложения Partylnvites 360
Создание модели и хранилища 360
Создание базы данных 363
Создание контроллеров и представлений 365
Модульное тестирование в Visual Studio Code 369
Конфигурирование приложения 369
Создание модульного теста 371
Прогон тестов 371
Резюме 372
Содержание 11

Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 373

Глава 14. Конфигурирование приложений 374


Подготовка проекта для примера 376
Конфигурационные файлы JSON 377
Конфигурирование решения 378
Конфигурирование проекта 380
Класс Prograrn 383
Класс Startup 385
Особенности использования класса Star tup 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. Дополнительные возможности маршрутизации 465


Подготовка проекта для примера 467
Генерация исходящих URL в представлениях 468
генерирование исходящих ссылок 468
ГенерацияURL (без ссылок) 478
генерирование URL в методах действий 478
Настройка системы маршрутизации 479
Изменение конфигурации системы маршрутизации 479
Создание специального класса маршрута 481
Применение специального класса маршрута 484
Маршрутизация на контроллеры МVС 485
Работа с областями 491
Создание области 492
Создание маршрута для области 492
Заполнение области 493
Генерирование ссылок на действия в областях 496
Полезные советы относительно схемы URL 497
Делайте URL чистыми и понятными человеку 497
GET и POST: выбор правильного запроса 499
Резюме 499
Глава 17. Контроллеры и действия 500
Подготовка проекта для примера 501
Подготовка представлений 503
Поняти е контроллеров 505
Создание контроллеров 506
Создание контроллеров РОСО 506
Использование базового класса Controller 508
Получение данных контекста 509
Получение данных из объектов контекста 510
Использование параметров метода действия 514
генерирование ответа 516
Генерирование ответа с использованием объекта контекста 516
Понятие результатов действий 517
Генерирование НТМL-ответа 520
Передач а данных из метода действия в представление 523
Выполнение перенаправления 528
Возвращение разных типов содержимого 535
Реагирование с помощью содержимого файлов 538
Возвращение ошибок и кодов Н1ТР 540
Другие классы результатов действий 542
Резюме 542
Глава 18. Внедрение зависимостей 543
Подготовка проекта для примера 544
Создание модели и хранилища 545
Создание контроллера и представления 547
Создание проекта модульного тестирования 548
Создание слабо связанных компонентов 549
Исследование сильно связанных компонентов 550
Содержание 13
Введение в средство внедрения зависимостей ASP. NET 556
Подготовка к внедрению зависимостей 556
I\онфигурирование поставщика служб 558
Модульное тестирование контроллера с зависимостью 560
Использование цепочек зависимостей 560
Использование внедрения зависимостей для конкретных типов 563
Жизненные ЦИIUIЫ служб 565
Использование переходного жизненного цикла 566
Использование жизненного цикла, ограниченного областью действия 570
Использование жизненного цикла одиночки 571
Использование внедрения в действия 572
Использование атрибутов внедрения в свойства 573
Запрашивание объекта реализации вручную 57 4
Резюме 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
Создание контролле ра и представлений 61 7
Конфигурирование приложения 619
Роль контроллеров REST 621
Проблема скорости 621
Проблема эффективности 622
Проблема открытости 622
14 Содержание

Введение в RESТ и контроллеры АР! 623


Создание контроллера АР! 623
Тестирование контроллера АР! 628
Использование контроллера АР! в браузере 631
Форматирование содержимого 634
Стандартная политика содержимого 635
Согласование содержимого 636
Указание формата данных для действия 639
Получение формата данных из маршрута или строки запроса 639
Включение полного согласования содержимого 641
Получение разных форматов данных 643
Резюме 643

Глава 21. Представления 644


Подготовка проекта для примера 645
Создание специального механизма визуализации 647
Создание специальной реализации интерфейса I View 649
Создание реализации интерфейса IViewEngine 650
Регистрация специального механизма визуализации 651
Тестирование механизма визуализации 652
Работа с механизмом Razor 654
Подготовка проекта для примера 654
Прояснение представлений Razor 656
Добавление динамического содержимого к представлению Razor 660
Использование разделов компоновки 661
Использование частичных представлений 666
Добавление содержимого JSON в представления 669
Конфигурирование механизма Razor 671
Расширители местоположений представлений 672
Резюме 677

Глава 22. Компоненты представлений 678


Подготовка проекта для примера 679
Создание моделей и хранилиш 680
Создание контроллера и представлений 682
Конфигурирование приложения 685
Понятие компонентов представлений 686
Создание компонента представления 686
Создание компонентов представлений РОСО 686
Наследование от базового класса ViewComponent 688
Понятие результатов компонентов представлений 690
Получение данных контекста 695
Создание асинхронных компонентов представлений 701
Создание гибридных компонентов контроллеров/представлений 703
Создание гибридных представлений 704
Прим енение гибридного класса 706
Резюме 707
Глава 23. Дескрипторные вспомогательные классы 708
Подготовка проекта для примера 709
Создание модели и хранилища 710
Содержание 15
Создание контроллера, компоновки и представлений 711
Конфигурирование приложения 713
Создание дескрипторного вспомогательного класса 715
Определение дескрипторного вспомогательного класса 715
Регистрация дескрипторных вспомогательных классов 719
Использование дескрипторного вспомогательного класса 719
Управление областью действия дескрипторного вспомогательного класса 721
Усовершенствованные возможности дескрипторных вспомогательных классов 726
Создание сокращающих элементов 726
Вставка содержимого перед и после элементов 728
Получение данных контекста представления и использование внедрения
зависимостей 732
Работа с моделью представления 734
Согласование дескрипторных вспомогательных классов 736
Подавление выходного элемента 738
Резюме 740

Глава 24. Использование дескрипторных вспомогательных классов для форм 7 41


Подготовка проекта для примера 7 43
Изменение регистрации дескрипторных вспомогательных классов 7 43
Переустановка представлений и компоновки 7 43
Работа с элементами f о rrn 7 45
Установка цели формы 7 46
Использование средства противодействия подделке 7 46
Работа с элементами inpu t 7 49
Конфигурирование элементов inpu t 7 49
Форматирование значений данных 751
Работа с элементами label 754
Работа с элементами select и option 756
Использование источника данных для заполнения элемента se lect 758
генерирование элементов option из перечисления 758
Работа с элементами textarea 763
Дескрипторные вспомогательные классы для проверки достоверности форм 764
Резюме 765

Глава 25. Использование других встроенных дескрипторных


вспомогательных классов 766
Подготовка проекта для примера 767
Использование вспомогательного класса для среды размещения 768
Использование вспомогательных классов для JavaScript и CSS 769
Управление файлами JavaScript 769
Управление таблицами стилей CSS 778
Работа с якорными элементами 781
Работа с элементами i rng 782
Использование кеша данных 783
Установка времени истечения для кеша 786
Использование вариаций кеша 788
Использование URL, относительных к приложению 789
Резюме 792
16 Содержание

Глава 26. Привязка моделей 793


Подготов:ка прое:кта для примера 794
Создание модели и хранилища 795
Создание контроллера и представления 796
Конфигурирование приложения 798
Понятие привязки моделей 799
Стандартные значения привязки 80 l
Привязка простых типов 802
Привязка сложных типов 803
Привязка массивов и коллекций 813
Привязка коллекций сложных типов 817
Указание источника данных привяз:ки моделей 820
Выбор стандартного источника данных привяз:ки 820
Использование заголовков в качестве источников данных привязки 82 1
Использование тел запросов в качестве источни:ков данных привязки 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
Содержание 17
Завершение средств администрирования 890
Реализация средства удаления 891
Реализация возможности редантирования 892
Резюме 897

Глава 29. Применение ASP.NET Core ldentity 898


Подготовка проекта для примера 898
Аутентификация пользователей 899
Подготовка к реализации аутентификации 901
Добавление аутентификации пользователей 904
Тестирование аутентификации 907
Авторизация пользователей с помощью ролей 908
Создание и удаление ролей 909
Управление членством в ролях 914
Использование ролей для авторизации 919
Помещение в базу данных начальных данных 923
Резюме 925

Глава 30. Расширенные средства ASP.NET Core ldentity 926


Подготовка проекта для примера 927
Добавление специальных свойств в класс пользователя 927
Подготовка миграции базы данных 931
Тестирование специальных свойств 931
Работа с заявками и политиками 932
Понятие заявок 933
Создание заявок 937
Использование политю< 940
Использование политик для авторизации доступа к ресурсам 946
Использование сторонней аутентификации 951
Регистрация приложения в Google 951
Включение аутентификации Google 952
Резюме 957

Глава 31. Соглашения по модели и ограничения действий 958


Подготовка проекта для примера 959
Создание модели представления, контроллера и представления 960
Использование модели приложения и соглашений по модели 962
Модель приложения 963
Роль соглашений по модели 967
Создание соглашения по модели 968
Порядок вьmолнения соглашений по модели 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
Developer for .NET), сертифицированным Microsoft профессионалом (Microsoft Certifled
Professional), а также плодовитым автором и техническим рецензентом. За посл едние
десять лет он написал множество статей для итальянских и м еждународных журна­
лов и выступал в качестве соавтора в более чем 10 книгах по разнообразным темам ,
связанным с 1щмпьютерами.

Ждем ваших отзы вов!


Вы, читатель этой книги , и есть главный ее критик. Мы ценим ваше мнение и хо­
тим знать, что было сдел ано нами правильно, что можно было сделать лучше и что
еще вы хотели бы увидеть изданным нами. Нам интересны любые ваши замечания в
наш адрес.

Мы ждем ваших комментариев и надеемся на них . Вы можете прислать нам бу­


мажное или электронное письмо либо просто посетить наш веб-сайт и оставить свои
замечания там. Одним словом, любым удобным для вас способом дайте нам знать ,
нравится ли вам эта книга, а также выскажите свое мнение о том , как сделать наши

книги более интересными для вас.


Отпр авляя письмо или сообщение, не забудьте указать название книги и ее авто­
ров , а та кже свой обратный адрес. Мы внимательно ознакомимся с вашим мнением и
обязательно учтем его при отборе и подготовке к изданию новых книг.
Наши эл е ктронные адреса:

E-mail: info@dialektika.com
WWW: http : //www . dialektika . com

Наши почтовые адр е са:

в России: 195027, Санкт-Петербург, Магнитогорская ул., д. 30 , ящик 116


в Украине: 03150, Киев, а/я 152
ЧАСТЬ 1

Введение
в инфраструктуру
ASP.NET Core МVС

д ля разработчиковASP.NET
веб-приложений , использующих платформу Microsoft,
инфраструктура Core MVC представляется как радикальное изме­
нен и е . Особое значение придается чистой архитектуре, паттернам проектирова­
ния и удобству тестирования , к тому же не предпринимается попытка скрывать,
каким образом работает веб-среда.

Первая часть этой книги поможет понять в общих чертах основополагающие


идеи разработки приложений MVC, включая новые возможности ASP. NЕТ Core
MVC, и увидеть на деле, как применяется инфраструктура .
ГЛАВА 1
Основы
ASP.NET Core МVС

A SP.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 Framework, расширялись с целью поддержки Web Forms, и предпринимались до­
полнительные проектные ухищрения, чтобы подогнать все друг к другу. Одновременно
в Microsoft начали расширять ASP.NET новыми инфраструктурами для создания веб­
служб (Web АР!) и организации коммуникаций в реальном времени (SignalR) . Новые
инфраструктуры добавили собственные соглашения по конфигурированию и разра­
ботке, каждое из которых обладало своими преимуществами и причудами, породив в
результате фрагментированную смесь.

Обзор ASP.NET Core


В 20 15 году Microsoft заявила о новом направлении для ASP.NEТ и MVC Framework,
которое в итоге привело к появлению инфраструктуры 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.NET Core MVC

Адреса URL, подобные приведенному ниже:

/App_v2/U ser/Page . as p x?ac t io n=show%2 0p rop&prop id= 8 2742


встречаются все реже, а на смену им приходит более простой и понятный формат
следующего вида:

/ t o -rent/chica 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-интерфейс может в полной мере задействовать последние новшества языка
и исполняющей среды. знакомые программистам на С# , в том числе ключевое слово
awa 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 это обновление
применяется автоматически. Оно доступно для загрузки по адресу;

h t t p: //go . mic r os o ft . com/fw l ink/? Lin ki d= 69 1 129


Далее потребуется загрузить и установить платформу .NET Core, устано­
вочный файл которой находится по адресу h ttps : //go . mic r osoft .c o m/
fwli n k /? Link i d = Bl 72 45 . Загружаемый установочный файл .NET Core обязателен
даже для новых установок Visual Studio.
30 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC

Финальный шаг предусматривает установку инструмента под названием gi t , до­


ступного для загрузки по адресу h ttp s : / / gi t -scm. c om/ d own l o a d . Продукт Visual
Studio включает собственную версию g i t , но она не работает должным образом и
приводит к получению непредсказуемых результатов, когда используется другими

инструментами. в том числе Bower, который будет описан в главе 6. Во время уста­
новки gi t удостоверьтесь, что указываете программе установки на необходимость до­
бавления пути к инструменту в перем енную среды РА ТН (рис. 2. 1). Это позволит ср еде
Visual Studio найти новую версию gi t .

~~~-------------~-----------~
...., Git 2.9.0 Setup

Adjustin9 your РАТН t!nvironment


Но•и \\'OIJd you likr to ~ Glt IТom the cOl'f'VМnd line?

------- -------------------
О UR Glt from Git Sash 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 Git from the Windows Con1m• nd Prompt


Т№ optfon is ~s.sfe asltonlyadds some minirмl Gitwr~ to YQU"
РАТН to avold ciJttering YotX ~t ·дi th optiornlf t.ni.X tools. You wil Ье
.Ы. tD use Git fтom Ьoth Git в..t1 ш! lhe 1'Г111dом Coomand Prompt.

О Use Git ;)hd optional UnЬc tools from the Windows Command Pron1pt
В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 if VoU 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 NSTALLD I R) \We b \External \ gi t , чтобы блокировать з апуск встроенной в
Visual Studio версии g it, и проверьте , отмечен ли флажок$ (РА Т Н ), чтобы мог при­
м еняться только что установленный инструмент gi t .

!
1
SeatckOpьc.-is(Ctrf•E)

~?~:~c·:~ons
.Р lootions of cttrn<tl toot~:
-------,
~@;JwG
1

:B~u.":~l.d;•:in~dR:~un:; ng'i
1
1 &') S(РАТН]
1 ! 0 S(VSJNSTдt lDIR)\Wt b\Exte 1мf\gil
i
1
1

:, 1!
А Prci)tcts • nd Solutions
G1:nu1I
.NЕТ Соц~ P1oje<ts
i
1 '·------------------------........)1
1 !>tt<Nf\'/ф 1ook'
VSOtf;щ lts
! Re5tttoDl!f.щtts j

VC •• Oi rcc l o rit.~ The p•th~ listed obtwewШ Ье s.t11rched when V'кu.гl Studio U1U 3rd·(nrtytcols such IJ
1 VC• ~ P1oj«t Sdtings G1un~ Gulp, 8owtr, cr npm.

•...::.~ P<Oj«~
, _
-···- -~· -·-~· ---·- ... ------ -· · - · - ------- .. -~· ··-··-

L _________ - - - ~-о_к_~I Г~~~ _J ;

Рис. 2.2. Конфигурирование запуска gi 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 ~"!_ЕТ Fr"m~;~ :б~1 - _: . Sort Ьу: \ Dtf-;~ij_--_-- -·-~. ") i~~ :::: ~мchi~7~tit!d "Тtmplal F:j_Ctrl ... E) ~~-!: .-;
i• lnstall<d
@j A5P.NПWe.bApp1icгtion{.NE1 fr~me.work) VisualC• Туре: Visual (#
! • Template~ Project ttmplat6 for crtating ASP.N П
Core "pplic<1tions for Windows, l inux and
... Visual(#
OS Х щing .NE1 Core.

w.ь @ A.SP.NП Core Wtb Applit•tion (.NП fr"mework) Vт~ua/ с~ ' AppfКAtion lnslghts
.NПCore О Add Appf1c<1t1on lnsights to projкt
Android Optimi:tt ptrformt!11Ct 4nd monitor
Cloud us<1Qt in vour li<.•e "PPlit"tion.
bll!ns!Ьil1ty
.os
Reporting
S1fvttlight
Help me unde.r~t""d Applkation lns19hts
Click h(r~ 10 по onl•"( ond f1t'!d 1wRIJ.tg. P11\'iicySt<11ll!.merit
f> Online

Name: P artylnvitм
1
location: ВloWft". 1

l Solutiori ntn,,e:

l-------~~,~=~~
P.-trty!nvite.~ 0 Cre.111e. dlltctory for.solutiol'\
О Add to Source Contro1

,~~~~~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 (Имя) для нового проекта введите Pa r ty i nv i tes и удостоверь­


тесь в том , что флажок Add Application lnsights to Project (Добавить в проект службу
Application Insights) не отмечен (см . рис. 2.3). Для продолжения щелкните на кнопке
ОК. Откроется еще одно диалоговое окно (рис. 2.4), предлагающее установить началь­
ное содержимое проекта .

г-·-·--·------·-----·------·· ·-1 А project template for crtoting an ASP.NEТ Соге


i MP; CoreTcrn;
application with exll mpfe ASP.NEТ MVC Vi~s and
Contfo!lers. This template сап ~lso Ье used for RESТfuf
НТТР service:.
1
1. En1pty ltleb API 1

·1· 11 1
@haпgt'AUt~
1{ _ J Authentication: No Authentlc~tion - - - - -1
------· - ·- - · - 1<;;, MicrcкoftAzu re 1

\G О Host iп thecloud j

~1~.,.-г:з uo ~ I
1

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 require any user • uthentic•tio".

@ No Auth•ntication

О lndividual User Accounts

О Work And Scl>ool Accounts

О Windows Authentication

г-о~_:] \ Cьncel
Рис. 2.5. Выбор настроек аутентификации

Теперь можете запустить приложение,


Solution Explorer • 1:1 Х
выбрав в меню Debug (Отладка) пункт Start
Debugging (Запустить отладку); если появит- .~ с' ~ f''"Ф •·4'~· @ Wi 1,,, -- .
..:.,;;,,,·"- ··'·' .,;:;;;i~.-:. .~: \о...., •·.1, - ._,

ся запрос на включение отладки, тогда просто .se~5~~l u~on ~plorer (Ctrt,:;~ Р·

щелкните на кнопке ОК. Среда Visual Studio l4J Solution 'Partylnvites' (1 project}
" ;,,.,. Solution Jtems
скомпилирует приложение, с помощью сервера
/;J global.json
приложений IIS Express запустит его и откро­ ..; ·~1 src
ет окно веб-браузера для запроса содержимого
приложения. Результат показан на рис. 2.7. ~ ,1- Properties
~ •·• Reference.s
Когда среда Visual Studio создает проект с при­
менением шаблона Web Application, она добавля­ •·• Depe.ndencies
ет базовый код и содержимое, которое вы видите ~ ;.t; Controllers
после запуска приложения. В оставшейся части ~ ~ Vieo.vs

этой главы мы будем заменять такое содержимое,


lJ appsettings.json
lJ bundleconfigjson
чтобы создать простое приложение МVС.
С" Program.cs
По завершении не забудьте остановить от­ ~ lJ project.json
ладку, закрыв оюю браузера или вернувшись в .D Project_Readme.html
с• Startup.cs
Visual Studio и выбрав в меню Debug пункт Stop
у') web.config
Debugging (Остановить отлад~<у).
Как было только что показано, для отображе­
ния проекта среда Visual Studio открывает окно Рис. 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

Application uses How to Overvi ew Run & Deploy


• ~;а 1>о191-? UWЧ ASl'I Nfl • AddaQ.imrt'Jl!<'or....Ю'/- • Глrщ.Ш:.lt.>."4,.,.......iilwl~•t" · 11-"'У"'" '"""
CCrtMVC ' A'1':-."'lapps.;it!l>l)lflщ\/.glil'l<I ASPtlП ~ • Rllft l(ll'll•&ur.~.\~fТtr~

......
&.-l!l<m.Ul,}f)IП',j(~.$4dil

• Thfm!ng 11~ И«щщр


......,.."lln~
· M~-...Uwl~ll"l'lf.l'f.l>I
!.l1~
• Futnr.n1>00!ЬolNil ' Nf J C:.,."
~,,St.!1rup.,11::!~

· ~-iti~
• .,. •
/INl"ll'.r"
l'ld-Jh •<J !~/: .w.n.ww

· \.lw~'!/.IU~·~
• -'4.Jpatkog'!'>uPlg tll>O~
· ~
0 (.l~t-"'~•wr.t
'""
• A(l('ltl<lrllpN~u""'JI~ • ri.,.y,4<\p(">(j..!l<>:nr~f'l>,1'.IO'.~

o ljjf\l('f~fl''"'l.ЦloJ""JI)( • r~.tdn"3f"\,"!'l'"°(!t,.'Q,l/N>~"'tл1
ptOd!,ГЬIК\Wtlr.'JIИ'I(

'1l0 1t!l · l'iwfytпrrtн

Рис. 2. 7. Выролнение примера проекта

Test. An•lyz• Window H•lp

.-'------->=~. • 1 ~...,L__·_ .........


llSExpress
llS Express
Partyfnvit•s
Web Bro wш (G oogl• Cl11ome)
Google Chrome
Google Chrome C~n~ry ~
lntern et Explorer
Mic rosoft Edge

Рис. 2.8. Выбор браузера

Каждый открытый метод в контроллере называется методом действия, что оз­


начает возможность его вызова из веб-среды через некоторый URL для выполнения
действия. В соответствии с соглашением MVC контроллеры помещаются в папку
Contro lle rs , автоматически создаваемую Visual Studio при настрой1<е проекта.

Совет. Вы вовсе не обязаны соблюдать указанное или большинство других соглашений MVC,
но рекомендуется его придерживаться - и не в последнюю очередь потому, что это по­

может уяснить примеры , приведенные в настоящей книге .

Среда VisuaJ Studio добавляет в проект класс стандартного контроллера, который


вы можете увидеть , раскрыв папку Control le r s в окне Solution Explorer. Файл назы­
вается HomeCont r ol l e r. cs . Файлы классов контроллеров имеют имя, завершающе-
Глава 2. Ваше nервое nриложение MVC 35
еся словом Contro l ler, т. е. в файле Home Controll e r. c s содержится код контролле­
ра по имени Home - стандартного контроллера. используемого в приложениях МVС.
Щелкните на имени файла HomeCont r o ll er . cs в окне Solution Explorer, чтобы среда
Visual Studio открыла е го для редактирования. Вы увидите код С# , приведенный в
ли стинге 2.1.

Листинг 2.1. Первоначальное содержимое файла HomeController. cs


из папки Controllers
using System ;
using System . Co l lectio ns. Ge n e ri c ;
u sing System . Li nq ;
using System . Threa d 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 ntro ller
puЫic IActionRes ult I ndex() {
return View () ;

puЫic IAct i onResult About() {


ViewData [" Message " ] = " You r app l ication descriptio n pa g e .";
return View() ;

puЫic IActionRes ul t Contac t ( ) {


ViewData [ " Message "] = " You r c ont act page . ";
return View() ;

puЫic IActionResu lt Error() {


return Vi ew() ;

Заменит е код в файле Ho meCon tr o l ler . c s кодом, показанным в листинг е 2.2.


Эде сь были удалены все методы кроме одного , у которого изменен возвращаемый
тип и его р еали з ация, а также удалены операторы u s i ng для неиспользуемых про ­
странств имен.

Листинг 2.2. Изменение файла HomeController. cs


using Microsof t. AspNetCore .Mvc ;
n amespace Par tyi nv i te s.Cont r o ller s
puЫic class HomeCo ntr o l le r : Co n t r o lle r
puЫic str i ng In d ex() {
retu r n " He ll o Wo r l d";
36 Часть 1. Введение в инфраструктуру ASP.NET Core MVC

Изменения не приводят к особо впечатляющим результатам, но их вполне доста­


точно для хорошей демонстрации. Метод по имени I n dex () изменен так. что теперь
он возвращает строку "He ll o Wor l d ". Снова запустите проект, выбрав пункт Start
Debuggiпg из меню Debug в Visual Studio.

Совет. Если вы оставили в функционирующем состоянии приложение из предыдущего разде­


ла , то выберите в меню Debug пункт Restart (Перезапустить) или при желании пункт Stop
Debuggiпg и затем Start Debuggiпg.

Браузер сделает НТТР-запрос серверу. Стандартная конфиrурация MVC предусмат ­


ривает, что данный запрос будет обрабатываться с применением м етода Index () , на­
зываемого методом действия или просто действием, а результат, полученный из этого
метода , будет отправлен обратно браузеру (рис. 2 .9).

_
l0<a!host:S7628 Х

_?_@l~c~~~~~~~-8- ·~-=-=--~=-=---~-=--= ~-j _ _


l: lo World

Рис. 2.9.
-----------·-------
Вывод из метода действия
1
__,

Совет. Обратите внимание, что среда Visual Studio направляет браузер на порт 57628. Внутри
URL, который будет запрашивать ваш браузер, почти наверняка будет присутствовать
другой номер порта, т. к. Visual Studio выделяет произвольный порт при создании проекта .
Если вы заглянете в область уведомлений панели задач Windows, то найдете там значок
для llS Express. Этот значок представляет усеченную версию полного сервера приложе­
ний llS, которая входит в состав Visual Studio и используется для доставки содержимого
и служб ASP.NET во время разработки. Развертывание проекта MVC в производственной
среде будет описано в главе 12 .

Понятие маршруто в
В дополнение к моделям , представлениям и контроллерам в приложениях MVC
применяется система маршрутизации ASP.NEТ, которая определяет, как URL отоб­
ражаются на контроллеры и действия. Маршрут - это правило, которо е использу­
ется для решения о том, как обрабатывать запрос. Когда среда Visual Studio создает
проект MVC, она добавляет ряд стандартных маршрутов , выступающих в качеств е
начальных. Можно запрашивать любой из следующих URL, и они будут направлены

.
на действие


/
/Ноте
Inde x класса H oтeC ont r o lle r :

• / H oтe/Index

Таким образом, когда брауз ер запрашивает http : //ваш- сайт/ или http: //ваш_
сайт/ Н от е , он получает вывод из метода I ndex () класса H oтeController. Можете
Глава 2. Ваше первое приложение MVC 37
опробовать зто самостоятельно, изменив URL в браузере. В настоящий момент он бу­
дет выглядеть как http: //localhost : 57628/, но представляющая порт часть мо­
жет быть другой . Если вы добавите к URL порцию /Home или /Home/Index и нажмете
клавишу <Enter>. то получите от приложения MVC тот же самый результат - строку
" Hello , wo rl d " .
Это хороший пример получения выгоды от соблюдения соглашений, поддержива­
емых 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~ Error Х

: 1
---- --··· • 1
1 An Linhandled exception оссшгеd while processing the reqL1est.
lnvaliclOpeгatio'lExcep<ion: Т11е viev1 'MyViev/ v1as 110t fou r.d. The foi/01·1ing !ocations
were searcf1ed:
.f /Views/Home/MyView.cshtinl
ii
1

i Niews/Si1ared/MyVie\\1.CSl-1t •nl
f Eпiun:-su,cessful

1 В Q\Jery Cookies Heиders i


1
1. - ···---- - . :i

Рис . 2.1 О. Инфраструктура MVC пытается найти представление

Например, представления, которые связаны с контроллером Home , содержатся в


папке по имени Vi ew s / Home . Представления, не являющиеся специфическими для
отдельного контроллера, хранятся в папке под названием Vie ws / Sha r ed . Среда
Visual Studio создает папки Home и Sh a re d автоматически, когда используется шаб­
лон Web Application, и в рамках начальной подготовки проекта помещает в них не­
сколько представлений-заполнителей.
Чтобы создать представление , щелкните правой кнопкой мыши на папке Home
внутри V i ews в окне Solution Explorer и выберите в контекстном меню пункт Add~New
ltem (Добавить~Новый элемент) . Среда Visual Studio предложит список шаблонов эле­
ментов. Выберите категорию ASP.NET в панели слева и затем укажите элемент MVC
View Page (Страница представления МVС) в центральной панели (рис. 2.11) .

·~ 1nтlle<1 Sort Ь у: ~•foult i SNrcl1 lnst4JJed Tem~l;tts ~Ctr!•E} Р · .j


~J>.NEТ1 MVC (ontroller Clas~ ~ Туре: ASP.N H
ASP.NEТ
Client-side
Code
р MVC View Р•9е
W!:!b АР\ Contioller Class А5Р.NЕТ

f> Online

MVC Viow Start Page ASP.NEТ


~

. MVC View lmport:; Page ASP.NH

~~· R.!zor Tag HeJper д5Р.N ЕТ

№mt: MyVi~.csht1n l

Рис. 2. 11. Создание представления

Совет. В папке Vi 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;

< ! DOCTYPE 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 osof t. AspNetCore .Mvc ;
namespace Partyinvites . Contro ll ers
puЫic class HomeCon t roller : Con troller
puЫic ViewResu l t Index() {
int hour =
DateTime.Now.Hour;
ViewBag. Greeting =
hour < 12 ? "Good Morning" "Good Afternoon";
return View( "MyView " ) ;
Глава 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>
<htm l >
<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 exci ting party . <br />
(То do: sell i t better. Add pictures or something.)
</р>
</div>
</body>
</html >
Глава 2. Ваше первое приложение MVC 43
Мы двигаемся в верном направлении. Если запустить приложение, выбрав в меню
Debug пункт Start Debugging. то отобразятся подробности о вечеринке - точнее, за­
полнитель для подробностей, но идея должна быть понятной (рис. 2.14).

Goocl Moш.i11g \Vorld (fi·oщ tl1e Yie,v)

l
1 \Ve're goiдg to lшУе а11 exc iti.11g party.
(То do' "11 i1 bon« Add piono" 01 •onwd1iug._)______,

Ри с. 2.14. Добавление информации о вечеринке к НТМL-разметке представления

Проектирование модели данных


Буква "М" в аббревиатуре MVC обозначает тodel (модель) , и она является самой
важной частью приложения. Модель - это представление реальных объектов, про­
цессов и правил. которые определяют сферу приложения, известную как предметная
область. Модель , которую часто называют моделью предметной облас ти, содержит
объекты С# (или объекты предметной области), образующие "вселенную" приложе­
ния, и методы, позволяющие манипулировать ими. Представления и контроллеры
открывают доступ клиентам к предметной области в согласованной манере, и любое
корректно разработанное приложение МVС начинается с хорошо спроектированной
модели, которая затем служит центральным узлом при добавлении контроллеров и
представлений .
Для про екта Partylnv i tes сложная модель не требуется, поскольку приложение
совсем простое, и нужно создать только один класс предметной области, который по­
лучит имя GuestRespo nse. Этот объект будет отвечать за хранение, проверку досто­
верности и подтверждение ответа на приглашение (RSVP).
По соглашению MVC классы, которые образуют модель. помещаются в папку по
имени Models . Чтобы создать эту папку. щелкните правой кнопкой мыши на проекте
Partylnv i 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 " "Good Afternoon";
return View( "MyView " );

puЫic ViewResul t RsvpForm ()


return View () ;

Метод действия RsvpForm () вызывает метод View () без аргументов, что сообща­
ет инфраструктуре МVС о необходимости визуализации стандартного пр едставления,
связанного с этим методом действия, которым будет представление с таким же име­
нем, как у метода действия (RsvpForm . cshtml в данном случае).
Глава 2. Ваше первое приложение MVC 45
Щелкните правой кнопкой мыши на папке Views внутри Home и выберите в кон­
текстном меню пункт 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 " con t e nt =" width=devi ce - wi dth " />
<title>RsvpForm</titl e>
</ head>
<bo dy >
<div>
This is the RsvpForm . c s html View
</d i v>
</body>
</html>

Содержимое состоит в основном из НТМL-разметки , но с добавлением Rаzоr ­


выраж ения @model , которое используется для создания строго mипизирован.н.ого
представле ния. Строго типизированное представление предназначено для визуали ­
зации специ фического типа модели, и если указан жела емый тип (в данном случае
класс GuestResponse из пространства имен Par t yinvite s . Models), то MVC может
создать ряд удобных сокращений, чтобы сделать его проще. Вскоре мы задействуем
преимущество характеристики строгой типизации.
Чтобы протестировать новый метод действия и его представление , запустите при­
ложение, выбрав в м еню Debug пункт Start Debugging, и с помощью брауз ера перей ­
дите на URL вида / Home/RsvpForm.
Инфраструктура МVС применит описанное ранее согл ашение об именовании для
направления запроса методу действия RsvpForm () , определенному в контролле­
ре Home . Этот метод действия указывает MVC о том, что должно визуализировать­
ся стандартное представление, которое посредством еще одного применения того же

соглашения об именовании визуализирует Rs vpFo rm. cs hml из папки Views/ Home .


Результат показан на рис. 2.15.

1· RsvpForm х

г---·-----"·~-·--
~ С Lq:> loca l ho~t 5}628/Home/RsvpFo rm. _ __

[ " ; ;, ;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>We'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 Rsvpform Х

_____'? i9?~°.~~~~~-;~3__ _
Пu$ is 111~ RsYpF011n.cslitшl \ ' ie\\·

L ~--~- ~----
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-форму для редактирования объектов Gue stResponse (листинг 2.12).
Листинг 2.12. Создание представления в виде формы в файле RsvpForm. cshtml
@model Pa r tyinvites . Models . GuestRe sponse
@{
Layout = null ;

< !DOCTYPE htm l >


<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=" /Home /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. Эта версия действия будет
отвечать за отображение изначально пустой формы , когда ~по-нибудь впервые
посещает / Home/ RsvpFo rm.
• Метод, который отвечает на НТГР-запросы POST. По умолчанию формы, ви­
зуализированные с помощью Ht ml. Be g in Form () ,отправляются браузером как
запросы POST. Эта версия действия будет отвечать за получение отправленных
данных и принятие решения о том, что с ними делать.

Обработка запросов GET и POST в отдельных методах С# способствует обеспечению


аккуратности кода контроллера, т.к. эти два метода имеют разные обязанности. Оба
метода действий вызываются через тот же самый URL, но в зависимости от вида за­
проса - GET или POS T - инфраструктура МVС вызьmает подходящий метод. В листин­
ге 2.13 показаны изменения, которые необходимо вне сти в класс HomeContro ll er.
Листинг 2.13. Добавление метода действия для поддержки запросов POST
в файле HomeController. cs

using Sys tem ;


us i ng Mi c r oso f t .AspNet Cor e .Mvc;
using Partyinvites.Models;
name s pace Part yinvi te s. Con troll e r s
p uЫ ic clas s HomeC ont ro lle r : Cont r oll e r
puЫi c ViewResu l t Index( ) {
int ho ur = Date Time . Now .Hou r ;
Vi e wBag . Greeti ng = hou r < 1 2 ? "Good Morn in g " "Good Af te r noon ";
r e t u rn View ( "MyView" ) ;

[HttpGet]
puЫ i c ViewRe s ul t RsvpFo rm () {
r et ur n Vi ew ( ) ;

[HttpPost]
puЬlic ViewResult RsvpForm(GuestResponse guestResponse)
//Что сделать: сохранить ответ от гостя
return View () ;
50 Часть 1. Введение в инфраструктуру ASP.NET Core MVC

Существующий метод действия RsvpFo r m ( ) был снабжен атрибутом HttpGet,


который указывает 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 vp Form () визуализирует то же
самое представление, что и ранее (файл RsvpF orm . cs h t ml), для генер ации формы,
показанной на рис. 2.17. Вторая перегруженная версия более интересна из -за нали­
чия параметра. Но с учетом того, что этот метод действия будет вызываться в ответ
на НТГР-запрос POST, а тип GuestResponse является классом С# , каким образом они
соединяются между с обой?
Секрет кроется в привязке модели - чрезвычайно полезной функциональной воз­
можности МVС, посредством которой производится разбор входящих данных и при­
менение пар "ключ/значение " в НТГР-запросе для заполнения свойств в типах моде­
лей предметной области.
Привязка модели - мощное и настраиваемое средство, которое избавляет от кро­
потливого и тяжелого труда по взаимодействию с НТГР-запросами напрямую и поз­
воляет работать с объектами С#, а не иметь дело с индивидуальными значениями
данных, отправляемыми браузером. Объект Guest Resp o ns e , который передается
этому м етоду действия в качестве параметра, автоматически заполня ется данными
из полей формы. Привязка модели, включая ее настройку, подробно рассматривается
в главе 26.
Одной из целей приложения является предоставление итоговой страницы с дета­
лями о том, кто придет на вечеринку, что означает необходимость сохранения полу­
чаемых ответов. Мы собираемся делать это с помощью созданной в памяти коллекции
объектов . В реальном приложении такой подход не подойдет, т.к. данные ответов бу­
дут утрачиваться в результате останова или перезапуска приложения , но он позволя­

ет сосредоточить внимание на MVC и создать приложение, которое может быть легко


сброшено в свое начальное состояние.

Совет. В главе 8 будет продемонстрировано использование MVC для постоянного хранения


и доступа к данным как часть более реалистичного примера приложения.

Мы добавили в проект новый файл, щелкнув правой кнопкой мыши на папке


Mod e l s и выбрав в контекстном меню пункт Add9Class (Добавить9Класс). Файл име­
ет имя Repo si tory . cs и содержимое, показанное в листинге 2.14.

Листинг 2.14. Содержимое файла Reposi tory. cs из папки Models

u s in g System . Collec ti o n s .Ge ne ric;


names p ace Par t yi nvit es.Models {
puЫ i c sta ti c cla s s Repo sit ory
p r ivate stat ic List<Gues t Response > responses new Li s t<Gue 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 .Mode 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 ? "Good Morning" " Good Afternoon ";
return View ( " MyView " ) ;

[HttpGet]
puЫic ViewResult RsvpForm() {
return View () ;

[HttpPost]
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 , применяется Mod e l. Name . Не беспокойтесь, если синтаксис Razor пока не по­
нятен - он более подробно объясняется в главе 5.
Теперь. когда создано представление Th a nk s, появился работающий базовый при­
мер обработки формы посредством МVС. Запустите приложение в Visual Studio, вы­
брав в меню Debug пункт Start Debugg ing, щелю-rите на ссылке RSVP Now, введите на
форме какие-нибудь данные и щелкните на кнопке Submit RSVP. Вы увидите резуль­
тат, показанный на рис. 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 \\~ill уа11 ntte11d? г~;~ ii~i~e~~ ~~-- Click~ !О see \ V!IO is сошiнg.
~ uьmit R~~-~ L______Г _____________________ _J

1
[___ ---- ---- -------------- --------------- ------ _,
Рис. 2.18. Представление Tha n ks

Отображение ответов
В конце представления Th a nks. c sht ml мы добавили элемент а для создания ссыл­
ки. которая позволяет отобразить список людей, собирающихся посетить вечеринку .
С применением атрибута дескрипторного вспомогательного класса a s p -acti o n со­
здается URL, который нацелен на метод действия по имени Lis t Re s pon ses ():

<p>C l ick <а asp-action="ListResponses" >h ere</a >


to see who is comin g.< /p >

Наведя курсор мыши на с сылку, rшторую отображает браузер, вы заметите, что


она указывает на URL вида / Home/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 HomeController : Controller
puЫic ViewResult Index() {
int hour = DateTime .Now .H our;
ViewBag. Greeting = hour < 12 ? "Go od Morning " "Good Afternoon ";
return View ( " MyView " ) ;

[HttpGet]
puЫic ViewResult RsvpForm() {
return View();

[HttpPost]
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 ш a il Phont>
Joe joe,rg. exiuнp le . co 111 555- 1234
Alice al i ce@e.xaшple . coш 555-5678
1

-----·--------·--------------·---__J
Рис. 2. 19. Отображение списка участников вечеринки

Добавление проверки достоверности


Теперь мы готовы добавить в приложение проверку достоверности вводимых дан­
ных . В отсутствие прове рки достове рности пользователи смогут вводить бессмыслен­
ные данные или даже отправлять пустую форму . В приложении МVС пров е рка до­
стоверности обычно прим еня ется к модели предметной области, а не производится
в пользовательском инт ерфейс е. Это з начит, что проверка достоверности опр еделя­
ется в одном месте , но оказывает воздействие в приложении везде, где испол ьзует­
ся класс модели. Инфраструктура MVC поддерживает де кларативные правила про­
верки дос товерности, опр еделенные с помощь ю атрибутов из пространства им е н
System . Componen t Mode l. DataAnnotations , т.е. ограничения проверки до стовер­
ности выражаются посредством стандартных атрибутов С#. В листинге 2.19 пока з а ­
но, как применить эти атрибут ы к классу модели GuestRespon se.

Листинг 2.19. Применение проверки достоверности в файле GuestResponse. cs

using System.Componentмodel.DataAnnotations;

name s pace Pa rt yi n vi t es . Models


p u Ыi c clas s 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 Пожалуйста , введите свой адрес электрон н ой почты
p u Ыic s t ri n g Email { get ; set ; }
[Required (ErrorMessage = "Please enter your phone numЬer")]
11 П ожа луй с та, вве дите свой н омер т елефо н а
puЫic st ri ng Phone { get ; set ; J
[Required (ErrorMessage = "Please specify whether you' 11 attend")]
11 П ожалуйс т а , укажите , п риме т е л и учас т ие
puЫ i c 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" "Good Afternoon";
return View ( "MyView " ) ;

[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 - va lidat ion-summar y берется из перечисления по име­
ни Val idationSummary, которое указывает типы ошибок проверки достоверности,
помещаемые в сводку . Мы указали All, что является хорошей отправной точкой для
большинства приложений, а в главе 27 будут описаны другие значения.
Чтобы взглянуть, как работает сводка по проверке достоверности, запустите при ­
ложение, заполните поле Name и отправьте форму. не вводя другие данные. Вы уви­
дите сводку с ошибками, показанную на рис. 2.20.

R\Vl)Form х

1L·-·-- · - - · - - - - - - - - -:i
С ф lo.alhost:S7626/Horпe/Rs11pform '(( 1
1· ~ -_.----·-~ __"_ --=~·~-:..=--·---=-·::=--::-:--:---

1 • Рlеме nller yo11.f ешаi\ acklr~s


· • Please enter )'O\lf phone U11J11Ьer
1 • Рlеме specit)· ,,·he1/1o:r yo11'll :11ta1d

j Yot1r шuue; \~~-=--=-


1 . r -
__--· -=--=]
Yo11.f ешмl : l --. . .

1
Уощ pl1011e: с_-=~ ·
1

1 \\'1 11 уон attend" : С~оо~е ал 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-requ 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="Ph one "
narne= " Phone " va lue="" >
Отличие выделено полужирным: атрибут дес.крипторного вспомогательного класса
asp-for добавил .к элементу input .класс по имени input-validation-error . Мы
можем воспользоваться этой возможностью, создав таблицу стилей, которая содер­
жит стили CSS для этого класса и другие стили, применяемые различными атрибу­
тами дескрипторных вспомогательных классов.

По принятому в проектах МVС соглашению статическое содержимое, доставляе­


мое клиентам, помещается в папку wwwroot , подпапки которой организованы по типу
содержимого, так что таблицы стилей CSS находятся в папке wwwroot/css , файлы
JavaScrlpt - в папке wwwroot/j s и т.д.
Для создания таблицы стилей щелкните правой .кнопкой мыши на папке wwwroot/
css в окне Solution Explore r и выберите в контекстном меню пункт AddФNew ltem
(ДобавитьФ Новый элемент). В открывшемся диалоговом окне Add New ltem (Добавление
нового элемента) перейдите в раздел Client-side (Клиентская сторона) и выберите шаб­
лон Style Sheet (Таблица стилей) из списка (рис. 2.21).

P· j
r
~ . !:;~: Sort Ьy. Odault

CJ .а. Туре; C!itnHidt

1
.litnt·sidt
Code
1
HTML P•ge C/1cnt ·side

1 (.tS(adir19 ~ty!t
sh<!d. (CS.S)
HT ML\tyledtfinot1oros
u~cd for1i<h
! t> Onli~
1 @j5 TyptS<ript Fi!e. Clttnt·sidt

l§J' T~ScriptJSX F i lc Clicnt·side

rJ Type5(t1pt JSON Conf19ur111on f1le Cl1cnt·~ide

1
rJ Sowc1 Confi9u11tion f 1lc Clltnt-1idc

1
f N.:iu1~ J
Q d"d-) o;,-;;-i j
L --- - __ 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; }

Чтобы применить эту таблицу стилей, мы добавили элемент li nk в раздел 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
- -
. ~- ... --~ -. ___ ._.._."-;-::- ~- _.., ·-~~:-·- --···- - -::·-:·;::::-.:-- ... ·::--..::.::
• Plta8• 1.>ntft' ~·out· t>m.;iiJ addi'\'и
• Р1Е-а~.- e11ftr :;1·ощ· phoot' numbtr
• Pl•:a~• \pc.>('if·y " 'htther ~·ou 'U atttod
1
--- -"
!
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: / / getbo ots tr ap . с от , а
в листинге 2.24 демонстрируется использование нескольких базовых стилей в пред­
ставлении MyView. cshtml.

Листинг 2.24. Добавление классов Bootstrap в файл МyView. cshtml

@{
Layout = null;

< ! DOCTYPE html>


Глава 2. Ваше первое прило жение MVC 63
<h t ml>
<head>
<meta name= "vi e wpo r t " content=" widt h=dev i ce - width " />
<title>Index</ ti tle>
<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>
</d i v>
</body>
</html>

В разметку был добавлен элемент link, атрибут hr ef которого загружает файл


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 , inpu t или а в виде симпатичной кнопки. а
класс Ьtn - primary указывает диапазон цветов для этой кнопки . Запустив прил оже­
ние, можно увидеть результат, показанный на рис. 2.23 .
Вам должно быть вполне очевидно , что я - не веб-диз айнер . На самом деле, буду­
чи еще ребенком , я был освобожден от уроков рисования по при чине пол ного отсутс­
твия таланта. Это произвело благоприятный эффект в виде того, что я стал уделять
больш е времени урокам математики, но вместе с тем мои художественные навыки не
развивались примерно с десятилетнего во зраста. Для р е альных проектов я обратился
бы к помощи профессионального дизайнера содержимого, но в настоящем примере я
собираюсь делать все самостоятельно и применять Bootstrap с максимально возмож­
ной сде р ж анностью и с огласованностью, на какую толь ко способен.

lndex х

С !. ф localhost:57628

We're going to have an exciting party!


And you are invited
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 st Res ponse


@{
Layout = n ull ;

< ! DOCTYPE htm l >


<html>
<h ead>
<meta name= " viewport " content= " width =devic e- width " />
<t i tle>Rsvp Form</t i t l e>
<l i n k re l = " s tyleshee t" href =" /cs s /sty l es . 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="pane 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>
</f orm>
</div>
</div>
</body>
</html>
Глава 2. Ваше первое приложе ние MVC 65
Классы Bootstrap в этом примере создают заголовок, просто чтобы придать компо­
новке структурированность. Для стилизации формы используется класс form-group,
J{Оторый стилизует элемент, содержащий label и связанный элемент input или
select. Результаты стилизации можно видеть на рис. 2.24.

1' .~
.

YourpJюne:
!
1
j

1
W1ll you attend? j

·-
Choose an opuon
1
1 1
!
1
1
1
___ j
L . -----~-·-~------·-----·-----··-·-- · - - - - -

Рис. 2.24. Стилизация представления RsvpForm

Стилизация представления Тhanks


Следующим стилизуемым представлением является Thanks. cshtml; в листин­
ге 2.26 показано, как это делается с применением классов CSS, подобных тем, которые
использовались для других представлений. Чтобы облегчить управление приложени­
ем, имеет смысл избегать дублирования кода и разметки везде, где только возможно .
Инфраструктура MVC предлагает несколько средств, помогающих сократить дубли­
рование, которые рассматриваются в последующих главах. К таким средствам отно­
сятся компоновки Razor (глава 5), частичные пр едставления (глава 21) и компоненты
представлений (глава 22).
Листинг 2.26. Добавление классов Bootstrap в файл Thanks. cshtml

@model Partyinvites . Models . GuestResponse


@{
Layout = null;

<!DOCTYPE html>
<h tml>
<hea d >
<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. The 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 that you're coming. The drinks are already in tl1e fridge'
Click t1ere to see who is coming.

- - - - - - - - - - - - - - - - - - - - - - - - - - - --J

Рис. 2.25. Стилизация представления Thanks

Стилизация представления Lis tResponses


Последним мы стилизуем представление ListResponses, которое отображает
список участников вечеринки . Стилизация содержимого следует тому же ба зо вому
подходу, который применялся в отношении всех стилей Bootstrap (листинг 2.27).

Листинг 2.27. Добавление классов Bootstrap в файл ListResponses. cshtml


@model IEnumeraЬle<Partyinvites.Models . Gue s tResponse>

@{
Layout = null ;

<!DOCTYPE html>
<html>
<head>
<meta name= "viewport " content= "widt h=device- widt h" />
<link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" />
<tit l e>Responses</t itl e>
</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 .Models . 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 показано, как теперь выглядит таблица участников . Добавление сти­
л ей к данному представлению завершает пример приложения , которое теперь достиг­
ло всех целей разработки и имеет намного более совершенный внешний вид.

i f- i
1[~ Here is the list of people attending the party
1

1
!
1 Name Email Phone

1 Joe joe@example.com 555-1 234

1 Alico a1ice ~ example . com


1
i ВоЬ bob@example.com 255-2345
i
!
______ - ---·-- ---
[ --·- ___ J
Рис. 2.26. Стилизация представления ListResponses

Резюме
В этой главе был создан новый проект МVС , который использовался для построе ­
ния простого приложения ввода данных МVС, что позволило получить первое пред­
ставление об архитектуре ASP.NET Core MVC и применяемом подходе. Некоторые
о сн о вные ср едства (включая синтаксис Razor, маршрутизацию и тестирование) не
рассматривались, но мы вернемся к этим темам в последующих главах. В следующей
глав е будет описаны паттерны проектирования MVC, которые формируют основу эф­
фективной разр аботки с помощью ASP.NET Core МVС .
ГЛАВА 3
Паттерн МVС,
проекты и соглашения

п режде чемCoreприступить
ASP.NEТ 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 . Contro ll 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.

r---------------------.,
Stftct а tempfote:

ASP.NEТ Core Te mplates 1' /


1 А project templ•I• for cre•t•ng an ASP.NEТ Coro
•ppltcat1on wrth ""ampfe ASP.NET MVC Vil!\.,; and
Controllers. This ttmplate can afso Ье used for RESTTul
I ~ ~ ij 1' НТТР servtces.
1 Empty Web API Web
Applicatton
1 Ш!!!.т.ш
1
1
1 l~c-h~
an-g-
eA_ut
_h_e_
.nt-ic-
al-
io~
n1
i ______________________
L Authe:nticotio n: No AuthentkatIOn
1

<;!) Microsoft Azure


G О Host in the cloud

~OK~,, ~J
~-5:_rvice --]

Рис. 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:
• At<Ounl
IJ tJ!r>b•l.J~Ofl , 5:1. ..,,,~ @1 Cofll11mEm.11l.c\htmt
t> J- P109ff11ts !!!) (ictvnaltoginConfirm!bon.c1h!ml с- b1(m1llog1nCo"Г.rmtti0nVir ...Mod!!I.«
•• Rtfrrtncн с.• for9otP1s~rcМrwModtl.н
8 Ь1:ttl\lllogin FJИu1c.cshtml
}' PIOp('lties ." -V.~001 ~for9ctPшwaid.cshtml
<• loginVie1.~.'\odrl.cs
tJ t.\of\<hStШn91.j1on !:> • ·• DrprndtN:iti В ForgotP1иwordCo r1firmation .c1'11ml
с• Rrgist rt'lreY<Modd.c s
..r.- Co nt10Jlt." 8 Locl:out.,shtml
с• Rt1t!Pauwo1dVitwМodrl.{~
~ -·1001 с• Acc"untCont1olltr.cs t• !.tndCodrV-IC!'WМodrl . п
В login.rshtml
• • Otptnckntit:s t" HomeControllrцs с- \lf1ify(ode\'~Modt1.ts
[!!) д.f9•Sttr.cshtml
Со Pн19rem.cs с• М.n19rCon11ollcr.c~ • ;,,..· M1rцg rV1rwModrh
@) P.tu,1P1mwo1d.c.111tml
.l rJp1ojcrtJи111 t • AddPhontNumbr!VitwModr Lu
n pro;нtJocli:.j~Qn
• "" D1t1 0 Rt~ttP,шwo1dConfirn1•tion.csh tml
• ....• м.g1111cns
В SrndCodt.uhtml
с- 01n9eP1нwo1dVlrwModrl .< 1
.D P•ojt(t_Rt•dm~html t> с- OOOOOOOOOOOOOO.Crt1trldf.nlit)·St
8 'hrifyCodc.c1html
с• C onfigurrTwo F1ct oJV1e1.~ModrJ.c 1
С• St1r11tp.H С" Applк.11tionObCon!txtModf.!Srit С" f1ctcrV1ewMQdrl.ci
• loo.· Homt
у wtb.c~fig с- Applic.t1ionDbContm.cs с- lnduVitwMode!.ts
/!) AЬouttthtml
ь illl! t-.tud~lt ео J. l..,..9d.vyi11}Vif.'WМ0Ucl.~ }
@j Cont1ct.0Nml
• Strvicн G) lndu.cshlml С* Rtm0Vtlog1nVi,...Modrl,CJ
Р l[m1i1Srndt1.o с• SdP•иwoнfVitwf"1odt~C1
;..· M1"1gr
t• ISmsSt"dr:цs С" Vt•ily?hontNumbr<V1ewModt!.c.t
@1 AddPhontN11mЬtt.cshtml
с• Meщ9tScnAct,.ci С" Applktlil)r!Ultf.U
121 Ctш19rPt~nчo1d.<Ui1ml
Jt 81 Vi~
0 lndu.иhl ml
lJ 1ppsrtting1j10" @l№rцgtlogi n,.<thtml
61 Ьundlteonfig .jso n В SftP•1swo1d. нhtml
с- P1ogr1m.c1 0 VtrifyPhc~umbtr.csh1ml
" IJ project.j1or1 • ....- Sh"rd
.!~' Projr:cLR"dmc.html е .tl)'O\ILt~htmt
e< S1artup .cs В .L oginP11rti•l.ohtm! /> • ir"•9н
f) wtt. conl19 В .V11l1d1t1onXript~Patti11Ltshtml /1 $ j'
[!) (rfor.cshtml • .... r1 ь
@l .Viewimportм~htnil " "1i boot1t1J p
@} .ViewScer1.c1html !:> ~ 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 Области - это способ разбиения крупных приложений на


мелкие порции. Области рассматриваются в главе 16
/Dependencies Элемент зависимостей предоставляет подробные сведения
обо всех пакетах, от которых зависит проект. Диспетчеры
пакетов , применяемые Visual Studio, описаны в главе 6
/Component s Здесь определены классы компонентов представлений,
которые используются для отображения самодостаточных
средств, таких как корзины для покупок. Компоненты пред­
ставлений обсуждаются в главе 22

/ Controller s Сюда помещаются классы контроллеров. Это соглаше-


ние. Классы контроллеров могут размещаться где угодно,
потому что все они компилируются в одну и ту же сборку.
Контроллеры подробно рассматриваются в главе 17

/Data Здесь определены классы контекста баз данных, хотя я


предпочитаю игнорировать это соглашение и определять их

в папке Mode ls, как будет продемонстрировано в главе 8


/Migra t ions Здесь хранятся детали схем баз данных, чтобы мож но было
обновлять развернутые базы данных. Процесс развертыва­
ния будет показан в главе 12
/ Models Сюда помещаются классы моделей представлений и моде­
лей предметной области. Это соглашение. Классы моделей
могут определяться где угодно в текущем проекте или даже
в отдельном проекте

/Vi ews Здесь хранятся представления и частичные представления,


обычно сгруппированные в папки с именами контроллеров,
к которым они относятся. Представления будут подробно
описаны в главе 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)
/p r o j ect. j son В этом файле указаны базовые конфигурационные пара­
метры для проекта , в том числе применяемые им пакеты
NuGet (глава 6)
/Prog r 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. Сводка по главе

Задача Решение Листинг

Избегание обращения к свойствам Используйте n u ll-условную 4.6-4.9


по ссылкам nul l операцию

Упрощение свойств С# Используйте автоматически реа- 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
методов a sync и awa i t
Получение имени метода или свойства клас­ Используйте выражение 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 ··1 - -
" S.Ort Ьу: ~efau!t_
·- -
__ ".
." .::::
! ~ - · ~::J
-· -· - -
~ ~,;_c!:'_J~sblled !:m!:!~ts.\~ tr/"fJ.
·-
i
~-·!r" !f\rt.Jllr:d
ij ASP.NП \Чf!Ь Application (.NЕТ Fran\~ork} VisualC: Туре: Vi1ui1I с;

.с Templistes Projec t t empl.ttes for cre11tin9 ASP.NET


.с Visu1t!C• Core •pplic11tions for Windo\>6, linux and
0) Х using .NП Core.
f itJindo~vs
wеь ASP.NET Core V./eb Application ( .N П Framtwoik) VisualC~ ' ApplkottIOn lnsJ9hts
.NE1 Core О Add Applit~tion ln~ight!; to proje<:t
And1oid Optimize performonce •nd monitot
Cloud usage in ycur live opplicaticn,
Exten~ibllity

iOS
Reporting
Silverlight
Help ntt undtrs~nd Appl1cttio.n lr1s!ghts
\ ~ Onliм Pri11;,cy Stat~.ment

1
Locatiorn \ Br~:;:J 1
Solutionnгme Li11n9 u:g~Fe0Jtur~ ., _ _ C.reatedl1eф;iryforsolutlon'

L~-'---·-·--"-· ----~--~-~--_:_--~-----·~-~~~:~~~~-·-·~-Add:~"~j~~~::r J!__:~~ !j


Рис. 4.1. Выбор типа проекта

Когда отобразятся различные конфигурации проекта ASP.NET, выберите шаблон


Empty (Пустой), как показано на рис. 4.2, и щелкните на кнопке ОК, чтобы создать
проект.

1 S•l«t • lemplate:
с - ~- ~--- -----~- -----------"'--- ----1 An empty p,oject template fo r cr9tin9 an ASP.NП Co re 1
1 j1 AS P.NEТ Core Tempiates 1 app!kэtion. This templi!!te does not have any content in
it.

~ 1 ~
'

111 ij
1 Web AP I \'leb ,. 1
I Application
1
1 1, 1 _________ l
j j l~~~~-~~~)-t~~:.~~~~

i__ ··-· --·- ·--··-~--~- -- ·--·-·~--·~ ----·· ----~-J ~~~~~~~~n-----·---


~ Microsoft Azur~
1
1 0 О Hort in the cloud

lf дрр S~~~--·:·:
1
'-'--·---~·-·j
1

L ----·--·---------------- - - - - ·---···-------------------DC1~ 1
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"
},
"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"
} 1

В разделе dependencies файла proj ect. j son перечислены сборки, которые тре­
буются для про екта. Мы добавили сборку Microsoft. AspNetCore. Mvc, содержащую
классы МVС. Обратите внимание на добавление запятой в конце строки, предшес­
твующей Microsoft . AspNetCore . Mvc. Файлы конфигурации JSON чувствительны
к корректному форматированию, а о добавлении запятой легко забыть и тем самым
вызвать ошибку.

Совет. Каждая сборка указывается с номером версии. Вы должны удостовериться , что все
сборки с указанными версиями нормально работают вместе . Во время редактирования
файла proj ect. j son среда Visual Studio предоставит списо к доступны х верси й сборок .
Простейший подход заключается в том, чтобы указанная вами версия для Microsoft .
AspNetCore . Mvc совпадала с версией существующих сборо к в разделе dependenc ies,
к оторый был добавлен 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 tu p {

puЫic void Config ureServices (I Se r vi ce Collection services ) {


services.AddМvc();

puЫic void Configure(IApp l i cationBu i l de r ар р, I Host i ngEnvironrnen t env ,


ILogge r Factory loggerFac t 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 ", Pr i ce = 275М
};
Product l ifejacket = new Prod uct
Narne = "Lifejacket ", Price = 48 . 95М
};
return new Product[] { kayak , lifejacket , null };

В ю~ассе Products определены свойства Name и Pr i c e, а также статич е ский метод


по имени GetP r oducts () , который возвращает м ассив элементов Product . Один из
эле м е нтов , с оде ржащихся в возвращаемом из метода GetProducts () массив е, уста­
новл ен в null .
86 Часть 1. Введение в инфраструктуру ASP.NET Core MVC

Создание контроллера и представления


В примерах этой главы мы применяем простой контроллер для демонстрации р а з­
личных языковых средств . Создайте папку Controllers и добавьте в не е файл класса
по имени HomeContro ll e r . cs , содержимое которого показано в листинге 4.4 . В слу­
чае использования стандартной конфигурации MVC инфраструктура будет по умол ­
чанию отправлять НТГР-запросы контроллеру Home.

Листинг 4.4. Содержимое файла HomeController . cs из папки Controllers


using Microsoft . AspNetCore . Mvc ;
namespace Language Feat u res . Contro ll e rs
p u Ьlic cla s s HomeCo ntro l ler : Control l er

puЬlic Vi e wRes ult I ndex()


ret ur n Vi ew (n ew str in g [] { "С#", "Langua ge ", " Fe a t u res " }) ;

Метод действия 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 = null;
< ! DOC TYPE html >
<html>
<head>
<meta name =" v iewp o rt " co nt e nt= " width=devi ce - width " / >
<t itl e >La ng ua ge Features</ t it l e>
</head>
<body>
<u l >
@foreach (str ing s in Mode l ) {
<li >@s</ l i>

</ul>
</body>
</html >

Запустив пример приложения путем выбора пункта Start Debugg ing (Запустит ь от ­
ладку) в меню Debug (Отладка) , вы увидите вывод , предст авленный на рис . 4.3.
Глава 4. Важные функциональные возможности языка С# 87

D language 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;
name space LanguageFeatures . Controlle rs
pu Ы ic class HomeCo nt roll er : Co nt ro l le r

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, возвраща­


ет массив объектов, который инспектируется в методе действия Inde x () контрол­
лера с целью получения списка значений 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 Languag e Feat ures.Models {
puЬlic class Product {

puЬlic string Narne { get; set ; }


puЫic decimal? Price { get ; 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 . Mvc;
using System . Collect i o n s . Gene r ic ;
using LanguageFeatures . Models ;
namespace Language Featu r es . Cont r o ll ers
puЫic c l ass HomeCo nt r oll e r : Co nt r o ller

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 ;
dec i mal? price = p? . Price;
string relatedName = p?.Related?.Name;
resul ts .Add (string. Format ( "Name: {О}, Price: {1}, Related: {2}",
name, price, relatedName));

r etu r n Vi ew(re sul ts) ;

n ull-условную операцию можно применять :к каждой части цепочки свойств,


например:

string relatedName = p?.Related?. Name;

В таком случа е переменная re la tedName получит значение nu ll, когда значение


null и м еет р или р . Rela ted. Иначе relatedName будет присвоено знач ение свойства
р . Rela ted . Name. Запустив пример приложения, вы увидите в окне браузера следую­
щий вывод:

Name : Ka yak , Pr i ce : 275 , Re l ated : Lifej ac ket


Name : Lifejacket , Pr i ce : 48 . 95 , Relat ed:
Name : , Price : , Related :

Ко мби ниро в ание null-условной операции


и оп ерации объеди нения с null
Для установки альтернат ивного зн ачения, представляющего null, удобно комби­
ниров ать nu l l -условную оп е рацию (один знак вопроса) и операцию объедин е ния с
nul l (два знака вопроса), как демонстрируется в листинге 4.9.

Листинг 4.9. Сочетание операций работы с null в файле HomeController. cs


using Microsoft . As pNetCore . Mvc ;
us i ng System . Col l ect i ons . Ge ne ri c ;
using LanguageFeatu r es . Models ;
90 Часть 1. Введение в инфраструктуру ASP.NET Core MVC

namespace LanguageFeatures.Controllers {
puЬl i c class HomeController : Con tr oller

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 .Models {


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 ; se t; }

как экви в ал ентное приведенному ниже коду:

puЫic st r ing Name {


get { return name ;
set { name = val u e ; }

Ср едств а подобного типа и з вестны как "синтаксический сахар", который делает


р аботу с С# более приятной (за счет устранения в этом случа е избыточного кода , дуб­
л ируем ого в итоге для 1шждого свойства), однако существенно н е изм е няет поведение
яз ы ка. Те р мин " саха р " может показаться уничижит ельным, но л юбы е улучшения. ко ­
торые облегчают н ап исание и сопровождение кода . приносят пользу - особенно в
кру пных и сл ожных про е ктах.

И спользо ва ни е и ни циал и заторов


автомат и чески реализуемых свойст в
Автоматич е ски р е ализуемые свойства поддерживаются, н ачиная с ве р сии С # 3.0.
В последней версии С# доступны инициализаторы для автоматич е ски р еализуемых
свойств . которые позволяют устанавливать начальны е знач ения. не требуя прим ене­
ния конструкторов (листинг 4 . 10) .

Листинг 4.1 О. Использование инициализатора автоматически реализуемого


свойства в файле Product. cs
n a mespace LanguageFeatures . Models {
puЫic c l ass Product {

puЫic str ing Name { get ; s et ; }


puЬlic string Category { get; set; "Watersports";
puЬl i c decimal? Pr i ce { ge t; se t; }
puЫi c Product Related { get ; set ; }
puЫic stat i 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 ", Pr i ce = 48 . 95М
};
ka y ak . Re l ated = lif ejacke t;
return new Produc t [] { kayak , life jacket , null } ;
92 Часть 1. Введение в инфраструктуру ASP.NET Core MVC

Присваивание значения автоматически реализуемому свойству не препятствует


применению установщика для изменения свойства в более позднее время, а всего
лишь приводит в порядок код для простых типов с конструкторами. которые содер­

жат список присваиваний свойств, обеспечивающих наличие стандартных значений.


В приведенном примере инициализатор присваивает свойству Category значение
11
Wa tersports 11 • Начальное значение может быть изменено, что и делается при со­
здании объекта когда ему указывается значение
11
kayak. 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; 11
Watersports" ;
puЫic decimal? Price { get ; set ; }
puЬlic Product Re l 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 ;
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 i c string Name { ge t; set; }
puЫic string Category { ge t; set ; " Wate r spo r ts ";
puЫic decima l ? Price { get ; set ; }
puЫic Product Related { ge t; s et ; }
puЫic Ьооl InStock { get; }

puЫic s t atic Pr o d uct[ ] Get Pro duc t s( }


Product kayak = new Product {
Name = " Kayak ",
Category = " Water Craft ",
Pri ce = 275М
};

Product lifejacket = new Product (false)


Name =
"Lifejacket",
Price = 48.95М
};

kayak . Re l ate d = l ifeja cket ;


return new Pr o du ct [ J { kaya k , l i f ej ac ket, nu l l };

Конструктор позволяет указывать в 1шчестве аргумента значение для свойства,


допускающего только чтение, и если значение не пр едоставлено, тогда он устанавли­

вает свойство в стандартное значение tru e . Посл е установки конструктором значе­


ния свойства оно не может быть изменено.

И с п ольз о ва ние интерполяции строк


Традиционным инструментом С# для образования строк, содержащих значения
данных, является метод str ing. Fo 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 Sy stem . Col l e c tions . Ge n e r ic ;
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() {


Lis t<st ri ng> r e sults = n e w Li st< s t rin g>( );
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.Co l lections . Ge neric ;
us i ng LanguageFeatures . Model 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 .AspNetCore . Mvc ;
using System . Col l ections . Generic ;
using LanguageFeatures . Models ;
namespace 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 ll ections.Gener ic;
using LanguageFeatures . Models ;
namespace LanguageFeatures.Contr oll 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 ShoppingCart , добавленного в папку Models в
виде файла ShoppingCart . cs, который представляет коллекцию объектов Product.

Листинг 4.18. Содержимое файла ShoppingCart. cs из папки Мodels

using System . Co ll ections.Generic ;


namespac e 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 h ods . cs.

Листинг 4.19. Содержимое файла МyExtensionМethods. cs из папки Models


namespace LanguageFeatures . Mode l s {
puЫic static c l ass MyExtens i onMethods {
puЫic static decimal Tota lPr ices(t his ShoppingCart cartParam) {
decimal total = О;
foreach (Product prod in cartParam . Products)
total += prod?.Price ? ? О ;
return total ;

Ключ е во е слово this, расположенное перед первым параметром, помечает


TotalPrices () ка~< расширяющий метод. Первый параметр указывает .NЕТ, к какому
классу может применяться расширяющий метод - I< Shopp ingCart в данном случае.
На экземпляр класса 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 Features . Mode 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 E nume r aЫ 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 I En u m eraЫe<Pro d uc t > Pr oduct s { g et; 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 c l a s s MyEx t ens i o nMethod s
puЫic static decimal TotalPrices (this IEnumeraЫe<Product> products)
decimal total = О ;
fo r each (Product pr od in pr od u c 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 c l ass HomeCo nt rol l e r : Cont ro ller

puЫic ViewResu l t Index() {


ShoppingCart cart
= new Shoppin g Cart { Produ cts = Pr o du ct . GetP r 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;
}

Расширяющи й метод по имени Fil te rByPrice () принимает дополнительный


параметр. который позволяет фильтровать товары, так что в результате возвраща­
ются объекты 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 "Kaya k", Price = 275М} ,
new Product {Narne "Lifejacket ", Price = 48 . 95М),
new Product {Name =
"Soccer ball", Price 19.SOM}, =
new Product {Name = "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) {
i f ( (prod? . Price ? ? 0) >= mi n i mum Price) {
yield return prod ;

puЫic s ta tic IEnumeraЫe<Product> Fil terByName (


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 . Con t rollers
puЫic cla s s HomeContro l ler : Co n troll er

puЫ i c ViewResult Index() {


Produ c t[] p r oductArray = {
new Product {Name " Kayak ", Pr ice = 275М} ,
new Produ ct {Name " Lifejac ket ", Price = 48 . 95М} ,
new Product {Name " Soccer ba l l ", 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 Fil terTotal: С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;

Вторым аргументом метода Fi l ter () является функция, которая принимает объ­


ект Product и возвращает значение типа boo l. М етод Fil ter () вызывает эту фун­
кцию для каждого объекта Product и включает его в результат. если функция воз­
вращает true . Для применения метода Fil ter () можно указать метод или создать
автономную функцию (листинг 4.29).

Листинг 4.29. Использование функции для фильтрации объектов Product


в файле HomeController. cs

using Microsoft . AspNetCore . Mvc ;


us i ng System . Collections .Ge neric;
using LanguageFeatures . Models ;
using System;
namespace Language Featu res . Contro l ler s
puЫic class HomeController : Controller
104 Часть 1. Введение в инфраструктуру ASP.NET Core MVC

bool FilterByPrice(Product р) {
return (p?.Price ?? 0) >= 20;

puЫic ViewResult Index() {


Product [ ] productArray = {
new Product {Name " Kayak ", Pri ce = 275М} ,
new Product { Name " Lifej acket ", Price = 4 8 . 95М} ,
new Product {Name " Soccer ball" , Price 19.SOM} ,
new Product {Name " Corner flag", Price = 34 . 95М}
};

Func<Product, bool> nameFilter = delegate (Product prod) {


return prod?. Name? [О] == 'S' ;
} ;

decirnal priceFilterTotal = productArray


.Filter(FilterByPrice)
. TotalPrices () ;
decirnal nameFilterTotal = productArray
.Filter(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 = {
new Product { Name " Kayak ", Price = 27 5М} ,
new Product {Name " Life j acket ", Price = 48 . 95М} ,
new Product {Name " Soccer ball ", Price 1 9.50М} ,
new Product {Name " Corner flag ", Price = 34 . 95М}
1;
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 io ns.Generic ;
using Lang uage Features.Mode l 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 decima l ? 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 ;
using System ;
us ing System.Linq ;
namespa ce LanguageFeatures.Controllers
puЫic class HomeControlle r : Cont r 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() {


va r 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\у
.NET. чтобы можно было делать асинхронные НТТР-запросы. В листинге 4.37 показа­
но добавление, произведенное в разделе dependenci es файла proj ect. j son.

Листинг 4.37. Добавление ссылки на сборку в файле project. json

"dependencies ":
"Microsoft . NETCore.App" :
" ve r 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 .Conso 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 li e nt для запрашивания со­
держимого домашней страницы издательства Apress и возвращает его длину. Работа,
которая будет выполняться асинхронно , представлена в .NET как объект Ta sk .
Объ е кты Task строго типизируются на основе результата , выдаваемого фоновой ра­
ботой . Таким образом , при вызове метода HttpClient . GetAs yn c () получается объ­
ект Task<HttpResponseMes sage> . Он сообщает о том, что запрос будет выполнен в
фоновом режиме и его р езультатом будет объект HttpRespons eMe ssage.

Совет. Когда мы употребляем понятия вроде фоновый режим, то опускаем массу деталей,
чтобы подчеркнуть только ключевые аспекты, которые важны для мираMVC . Поддержка
.NET для асинхронных методов и параллельного программирования в целом превосход­
на, и ее рекомендуется внимательно изучить, если вы хотите создавать по-настоящему

высокопроизводительные приложения, которые могут извлекать преимущества от много­

процессорного или многоядерного оборудования . Вы увидите , как MVC облегчает созда­


ние асинхронных веб-приложений далее в книге по мере представления разнообразных
функциональных средств.

Самой непонятной для большинства программистов частью явля ется продолже­


ние, пр едставляющее собой механизм , с помощью которого указывается то, что долж­
но произойти, когда фоновая задача зав е ршится . В приведенном примере применяет­
ся метод ContinueWi th () для обработки объекта HttpResponseMe s sag e , получаемого
из метода Ht t pCli ent . GetAsync () . Это делается с использованием лямбда-выраже­
ния , которо е возвраща ет значение свойства, содержащего длину полученного от веб­
се рвера Apress содержимого. Вот код продолжения :

return httpTask . ContinueWith((Task<HttpRespons eMessag e > anteceden t ) => {


return a n tecedent . Res ul t . Content .He aders . Con tent Len g t h ;
}) ;

Обратите внимание , что ключевое слово r e turn встречается два раза. Именно
эта часть вызывает путаницу . Первое применение ключевого слова return указыва­
ет, что во звращается объект Ta s k<Http Re spons eMessage> , который при завершении
задачи возвратит (второе ключевое слово r e t u r n) длину из заголовка Conte n t Le ng th.
Заголовок ContentLength возвращает р е зультат long ? (тип long , допускающий зна­
чения nul l), т.е . результатом метода Ge tPage Length () является Ta s k< l ong?> :

puЫic static Task<long?> GetPageLength() {

Не переживайте , если все сказанное выглядит для вас бессмысленным - в этом вы


не одиноки. Как раз по такой причине в Microsoft и решили добавить в С# ключевые
сло в а , упрощающие р а боту с асинхронными методами.

Применение ключевых сло в async и awai t


Разработчики из Microsoft ввели в язык С# два ключевых слова, которые специаль­
но призваны облегчить использование асинхронных методов, подобных HttpClient .
GetAsync () . Ими являются async и a wa i t ; в листинге 4.39 показано, как с их помо­
щью упростить наш пример метода.
112 Часть 1. Введение в инфраструктуру ASP.NET Core MVC

Листинг 4.39. Применение ключевых слов async и awai t в файле М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 .GetAsync("http://apress.com");
return httpMessage.Content.Headers.ContentLength;

Ключевое слово awai t используется при вызове асинхронного метода. Оно сооб­
щает компилятору С# о том, что необходимо подождать результата Task, который
возвращается методом GetAsync (), и затем заняться выполнением остальных опе­
раторов в том же методе.

Применение ключевого слова awai t означает. что мы можем трактовать результат


метода GetAsync (), как если бы он был обычным методом, и просто присвоить воз­
вращаемый им объект HttpResponseMessage какой-нибудь переменной. Еще лучше
то, что затем можно использовать ключевое словоreturn традиционным образом для
выдачи результата из другого метода - значения свойства ContentLength в рассмат­
риваемом случае. Это намного более естественный подход, к тому же нам не придется
беспокоиться по поводу метода ContinueWith () и многократного при менения юпоче­
вого слова return.
При использовании ключевого слова awai t потребуется также добавить к сигнатуре
метода ключевое слово 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<ViewResul t> 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. GetPathLength (). О продолжениях позаботят­
ся инфраструктура MVC и платформа .NET, а результатом будет код, который легко
писать, читать и сопровождать. Запустив приложение, вы увидите вывод, похожий
на показанный ниже (хотя наверняка с отличающейся длиной, т.к. содержимое веб­
сайта Apress часто изменяется):

Length : 62164

Получение имен
Во время разработки веб-приложений есть много задач, в которых необходимо
ссылаться на имя аргумента , переменной, метода или класса. Распространенными
примерами являются ситуации с генерированием исключения или созданием ошибки
проверки достоверности при обработке пользовательского ввода. Традиционный под­
ход предполагал применение строкового значения с жестко закодированным именем

(листинг 4.4 1).


Листинг 4.41. Использование жестко закодированного имени в файле HomeController. cs

using Microsoft . AspNetCore . Mvc;


using System . Collections.Generic;
using LanguageFeat ur es . Model s ;
using System ;
using System.L inq ;
namespace LanguageFeatures . Controllers
puЫic class HomeController : Co ntro ller
puЬlic ViewRes ul t I ndex() {
var products = new [ ] {
new { Name = "Kayak", Price = 275М } ,
new { Name = "Lifejacket", Price = 48. 95М } ,
new { Name = "Soccer ball" , Price = 19. SOM } ,
new { Name = "Corner flag", Price = 34. 95М }
} ;
return View(products. Select(p => $"Name: {p.Name}, Price: {р. Price} "));

Вызов метода Se le ct ( ) из 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.Contro l lers
puЬlic class HomeC ontroller Contro l ler
puЫic ViewResult Index() {
var products = new [] {
new { Name " Kayak ", Price = 275М ),
new { Name "Lifejacket ", Price = 48.95М } ,
new { Name "S occer ball" , Price 1 9 . SOM } ,
new { Name "Corner 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 в контекст

Вопрос Ответ

Что это такое? Razor - это механизм визуализации, отвечающий за встраивание


данных в документы HTML
Чем он полезен? Возможность динамической генерации содержимого являет-
ся неотъемлемой частью процесса написания веб-приложения .
Механизм визуализации Razor предоставляет средства, которые
упрощают работу с остальной инфраструктурой ASP.NET Core MVC с
применением операторов С#

Как он используется? Выражения Razor добавляются к статической НТМL-разметке в


файлах представлений. Эти выражения оцениваются для генерации
ответов на клиентские запросы

Существуют ли какие­ Выражения Razor могут содержать почти любые операторы С#. Иногда
то скрытые ловушки может быть трудно решить, должна ли логика принадлежать представ­
или ограничения? лению или контроллеру, что способно разрушить принцип разделения
обязанностей, который является центральным в паттерне MVC

Существуют ли В главе 21 объясняется , как написать собственный механизм визу­


альтернативы? ализации . Доступны механизмы визуализации от независимых пос­
тавщиков, но они обычно полезны только в ограниченных ситуациях
и для них не гарантируется долгосрочная подцержка

Изменился ли он по Механизм визуализации Razor работает в значительной степени


сравнению с версией таким же образом, как и в MVC 5, но с рядом удобных улучшений.
MVC5? Файл импортирования представлений используется для указания
пространств имен, в которых будет производиться поиск типов при
обработке представления , а также для определения мест, где нахо­
дятся дескрипторные вспомогательные классы (глава 23)
116 Часть 1. Введение в инфраструктуру ASP.NET Core MVC

В табл. 5.2 приведена сводка по главе.

Таблица 5.2. Сводка по главе

Задача Решение Листинг

Доступ к модели представления Используйте выражение @Model для опре­ 5.6, 5.15,
деления типа модели и выражение @rnodel 5.18
для доступа к объекту модели

Использование имен типов без Создайте файл импортирования 5.7, 5.8


их уточнения с помощью про­ представлений
странств имен

Определение содержимого, Применяйте компоновку 5.9-5.11


которое будет использоваться
множеством представлений

Указание стандартной Используйте файл запуска представления 5.12-5.14


компоновки

Передача данных из контролле- Применяйте объект ViewBag 5.16,5.17


ра представлению за предела­

ми модели представления

Выборочная генерация Используйте условные выражения Razor 5.19, 5.20


содержимого

Генерация содер жимого для Применяйте выражение @foreach 5.21, 5.22


каждого элемента в массиве механизма Razor
или коллекции

Подготовка проекта для примера


Для демонстрации работы механизма визуализации 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 platform 11
},
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 .Ex tensions.Logging .Console 11 : " 1.0.0 11 ,
"Microsoft . AspNetCore.Mvc 11 :
11
1.0.0 11
},
Глава 5. Работа с Razor 117
После сохранения изменений , внесенных в pr oj ect . j son, среда Visual Studio
добавит в проект сборку Microsoft. AspNetCo re. Mvc . Далее мы включаем инфра­
структуру 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 .Dependency inj 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 : Contro ller

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/ Home и добавляем в нее файл типа MVC View Page (Стр аница пр ед ­
ставления MVC) по имени Index . cs html, куда помещаем содержимое, показанное в
листинге 5.5.

Листинг 5.5. Содержимое файла Index. cshtml из папки Views/Home


@model Razor . Models . Product
@{
Layout = nul l;

< ! DOCTYPE html >


<h tm l >
<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;

< !DOCTYPE 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), а доступ к свойству Name производится с
применением @Model (с прописной буквой М). Поначалу зто может немного запутывать,
но со временем станет вполне привычным .

Запустив приложение, вы увидите вывод, представленный на рис. 5.2.

[j lndox х

"·· С [QS-;-~~ 1110~1:w7sз --~--------==*l

~
-------·· ' . . ·-·-- -
Kayak
---· ·------.·~------·--·-·--··~-·---·-w-·--w-·--·---*-"•-...,•-•

Рис. 5.2. Результат чтения значения свойства внутри представления

Представление, которое использует выражение @model для указания типа, назы­


вается строго типизированным представлением. Среда Visual Studio способна при­
менять выражение @mode l для открытия окна со списком предполагаемых имен чле­
нов, когда вы вводите @Mode l с последующей точкой (рис. 5.3).

Razor - 1:1 Х

, ~d el R~:tor ./-lodels . Product


l+-
@{
L~yo u t ~ null;

<! ООСТУРЕ html >


~
B <html>
. B <t,ead >
·i <me ta nэrr.e•"viewport" contenta'·\-..idth=device·\"lidth'' />
i <title>l ndex</title>
• l </head >
B <body >
i l!t'lodel .J11
1</Ьоdу> .,,, Category
[ </html> -" Oe<cription
Ф Equols
_ _,.._ _ _ ___tl· Ф GetType
100% - ~
Gt:tHashCode i
Ф

"' ---~­
,,, Price
-" ProductlD
,Ф . ToS.tring

Рис. 5.3. Среда Visual Studio предлагает список предполагаемых имен членов
на основе выражения @Model

Список предполагаемых имен членов, отображаемый Visual Studio, помогает из­


бегать ошибок в представлениях Razor. При желании вы можете игнорировать эти
предположения, и тогда Visual Studio будет подсвечивать проблемные имена членов,
Глава 5. Работа с Razor 121
чтобы вы внесли исправления, как поступает в обычных файлах классов С#. Пример
подсветки проблемы можно видеть на рис. 5.4, где мы пытается сослаться на свойство
@Mode l . No tARe alProperty. Среда Visual Studio обнаруживает, что класс Produc t,
указанный в качестве типа модели, не содержит такого свойства, и подсвечивает
ошибку в редакторе .

. 1· , r.
FJ<head)
l <rneta nan;e ="vie1~port " conten t " "1~idtl1=device-widt h " />

l <title>I nd ex</title>
</head>
Э <Ьоdу>
@f'tode l . ~e~y,,

· l </body>
</html>
~...; ,..," ".~, ....
~
.м. ~
r
Рис. 5.4. Среда Visual Studio сообщает о проблеме с выражением @Mode l

Использование файла импортирования представлений


При определении объекта модели в начале файла Index . cshtml необходимо было
включать пространство имен, которое содержит класс модели, например:

@model Razor.Models.Pr oduct

По умолчанию все типы, на которые производится ссь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

ASP.NEТ
• Тур~: ASP.N~
MVC View l •yout Page
Clitnt·side
MVC Vie\v lmpc
Code
MVC у;..,, St4rt Page ASP.NEТ t
t> Online
J'!

·.
ASP . NП Ccnfiguration File ASP .NEТ

~е to 90 online and find templates.

Nыn<: _Viewlmports.c shtn' I

Рис. 5.5. Создание файла импортирования представлений

Листинг 5.7. Содержимое файла_Viewimports. cshtml из папки Views


@using Razor . Mode 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= "v i ewpo r t " content=" wi dth=de v i ce - width " />
<t i tle> I ndex</title>
</ head>
<body>
@Mo del . 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
Client-s id• ~·
fl;
MVC Controller Closs ASP . NEТ
Туро: AS P . NEТ

MVC Vi.,,v Loyout Pog


Code
~· Web API Controller Cla1S ASP.N EТ
fl- Online
fl;
i


. МVС View lmpor1s Poge ASP.NET


fl; Ritzor Tag H ~ l pe r ASP.NET
"'
Cljclc here to go onlioe itnd find tempi<!t es.

_Bo sicl"yout.csl,tml

Рис. 5.6. Создание компоновки

Листинг 5.9. Начальное содержимое файла _BasicLayout. cshtml


<!DOCTYPE html>
<html>
<head>
<meta name="viewport " content="width=device-width" />
<title >@ViewBag.Title </title>
</head>
<body>
<div>
@RenderBody ()
</di v>
</body>
</html>

Компоновки - это специализированная форма представлений; в листинге 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 Informa tion</hl>
<div id="mainDiv">
@RenderBody ()
</div>
</body>
</html >

Здесь был добавлен элемент заголовка, а также стиль CSS для стилизации со­
держимого элемента di v, в котором находится выражение @RenderBody (), прос­
то ради прояснения, какое содержимое поступает из компоновки, а KaJ{Oe - из

представления.

Применение компоновки
Чтобы применить компоновку к представлению, понадобится установить значение
свойства Layou t и удалить НТМL-разметку, которая теперь будет предоставляться
компоновкой, такую как элементы html, head и body (листинг 5.11).

Листинг 5.11. Применение компоновки в файле Index. csh tml


@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С ищет файл по имени
_ ViewStart . 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

Client~side
MVC View l"yout Page ASP.NEТ
. Тур е: ASP.N ET
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 online § ПО [ind A~ml2; 1 c1t~ .

№me: _ViewStart.cshtml

Рис. 5.8. Создание фай л а запуска представления

Листинг 5.1 З. Применение стандартного представления в файле_ViewStart. cshtml


@{
Layout = "_BasicLayout";

Поскольку файл з апуска представления содержит значение для свойства Layout,


можно удалить соответствующее выражение из файла Iпd e x . c shtml (листинг 5.14).

Листинг 5.14. Обновление файла Index. csh tml, позволяющее отразить


использование файла запуска представления

@model Product
@{
ViewBag . Ti t le = " Pr oduct Name";

Produc t Name: @Model.Name

Специально указывать, что должен применяться файл запуска представления, не


требуется . Инфраструктура MVC автоматически обнаруживает данный файл и ис­
пользует его содержимо е . Преимущество отдается значениям , определяемым в фай­
ле представления, что делает переопределение файла запуска представления очень
простым.

Можно также применять несколько файлов запуска представлений , чтобы устано­


вить стандартное поведение для разных частей приложения . Механизм Razor ищет
самый близкий файл запуска для обрабатываемого представления, а это значит, что
стандартные настройки можно переопределять, добавляя файл запуска представле­
ния в папку View s /Home или Views / Share d.
128 Часть 1. Введение в инфрастру ктуру ASP.NET Core MVC

Внимание! Важно понимать разницу между отсутствием свойства Layout в файле пред­
ставления и его установкой в nu ll. Если представление является самодостаточным, и
вы не хотите применять компоновку, то установите свойство Layout в n ul l. Если же
просто опустить свойство Layou t , то инфраструктура MVC будет считать, что компоновка
вам необходима, и она должна использовать значение, которое найдет в файле запуска
представления.

Использова ни е в ы ражен и й Razor


Теперь , когда вы видели основы представлений и компоновок, давайте переклю­
чимся на другие виды выр ажений, поддерживаемые Razor, и посмотрим , к а к их при­
менять для создания соде ржимого представлений. В хорошем приложе нии MVC им е ­
ется четкое разделение между ролями метода действия и представления . Роли просты
и кратко описаны в табл. 5.3.

Таблица 5.3. Роли, исполняемые методом действия и представлением


Компонент Что делает Чего не делает

Метод действия Передает представлению объект Не передает представлению


модели представления сформатированные данные

Представление Использует объект модели пред­ Не изменяет ни одного аспе кта в


ставления для отображения со­ объекте модели представления
держимого пользователю

Далее в книге мы будем неоднократно возвращаться к этой теме. Чтобы извлечь


максимум из MVC , необходимо соблюдать и обеспечивать разделение между разными
частями приложения. Вы увидите , что механизм Razor позволяет делать очень мно­
гое , включая применение операторов С# , но вы не должны использов ать Razor для
выполнения бизнес-логики или манипулирования объектами моделей предметной
области.
В качестве простого примера в листинге 5.15 демонстрируется добавлени е нового
выраж ения в представление I n d ex.

Листинг 5.15. Добавление выражения в файл Index. csh tml


@mode l Produ 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>Product 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 = "Watersports ",
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~ Х

С ~- localho;GO~ - ---- - - - - - - - · - - _j] ~-1


1

1 P1·oduct I11fo1·matio11
1

1 Product Name: Kayak 1

1 ::::::::~~е2 $275 00 1

iL_..._- -__- - - - -_--__-_-_=:_-:_-_-_-_-_-_-_-__-_-_-_- _-_::_- -._-:-:-:-:-:-:..._,


1

Рис. 5.9. Применение выражений Razor для вставки значений данных

Установка значений атрибутов


Во всех рассмотренных до сих пор примерах устанавливалось содержимое элемен­
тов, но выражения Razor можно использовать также для установки значений атри­
бутов элемента . В листинге 5.18 демонстрируется применение выражений @Model и
@ViewBag для установки содержимого атрибутов в элементах представления Index.
Глава 5. Работа с Razor 131
Листинг 5.18. Использование выражений Razor для установки значений атрибутов
в файле Index. cshtml
@model Product
@{
ViewBag . Title = " Product Name ";

<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 пр и меняются для установки знач е ний некот орых атрибутов
данных в эл ементе di v.

Со в ет. Атрибуты данны х , которые представляют собой атрибуты с именами, начинающимися


на da ta - , в течение многих лет были неформальным способом создания специальных
атрибутов и затем сделаны частью формального стандарта 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 - product i 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 witc h ( (int)Vi ewBag . StockLevel )


case О :
@: Ou t of Stock
brea k;
case 1 :
case 2 :
case 3 :
<b>Low Stock (@Vie wBag . StockLeve l)</b>
b re ak ;
defaul t:
@: @ViewBag. StockLeve l in Stock
brea k;

< /р >
</ div>

Чтобы начать условный оператор , перед условным ключевым словом С# (в этом


примере 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 ;


us i ng Raz or. Mode ls;
namespace Razor . Controllers {
pu Ы ic c l a s s HomeCont r ol l er : Con troll e r

pu Ыi c I Actio nRes ult Index () {


Product [] array = {
new Product {Name = "Kayak", Price = 275 М},
new Product {Name "Lifejacket", Price = 48. 95 М},
new Product {Name "Soccerball", Price 19.50М},
new Product {Name "Corner flag", Price = 34.95 М}
} ;
return View(array);

Метод действия Index ( ) создает объект Product [] , который содержит простые


значения данных, и передает его методу Vi ew () для визуализации с применени ем
стандартного представления. В листинге 5.22 изменен тип модели для пр едставления
Index и с помощью цикла for e ach производится проход по объектам в массиве.

Листинг 5.22. Проход по массиву в файле Index. csh tml

@mode l Product[J
@{
ViewBag .T it l e "P roduct Name ";

<taЬle>
<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 .
Глава 5. Работа с Razor 135

['j Product №me Х

. С Гф lo-ca-111-os-t.60
- 75-=3=====
-- -- - ~-==---=----=:-:-_-:::::-::;::_-:::;:::::::-_-:-::::::::-:..-._-________- - - -

Product Info1·mation

Name Price
Kayak $275.00
Litej acket $48.95
Soccer ball $ 19.50
Согпе1· flag $34.95

----------------
Рис. 5.11. Применение Razor для прохода по массиву

Резюме
В этой главе был предложен обзор механизма визуализации Razor и показано, как
его использовать для генерации НТМL-разметки. Мы взглянули, каким образом ссы­
латься на данные, передаваемые из контроллера, через объект модели представления
и объект Vi ewBag, а также продемонстрировали применение выражений Razor для
настройки ответов пользователю на основе значений данных. В оставшихся главах
книги вы увидите много разных примеров использования Razor, а в главе 21 найдете
подробное обсуждение функционирования механизма визуализации МVС. В следую­
щей главе будут описаны некоторые средства, предлагаемые Visual Studio для работы
с про ектами ASP.NET Core MVC .
ГЛАВА 6
Работа с Visual Studio

в
этой главе рассматриваются основные средства, предоставляемые Visual Studio
для разработки проектов ASP.NET Core MVC . В табл. 6.1 приведена сводка по
главе.

Таблица 6. 1. Сводка по главе

Задача Ре шение Листинг

Добавление к проекту пакетов Отредактируйте раздел зависимостей 6.1-6.6


.NET (dependencies) файла proj ect . j son
или воспользуйтесь инструмен том NuGet

Добавление к проекту пакетов Создайте файл bower. j son и добавь­ 6.7, 6.8
JavaScript или CSS те требующиеся пакеты в его раздел
dependencies
Просмотр результатов изменения Используйте модель итеративной 6.9-6.11
представления или класса разработки

Отображение детальны х сообще­ Используйте страницы исключений 6.12


ний в браузере разработчика

Получение детальной информа­ Используйте отладчик 6.13


ции и контроля над выполнением

приложения

Повторная загрузка одного или Используйте средство Browser Link 6.14-6.16


большего числа браузеров с при­ (Ссылка на браузер)
менением Visual Studio
Со кращение количества НТТР­ Используйте расширение Bundler & Minifier 6.17-6.28
запросов и ширины полосы про­ (Упаковщик и минификатор)
пускания, требуемой для файлов
JavaScript и CSS

На заметку! Ка к объяснялось в главе 2, в Microsoft заявили , что в будущих выпусках Visual


Studio изменят инструменты , которые применяются для создания приложе ний ASP. NEТ
Core. Это означает, что приводимые в данной главе инструкции могут устареть . Проверяйте
веб-сайт издательства на предмет пересмотренных инструкций, которые я напишу, когда
новые и нструменты станут стабильными .
Глава 6. Работа с Visual Studio 137

Подготовка проекта для примера


Для ц елей настоящей главы мы создали новый проект ASP.NET Саге Web
Application (.NET Саге) (Веб-приложени е ASP.NET Core (.NET Core)) по имени
WorkingW it hVisualStudi o, и спол ьзуя шаблон Empty (Пустой). Мы добавили сборку
МVС з а счет редактирования раздела dependenc i es файла proj ect. j son (листинг 6.1).

Листинг 6.1. Добавление сборки MVC в файле proj ect. j son

"dependenc i es " : {
"Microsoft . NETCore . App":
" version ": 11
1 . 0.0 ",
"type ": "platform "
}'
"Mi c r osoft . AspNetCo r e . Diagnostics": "1. О . О ",

"Microsoft . AspNetCore . Server .I ISi ntegra t ion ": " 1 . 0 . 0",


"Microsoft . AspNetCore . Server . Kestre l": "1. 0 . 0 ",
"Microsoft . Ext ens i ons .Logging .Conso l e ": "1. 0 . 0 ",
Мicrosoft . AspNetCore. Mvc" : 11 1. О. О 11
11

},

З атем мы включили инфраструктуру MVC с ее стандартно й конфигурацией в фай ­


ле Startup . cs (листинг 6.2).

Листинг 6.2. Включение инфраструктуры MVC в файле Startup. cs


using Microsoft . AspNetCore . Bui lde r ;
using Microso f t . AspNe t Co r e . Host i ng ;
using Microsoft . AspNetCo r e . Http ;
using Mi crosoft .Extens i ons .Depe ndenc yinj ecti on ;
using Microsoft . Extensions . Logg i ng ;
namespace Worki ngWi thVi sualStud i o
puЫic class Sta r tup {
puЫ i c void ConfigureSe r vices (I Servi ceCo l lect i on se r v i ces) {
services.AddМvc();

puЫic void Configu r e (IAppl i cati onBuilder ар р, IHost ingEnv i ro nment env ,
ILoggerFact ory l oggerFact ory) {
app.UseMvcWithDefaultRoute();

Созда н ие м одел и
Со здайте папку Mode l s и добавьте в нее файл класса по имени Product . cs с оп­
редел е ни ем, показанным в листинге 6.3.
138 Часть 1. Введение в инфраструктуру ASP. NЕТ Саге MVC

Листинг 6.3. Содержимое файла Product. cs из папки Models


namespace Worki 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


using Microsoft . AspNet Core . Mvc ;
using WorkingWithVis ualStud i o . Model s;
narnespace Wo r ki ngW i thVis ua l Studio . Cont r o l lers
puЬlic class HorneCont r ol l er : Cont r oll er {
puЫic IAc t ionResu lt Inde x( )
=> View(SirnpleRepository . SharedRepository . Pr oducts) ;

Зд е сь имеется един ственный метод действия Index () ,который получает все объ­
екты модели и передает их методу Vi ew () для визуализации стандартного представ­
ле ния. Чтобы добавить это пр едставление, создайте папку Views/Home и поместите
в не е файл представления по имени I n dex . csh tml, содержимое которого приведено
в ли стинге 6 .6.
Листинг 6.6. Содержимое файла Index. cshtml из папки Views/Home
@model IE n u rne r aЫe< W o r k i ngW i t hVisu a l S tud i o.M od els. Prod u c t >

@{ Layout = null ;
< !DOCTYPE htrnl>
<html>
<head>
<meta name= "v iewport " c ont ent=" width=device - wi dth " />
<title>Working with Vis ual St udi o</t i t le >
</head>
<body>
<tаЫе>

<thead>
<tr><td>Narne</td><td >P r ice </td></ t r>
</thead>
<tbody>
@fo r each (var р i n Mode l )
<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аще Price
Kayak 275
Lifejacket 48.95
Soccer ba ll 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 выполняет код .

.NET Core - это небольшая оптимизированная исполняющая среда, первоначально созданная


специально для ASP.NET, но теперь она играет более широкую роль для других типов приложений
.NET. Она была спроектирована как межплатформенная и предоставляет возможности для раз­
вертывания приложений ASP.NET за пределами традиционного набора платформ Windows, а так­
же в легковесные облачные контейнеры, подобные Docker. Исполняющая среда .NET 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 Manage Packages for Solulio n


St.tr t h lt tft "[) . о

- Mltro,oft.As pNetCote.Dlllgnostlcs Ьу Mlooio11.o\lp№tC01t.O!•gn1a1ш


8'о ASPJ~fТ Co1t middfcw1rt for U(1:pt>0n hcrtdГ.ntJ, e:ctci1tion df1pl.y p•ljt\, 111d
v l .0.0
."8 Microso r.t.AspNetCore.Mvc

Vщlon(,) · 1
j
5
di.1gn"'1k~ 1nfo11Ntion. Jrn:lvdes devtloptr U<tp!!On P'!i" m1ddJtw~te, tнq}tiOl'I h•n~.
. 10
' 0
P1ojtct :_---~-:i
Ut\Vlor1cincJт'MhVi~u 1.0.О 1

Mlcrosoft.AspNetCore.Sc:rver.llSJntegratlon bylA1c10'.0fl.AsJJfle1Co1t.Sm·t _..i .o.o


1 1 1
t!) ASP.NП Co•t c.cmpotм:nti fc1 wo1 l~n9with1~ h~ д!pNt1CcrtM<1dul•.

1
18
~
Mlcrosoft.AJpNe1Core.S.rvtr.llSlntegration.Tools ЬуМЮ> v!Л.O·prlE'М"NHin11
llS 1rittg1.1tion yublish t.:io~ for .NfТ Cort ClL Cant11n' tht dctntl·puhll,h·
------- __ :J1
lllt1~•1t 111 comn11rn:1 lc1 pЩ:lnking wdi 1ppl1c.1Ucrn !о bt hoottd ustng ltS. lм t.llled: 1 .!1.О

M/crosoft.Asp№tCore.Scrvcr.Кo1trel ЬyM•c101oh.д1ptj(1Ca•c.StNcr.K~t1tl vl.Cl.O


• /\SP.Шf Ccrt ~tsщl cro~~·pl1tfc1mwct- ltNtt.

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
O t:Кrlpllon

ASP.IJE T CottMYC i1.i web fщТitio1cnl: t~t 9r1n


111 MlcroJoft. NEТCore.App ЬуМюоw.t ~ 1.0.0 you1 p o,.."ttfu~ p1lltt,.,1·b.1~«iw1ytobuild

~ д tti cf .NET lJll"~ INI olt' mduded ln the dt!1i.rlt .NП Cort f IK411on modrL 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
dependen cies 11
: {

Microsoft . NETCore.App ":


11

"version "1.0.0",
11
:

"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": 1.0.0 11 11
,

Microsoft . AspNetCore.Mvc
11
1.0.0" 11
:
11

} '
Каждый пакет указывается с использованием его имени и обязательного номера
версии. Некоторые пакеты, такие как Microsoft . NetCore . Арр в примере про екта,
имеют дополнительную конфигурационную информацию, как объясняется в главе 14.
Ср еда Visual Studio отслеживает содержимое файла proj ect . j son, т.е. вы можете до­
бавлять или удалять пакеты , редактируя файл напрямую, что и делается повсеместно
в книге , т.к. это помогает гарантировать получение ожидаемых результатов , если вы

прорабатываете примеры.
Когда вы применяете NuGet для добавления в проект какого-то пакета, он автома­
тически устанавливается наряду со всеми пакетами, от которых зависит. Исследовать
пакеты NuGet и их зависимости можно, открыв в окне Solution Explorer элемент
References (Ссылки), который содержит записи для всех пакетов NuGet в файле
proj ect . j son. В результате развертывания записи пакета отображаются пакеты, от
которых он зависит (рис . 6.3).

Solution Explorer • CJ х

Ф Ф_@ I ~ -1. ~-r~ ! ~-


3..:~г:!:~о~:ХУ~!~:.:) _ ·--
•·1 Referenc~
• ili .NEТCoroApp,Vorsi on:v1 .0
t> ·~ Microsoft.AspNetCore.Oiagnortics (1.0.0)

~ ·~ MicrosoftAspNetCo r e.М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)
·~ M i crosoft.Asp№tCore.Мvc . Rdzor (1.0.0)
0

1lj Microsoft.AspN<Kore.Mvc.TogHelpers (1.0.0)


·~ Micros: oft.AspNetCore. Мvc .ViewFeature.s
(1.0.0)
·~ Microsoft.Extensions.Caching.Memory {1.0.0)
·~ Microsoft.Extensions.Oependencylnjection (1.0.0)
•·• Microsoft.Asp№tCore.Mvc . dl l
~
0

fl! Microsoft .AspNetCore.Se.rver.llSlnt~ration (1.0.0)


·~ Microsoft .Asp№tCor~Server.Kestrel (1.0.0)
'llj Microsoft.Extensions.Logging.Console (1.0.0)
il} Microsoft.NEТCore.App {1.0.0}
0

·~ Microsoft,Visual Studio.\Чeb.Browserlink. l oader (14.0.0)

Рис. 6.3. Раздел References окна Solution Explorer


Глава 6. Работа с Visual Studio 143
На рис. 6.3 Mi cro s o ft. AspNetCor e . Mvc в лис­
видно, что при добавлении пакета
тинге 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 rki ngW i thVisua l Studio в окне
S ol u ti oп Explo re r, выберите в контекстном меню пункт Add<=:>New lte m (Добавить<=:> Новый
элемент) и укажите шаблон элемента Bowe r C o п figurat io п File ( Файл конфигурации
Bower) из категории Clie пt - s i d e (Клиентская сторона), как показано на рис. 6.4.

Sort Ь у: Def•ult
ASP.NET • Туре: Client-side
TypeScript JSON Configur•tion File Client-sido
(lient-side
Bower Configuration
Code and .bo1.;verrc .

~ Online
n рП1 Co11figuration File С lie n\ · •ide

rJs Gulp Configurotion File Client-side

rJS Grunt Configur•tion File ClienHide

п JSON Schemo File Client-side

гJs
JSXFile Client-side
<iiil>J
(lick here to go opline •nd find !empla\б.

Рис. 6.4. Созда н ие фай ла кон фи гу р ации Bower

На заметку! Дл я з а груз ки п акето в кл ие н тс ко й сторо н ы Bower используе т инстр у м е нт g i t.


Чт об ы обеспечит ь кор ректн ую р аб оту Bower, пот ребуетс я з ам е нить верс и ю gi t из Visual
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 on; чтобы его было видно, нуж­
но щелкнуть на кнопке Show All Files (Показать все файлы) в верхней части окна Solution
Explorer.

Листинг 6. 7. Стандартное содержимое файла bower . j s on

"name ": "asp .n et ",


"p rivate ": t ru e ,
"depende nc ie s ":
}

В листинге 6.8 демонстрируется добавление в файле b ower . j s on пакета клиент­


ской стороны, что делается включением записи в раздел dependenc i es с примене­
нием такого же формата , как в файле p r o j ec t. j so n.

Совет. Хранилище пакетов Bower находится по адресу h ttp: //bower . io/ sea r ch, где
можно искать пакеты для добавления в свои проекты.

Листинг 6.8. Добавление пакетов в файле bower. j son

"name ": "as p.net ",


"p rivate ": t r ue ,
"depende nc i 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 • Solution •
Schema: http://json.schem astore.org/ bower
в{ - - -
\ "narne" : "asp . net" ,
"p1· ivate" : t г ue ,
1
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 owe r . j son м огут указываться в виде диапазо­
на разнообразными способами, наиболее полезные из которых описаны в табл. 6.2.
Самый безопасный способ указания пакета предусматривает использование явного
номера версии. Это гарантирует, что вы всегда будете работать с той же самой верси­
ей до тех пор, пока пр еднамере нно не обновите файл b ower . j son для запроса другой
в е рсии.

Таблица 6.2. Ра спространенные форматы для номеров версий в файле Ьower. j son
Формат Описание

3.3.6 Выраже ние номера версии напрямую приведет к установке пакета с точно
совпадающим номером версии, например , 3.3. 6

* Применение символа звездочки позволяет инструменту Bower загружать и


устанавливать любую версию пакета

>3 . 3 . 6 Снабжение номера версии префиксом > или >= позволяет инструменту
>=3 . 3 . 6 Bower устанавливать любую версию пакета, которая больше либо больше или
равна заданной версии

<3.3.6 Снабжение номера версии префиксом < или <= позволяет инструменту
<=3 . 3 .6 Bower устанавливать любую версию пакета, которая меньше либо меньше
или равна заданной версии

-3 . 3 . 6 Снаб жение номера версии префиксом в виде тильды (символ - ) позволяет


инструменту Bower устанавливать версии , даже если номер уровня исправ­
лений (последнее число в номере версии) не совпадает. Например, указание
-3 . 3. 6 позволяет инструменту Bower устанавливать версию 3.3.7 или 3.3.8
(которая будет исправлена до версии 3.3.6), но не версию 3.4.0 (которая бу ­
дет новым младшим выпуском)

лз . 3 . 6 Снабжение номера версии префиксом в виде символа л позволяет инстру­


м енту Bower устанавливать версии, даже если номер младшего выпуска (вто­
рое число в номере версии) или номер исправления не совпадает. Например,
указание л 3 . 3.О позволит Bower устанавливать версии 3.3.1 , 3.4.0 и 3.5.0,
но не версию 4.0.0

Совет. Для примеров в настоящей книге мы создали и отредактировали файл bower . j son
напрямую . Редактировать файл очень просто , к тому ж е это способствует получению
предсказуемых результатов в случае проработки примеров. Среда Visual Studio также
предоставляет графический инструмент для управления пакетами Bower, который можно
открыть, щелкнув правой кнопкой мыши на проекте WorkingWithVisualStudio в окне Solution
Explorer (родительский элемент файла b ower. j son ) и выбрав в контекстном меню пункт
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).

"'1:1 х

... ii.J Bower


~ ~ bootstrap (3.3. б)
~ jquery (2.2.4)

Рис. 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; }
< ! DOCTYPE html>
<htrnl>
<head>
<meta name =" v i ewport " content="width=device- width " />
<title>Wo rking 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 .Name</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 1
1
L --- ·---·--__)
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

1
Lifejacket $48.95
Soccer ball $19.50
Соше.1' flag S34.95
'------------------------
Рис . 6.9.
_J
Автоматическая компиляция классов

Средство автоматической компиляции удобно, когда все идет по плану . Его недо­
статок в том, что ошибки этапа компиляции и времени выполнения отображаются в
браузере, а не в 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 "L ifejacket ", 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 .Hosting ;
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.
NL1 llRefe renceExceptioп: Ol)jec 1·eference r.ot set to an instance of an object.
<lndex>b_ O_O in HomeCon t ro ll e r. c:.. line 10
I~

• Que1y Cookies Heade1s

NullRefereпceException: Object refere1кe rюt set to ап instance of an object.


<lr.·:le<> b_O_C •n Hon1eCont:roller . cs
10. . \olhere(p •> р.Р гiс е < 50)) ;

f/ cv~№xt

~l o·,el·J ext

L
1r, Index . cs ht ml

··--·--···---:!
17 . @foгeac h (va 1· р 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, т.е. необработанное исключе­
ние Null Refere n ceExcept i o n, которое генерируется в классе S i mpleReposi tory ,
прервет приложение и передаст управление выполнением отладчику (рис. 6.11).

Ho meCont r oП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'.
using Worki n gWithVis ualStudio .М<Юtl::o ; r,--
. ....,.....,.._.,,,...~--:-""::..,,......,,.., __~ -~.-.----;i
l using Syste•.li nq; /-1.'..-~~~.!!;~ «-~c,epttono<tut~-· _.·_. ---~---21
j &:ctpdon thtown: "Systtm.Nul\Refe1ence.f.'щ1ption' in 1
1 89 n1и1esp\1Ьli
pace Wo гkingWithVi:иJ 1йStudi o . Cont rollers {
c class 11-)!l'eCм·uol.t~r : Contrclle { /
,/ l Wo ikingW! tliViм'5 tud'ю.dlr
1
J /' / Additi"onal lnfo~tion: OЬjr.ct rr.ferencl! oot sd to an inst.sn<.t of an objC!.ct.'.
puЬlic !1\tticnRc!.iJlt Index(} · -· -·- --"-------. ...,.~- --~ - -- - ..... ··-- ·• -·
•> Vitw( 51ro:rlt'1'to;>cs 1 tor}·· Sh!l':'C!~R!Po~J.~y.Products ! ~~~:~~-
· - - - - - - - - - - - - ----··-··--
. 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 • nt O\'м kt)'lf'.Old tc ctt<!tt dn cbjкt 1 nst •nt~

'1

lJ_~_!t'~~~~~lp fo~-:~~~.~~~-·---···-·----~···--··-=-~
1

~rch for n1ore Н~р 01'il1ne...


1 "- - - --· -- - -- -- - •• " --· -- . " _" -.
1 b c ~ptlon ~ t1 ings-:
! f2J Bruk \.\th~ n tJш rxc.ttp ti 11 м t).•pe i: thгa 1vn
1 д,..;;;- - -- . -- - .. ----- -·-·- "_,_ _, -·. "----,
1 v""o",;1 ". j
о/о . " -i (О р/ e.~ce ptщn detщ l to the cl1pbocr1d
100 1
" _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _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
Чтобы создать точку останова. щелкните правой кнопкой мыши на операто­
ре кода и выберите в контекстном меню пункт Breakpointqln se rt Breakpoint (Точка
остановаqВставить точку останова). В целях демонстрации поместите точку останова
на метод AddProduct (} в классе SimpleReposi tory (рис. 6.12).

o<J V-/ork1n9V11thVi~ualStud10 - Г-'J Х

[!) V/orkingWrthVisuaLSt~_dio"NEТCoreApp, Ver5ion •] ~ \t./crlungW1thViiu~IStudio.Mode:ls. Simple:Reposi • [ 19 Simpl~('.poяtoty{)


1
'$ puЫic S!mploRopository() {
vа г i niti.s lit ems • new[ ] {
1 ne\'l Pr·oduct { l~ a~ • "Kayak" 1 Price " 27S~' } , '
li n e't'1

new
Product { tlame .- "Li fe jacket" 1 Pric e • 48.95М },
11ew P r od tн:t { N am~ • "Soc.ce1· ball" , Price'"' 19.50!'1 } ,
P rodщ:t { fJame • ' ' C oгner flag" , Ргiсе '"' 34.9SМ }

1
~ 1
};
1 foreach (var р in initi.s lit ems ) {
AddProduct(p) ;

products . Add( "Error" , null); •


11

ll,
puЫic П1\ul!'. eг aЫ~ < Product > Products "'> products . Vii!l lues ;

• puЫic 11o id AddP roduc t( Produtt р) ""> P:ffAМфhfdl!фW3J;

100 8.'7 ..
Рис. 6.12. Создание точки о стано ва

Выберите пункт меню Deb ugc::>Start Debu gging (Отладка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~rkingWit hVis~11IStu dio .. NETCoreApp.Version " ~ Wo~ki~gWitl1VisuolStudio.Modei~:SimpleReposi . ...
nl!\'l Pl"'Qduct { Name • "Corner flag " , Price • 34.9S.Ч }
};
foreaich (vl!lr р in initi alitems ) {
+ 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}
1
pr-od ucts .Add( "Err ci r " 1 rшl l) ; 1" p.Name Р '" "Koyak"
#11 p.P rice 275

Gliil р {WorkingWrthVisualStudio.Mod els.Product}


JI p.Name Р • " Кауаk"
JI p.Price 275

Рис. 6.1 З. Инспектирование значения данных

Наведите курсор мыши на переменную р щелкните на кнопке с изображением


канцелярской кнопки, чтобы закрепить ссылку на Product . Разверните закреплен­
ную ссылку. чтобы также закрепить свойства Name и Price, получив в итоге резуль­
тат, представленный на рис. 6. 14.

lf!J Work1n9WithVisuolStud10"NEТCort.App, Vers1on ~ \ otk1n g\V1thV1"u"JStud 10.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~ р {WorkingWithVisualStudio.Models.Product}
JI p.Name Р " "lifejacket"
/1 p.Price 48.9S
Рис. 6.15. Наблюдение за изменением состояния
с применением за к реплен н ых значений

Использование окна Locals


Связанным средством является окно Locals (Локальные) , которое открывается пу­
тем выбора пункта меню Debugr::>Wiпdows r::> Locals (Отладкаr::> Окнаr::>Локальные). В окне
Locals отображаются значения данных похожим на закрепленные значения образом, но
6.16) .
здесь присутствуют все локальные объекты, относящиеся к точке останова (рис.

locals • 1:1 х

; Туре_
1
Name Value __ • _ ··-
" oi this {WorkingWithVisualStudio.Models.SimpleRepository) WorkingWithVisualStudio.Models.SimpleRepos
" 1' Producl< Count = 2 Syste m.Colfecticns.Generic .I EnumeraЫe<Work
~ oi 10) {WorkingWithVisualStudio.Models.Product) WorkingWitl1VisualStudio.Models.Product
~ oi [1] {\"lorkingWithVisualStudio.Mod<Ols.Product} Wo rkingWithVisualStudio.Models.Product
~ oi Raw Vie\v
~ !M!!fi!!.ijЩi!!Wi Sy;tem.Colfectrons.Generic.D1ct1щшy<5tring, i
~ "1: Static members
{WorkingWithVisualStudio.Models.Product}
" " р WorkingWit hVisualStudio.Model<.Product
1' Nan10 · soccer Ьа11 · 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аше Price
Lifejackcr $48. 95
Socce1· bal\ $19.50

L Соше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 фай­
ла proj ect . j son.

Листинг 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 . О" ,
"Microsoft . AspNetCore . Server. Kes trel " 1 . О. О
11
:
11
,

"Microsoft .Extens 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 .Dependencyin je ction ;
using Microsoft . Extensions .Logging ;
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= viewport content= "width=device-width " />
11 11

<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.

р t:! х
.1ninistrator)
"ools Test Дnalyze Window Help Adam Freeman • 11
Any CPU . ~ ' • llS Express • ·~~ • '[ ·ff ~-;j[__~ i:f!.L'~"~J~_!_.:!.:i! .:L .
....____. G Refresh Linked Browsers ~ Ctrl+Alt.-Enter .
. S~@} BromerlinkOashboard
, ~ ЕnаЫе Browser Link
1" ЕnаЫе CSS Auto·Sync

"
- -- -"...,.---...
= Solution ltems
61 global.json

Рис. 6.18. Применение средства Browser Link для перезагрузки браузера

Листинг 6.16. Добавление временной отметки в файле Index. csh tml


@model IEnumeraЫe<WorkingWithVisualStudio.Models.Product >
@{ Layout = nu ll; }
< !DOCTYPE html>
<html>
<head>
<meta name= " viewport " content= "width=device - width " />
<title>Working with Vi sua l Studio</tit l e>
</head>
Глава 6. Работа с Visual Studio 159
<body>
<h3>Produc ts </h3 >
<p>Request Time: @DateTime .Now. ToString ("HH:mm: ss") </р>
<tаЫе>
<thead>
<tr><td>Name</td><td>Pri ce</td></ tr >
</thead>
<tbody>
@foreac h (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

Web Bro,vstr (Google Chrome Canary)


Brows.Wrth".
th • dev ice-..,1idth" />
'title >

Рис. 6.19. Выбор множества браузеров

Browsers (select one or more):

Google Ch rome Ca nary


lnternal Web Browser

[---· --···---]
......----
Set as Default
.;,.~~
Opera lnternet Browser

Ptogram:

Size of browser window:

Рис. 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 Working with Visua1 Stu1 Х


11\ttp.://locilhost:60)18/ ~ 'Norr.in9 ""ilh Visu1'.I Stvd- Х ·

I ~· -) () 1 ~
l0<.1 ••::_ •i} ..O:..~~~~ait1~~~~~18~==~:::.~-=--=---=-~ S
P1·oducts 1
R•questTime: 17:5 1:26 Pi·odurts
Name Price
1 Nasne Price 1 ReqнestT11110 : 1 7 : 5 1 .26
Lifejacket !:48.95 11

Soccer ball .Е19. 50 Lif•jocket f 4&. 95 1 №111е Price


' Corner ftag 1:34.95 \ Soccer ball П 9 . 50 L1f<;ncket S4S .95
L ·-. ___ --·----- ___ __j Corner flag 04.95 Sосш bnll $19.50
j Сошеr flag $34.95
1
t--·" - -------------·---------·~ --~-- · - - - - - - -

Рис. 6.21. Работа с несколькими браузерами

Browser Link Dash board


.А \1,'o rkingWithVisualStudio (0 co nnections)
.А Connections
1·..z o cur re"t ~on n ec 1o ns
View in Brow~er

.А U nkno\'IП (З con nections)


.А Connections
Chrome • http:/ /localhost6051 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

Подгото вка ф айло в JavaScript


и CSS дл я раз в ерты в ани я
При создании клиентской части веб-приложения вы обычно будете создавать ряд
специальных файлов JavaScript и CSS, которые дополняют аналогичные файлы в па­
кетах, установленных инструментом Bower. Такие файлы требуют специальной обра­
ботки с целью оптимизации их доставки в производственную среду, чтобы миними­
зировать количество НТГР-запросов и долю полосы пропускания , необходимую для
доставки файлов клиенту. В этом разделе будет описано расширение Visual Studio,
которо е Microsoft пр едоставляет для выполнения указанной задачи .

В ключение доставки статического содержимого


Инфраструктура ASP.NET Core располагает поддержкой для доставки статических
файлов из папки wwwroot клиентам , но когда проект создается с применением шаб­
лона Empty, упомянутая поддержка по умолчанию отключена. Чтобы включить подде­
ржку статических файлов , в раздел dep e nde nci es файла p ro j ect . j s on потребуется
добавить новый пакет (листинг 6. 17).
162 Ч асть 1. Введение в инфраструктуру ASP.NET Core MVC

Листинг 6.17. Добавление пакета в файле project. json

"dependencies ": {
"Mic ros oft . NETCore . App ":
"version ": " 1 . 0 . 0 ",
" type ": "platform "
}'
"Microsoft . AspNetCore . Diagnostics ": " 1 .0.0",
"Microsoft . AspNetCore . Server.IISintegration ": " 1.0.0 ",
"Microsoft . AspNetCore . Server . Kestrel ": " 1 . 0 . 0 ",
"Microsoft . Extensions . Logging . Consol e ": " 1 . 0 .О",
"Microsoft . AspNetCore . Mvc ": " 1 . О . О ",
"Microsoft . VisualStudio . Web .B rowserLink . 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 .Hosting ;
using Microsoft . AspNetCore . Http;
using Microsoft . Extensions.Dependencyinjection ;
using Microsoft.Extensions.Logging;
namespace Worki ngWithVisualStudi o
puЫ i c class Startup {
puЫic void ConfigureServices(I ServiceColl ec tion services) {
services . AddMvc() ;

puЬlic void Configure(IApplicationBui l der арр , IHostingEnvironment env ,


ILoggerFactory l oggerFactory) {
app . UseDeveloperExceptionPage() ;
app . UseBrowserLink();
app.UseStaticFiles();
app .UseMvcWithDefaultRoute() ;

Добавление в проект статического содержимого


Чтобы продемонстрировать процесс пакетирования и минификации, пон адобится
добавить в проект какое-нибудь статическое содержимое и обеспечить возможность
его доставки клиенту. Для начала создайте папку wwwroot/css, где будут храниться
специальные файлы CSS. Затем добавьте файл по имени first . css, используя шаб ­
лон элемента Style Sheet (Таблица стилей), как показано на рис. 6.23.
Глава 6. Работа с Visual Studio 163

ASP.NEТ

Cli,e.nt-~e

Code

~ Online

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

Поместите в файл f i r s t . сs s стили 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~
" Туре: Client·side f'
А script file co ntai ning JlJvllScript со
(ode

~ Onlinf

Type:Script File Clien t·si d ~

TypeScript JSX File Client· side

f;J TypeSc ript JSO N Configuration File Client· side

f;J Bower Configurat1on File C l i ent - ~ ide

Click here. to $19 011line and find t~


1
Nllme: third.js {
-----------~-~-----·--"-~-·----ц
Рис. 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. csh tml
@model IE numeraЬle<WorkingWithVisua l Studio . Models . Produc t >
@{ Layout = null; )
Глава 6. Работа с Visual Studio 165
< !DOCTYPE html>
<html>
<head>
<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 Time : @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 Price
Lifejacket $48.95
Soccer ball $19.50

1 ~T,-hl-sl_s_th-e-el-em_en_t_r o-,n-th-e th-lr-o.J-s-fi~---------------------~1 1


1
1
Corner flag $34.95

~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::>Extensions and Updates (СервисqРасширения и обновления) и щелкните на кате­
гории On line (Онлайновые), чтобы отобразить галерею доступных расширений Visual
Studio. В поле поиска справа вверху введите строку Bund ler & Mini fie r (рис . 6.26).
Найдите расширение Bundler & Minifler и щелкните на кнопке Download (Загрузить).
чтобы добавить его в Visual Studio. Завершите процесс установки и перезапустите
Visual Studio.

~ lnstolled Bundler & Minifier х •


" o~itne:·.
,,. '' Created Ьу: Mods Kristensen
• Visual Studio G•lfery
~ Controls 1 Version: 2. 1.255
Oo>Vnloads: 144S40
~

~
~ Templates Web E.ssentinls 2015.3 Rating: ., (42 Votes)
• Tools Adds many us~ u l features to \/i<J ual Studio for we b More lnformat1on '
!Seatc ~ults develop•"· Requires Visu•I Studio 2015 Report Exten:-.ion to Mкrosoft
" 1

~
~ Samples Gallery

1 ~,,,
Web Exton.slon Pack
1
Р Updotes (2) The e a ~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 Studio for

~
JavdScript, Type:Sc ript, JSX. CSS .Jnd more

..t;J lmng" Sprites ,,.


у

-··-··
..:...~....._._.".:....._:....:;...~".-; .... ~~--·....;,._.' -·~·~·---,.,.:_ ... __
_;..;.,'
Close
................-:.._'~..... "-..;.·-~.:;·-· :.;_,-..:.~:..~-~-· ~-----'
' '
Рис. 6.26 . Поиск расширения Visual Studio

пакетирование и минификация файлов


После установки расширения и перезапуска Visual Studio вы можете выбирать
множество файлов одного типа, пакетировать их вместе и минифицировать их содер­
жимое. В качестве примера выберите файлы first. css и second. css в окне Solution
Explorer, щелкните на них правой кнопкой мыши и выберите в контекстном меню пункт
Bundler & MinifierqBundle and Minify Files (Упаковщик и минификаторqПакетировать и
минифицировать файлы). как показано на рис . 6.27.
Глава 6. Работа с Visual Studio 167

Solution Explorer • r:3 Х

О'IФ ~ ! Ф·~@!@ с+ Open


. .
Se6rch Solut•on Ex~or (C trl•:.!._ Opon With".
Ф ~JWroot Hide from Solution Exp lorer

{} Cle•nup Selocted Code


.@] fors\.css. ·,_.,. ,"
~ second.;;s
~
-Coll.apse Recurs1vely
. - -
" -.i js -·Rt1n Web Code:. Analysi.s
.
rr fourth.js Bundler & Minifitr
--..~-

rr thiгd .js .... NEW Solution Explore.r V1e.;,v


lib
J(, Cut Ctrl+X
~--------< Q1 Сору Ctrl+C
Х D•lete Del
Rfnan1t

1' Properties Aft•Enter

Рис. 6.27. Пакетирование и минификация файлов CSS

Сохраните выходной файл как bundle. 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 . mi n . js в папке www r oot/j s.

Внимание! Удостоверьтесь, что выбираете файлы в порядке, в котором они загружаются


браузером , для того чтобы предохранить порядок следования стилей или операторов
кода в выходном файле. Таким образом, например, выбирайте файл thi rd. j s перед
файлом four th. j s , чтобы обеспечить выполнение кода в правильном порядке.

В листинге 6.24 показано представление I ndex . csht ml, в котором элементы link
для отдельных файлов заменены одним таким элементом , запрашивающим пакетиро­
ванные и минифицированные файлы.

Листинг 6.24. Применение пакетированных и минифицированных файлов


в файле Index. cshtml
@model IE n ume r a Ыe <Wo rkin g W i thVisu a l S tudi o . Mo d e ls. Produc t >
@{ Layout = null ; }
<!DOCTYPE html>
<html>
<head>
<met a name= "viewpor t" cont ent=" wi dt h=devi ce - width " />
<title>Working with Visual Studio</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 Ti me : @Dat e Ti me . Now.T oS t r i ng ("HH: mm : s s" )</p>
168 Часть 1. Введение в инфраструктуру ASP.NET Core MVC

<tаЫе>
<thead>
<tr><td>Name</td ><td >P ri ce</ td></ tr>
</thead>
<tbody >
@f o r each (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, находяще мся в
корневой папке проекта. Вот конфигурация. которая была сгенерирована для ф айлов
в примере приложения:

"o utp utFileN a me": "wwwroo t/ c s s /bundle. c ss",


"inp utFile s": [
" wwwroot/ c ss/fir st . css ",
" wwwroo t /css/second . cs s"
]
} 1

{
"outputFil eName ": " wwwroot/ j s/bundl e .j s ",
" inputFiles ": [
" wwwroo t / j s/t hird .js",
" wwwroot/ j s / fo u r t h .j s "

Расширение автоматически отслеживает входные файлы на предмет изменений и за­


ново гене рирует выходные файлы, когда происходят изменения. гарантируя отражение в
пакетированных и минифицированных файлах результатов любого редактирования. Для
демонстрации в листинге 6.25 приведено изменение. внесенное в файл th i rd . j s.

Листинг 6.25. Внесение изменения в файл third. j s


d ocume nt . addEvent Lis t ener (" DOMConte nt Loade d", f unct ion () {
var e l eme nt = document . create El eme nt ( "р") ;
element.textContent =
"This is the element from the (modified) third . js file";
document . querySelector( "body " ) . append Ch il d(e l ement) ;
}) ;
Глава 6. Работа с Visual .Studio 169
После того как файл сохранен, расширение заново сгенерирует файл bundle . min. j s.
Перезагрузив страницу в браузере , вы увидите изменение (рис . 6.28).

Name Price
Lifejacket $48.95
Soccer ball $19.50
Corner 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. Сводка по главе

Задача Решение Листинг

Создание модульного теста Создайте проект модульного тестирования, 7.1-7.8


установите тестовый пакет и добавьте клас-
сы, которые содержат тесты

Изоляция компонентов для Используйте интерфейсы для разделения 7.9-7.16


модульного тестирования компонентов приложения и применяйте
фиктивные реализации с ограниченными
тестовыми данными в модульных тестах

Прогон тех же самых тестов Используйте параметризованный модуль­ 7.17-7.19


xUnit.net с разными значениями ный тест либо получайте тестовые данные
данных из метода или свойства

Упрощение процесса создания Используйте инфраструктуру имитации 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. Здесь мы добавим в него поддержку для создания но­
вых объ е ктов Pr 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


@addTagHelpe r *, Mi cro s oft. As pNe t Co r e . Mvc .TagHe lp e r s
172 Часть 1. Введение в инфраструктуру ASP.NET Core MVC

Этот оператор включает встроенные дескрипторные вспомогательные классы. в


том числе тот, который вскоре будет использоваться в представлении Index . Можно
было бы добавить операторы using для импортирования пространств имен из проек­
тов, но представления не являются важной частью рассматриваемого в данной главе
примера приложения, так что ссылка на типы моделей с их пространствами имен не
вызовет проблем.

Добавление действий к контроллеру


Первый шаг связан с добавлением действий к контроллеру Home, который будет
визуализировать представление для ввода данных и для получения этих данных от

браузера (ли стинг 7.2). Действия следуют тому же шаблону, который применялся в
главе 2 и подробно обсуждается в главе l 7.

Листинг 7.2. Добавление методов действий в файле HomeController. cs


usin g 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 IActionResul t Index () => View (Reposi tory. Products
.Where (p => p?.Price < 50));
[HttpGet]
puЬlic IActionResul t 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; }
< !DOCTYPE html>
<html>
<head>
Глава 7. Модульное тестирование приложений MVC 173
<meta name= " viewport " conten t ="width=de vi 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= "AddProduct " method= "post " >
<div c l ass= " form-group " >
<label asp - for= " Name">Name : </label>
<input asp - for= "Name " 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 ng 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 .Name</td>
<t d >@($ " {p .Pri ce :C 2} " )</td>
</ t r>
}
</tb o dy >
</ 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 в контекст

Вопрос Ответ

Что это такое? xUnit.net - это инфраструктура модульного тестирования, которая мо­
жет применяться для тестирования приложений ASP.NET Core MVC

Чем она полезна? xUпit.пet является хорошо написанной инфраструктурой модульного


тестирования, которая легко интегрируется со средой Visual Studio
Как она Тесты определяются как методы, аннотированные с помощью атрибута
используется? Fac t или Theo r y. Внутри тела метода используются методы класса
Assert для сравнения ожидаемого результата теста с тем, что получи­

лось в действительности

Существуют ли Главный просчет при модульном тестировании связан с недостаточной


какие-то скры­ изоляцией тестируемого компонента . За дополнительными сведениями
тые ловушки или обращайтесь в раздел "Изолирование компонентов для модульного
ограничения? тестирования" далее в главе. Самой крупной проблемой, специфичной
для xUпit.пet, является нехватка документации. Кое-какая базовая ин­
формация доступна на веб-сайте http: //xuni t . gi thuЬ . i o, но рас­
ширенное применение требует метода проб и ошибок

Существуют ли Доступно множество инфраструктур модульного тестирования.


альтернативы? Двумя популярными альтернативами являются MSTest (производства
Microsoft) и NUпit

Изменилась ли она Инфраструктура ASP.NET Core MVC делает легким проведение модуль­
по сравнению с ного тестирования , но вовсе не требует и не навязывает его использо­
версией MVC 5? вание, а также не регламентирует применение конкретных инструмен­

тов тестирования . Вы вольны использовать любые инструменты или не


выполнять тестирование вообще

На заметку! В области модульного тестирования практически все является предметом пер­


сональных предпочтений и вопросом шумных разногласий . Некоторым разработчикам не
нравится отделять модульные тесты от кода приложения, и они предпочитают определять

тесты в том же самом проекте или даже в том же самом файле класса. Я описываю здесь
распространенный подход , которому следую сам, но если он не кажется вам правильным,
то вы должны поэкспериментировать с различными стилями тестирования, пока не под­

берете для себя подходящий.

Создание проекта модульного тестирования


Для приложения ASP.NEТ Core обычно создается отдельный проект Visual Studio, со­
держащий модульные тесты, каждый из которых определя ется как метод в классе С# .
Применение отдельного проекта означает, что приложение можно развернуть, не раз­
вертывая одновременно тесты.
176 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC

Соглашение предусматривает назначение проекту модульного тестирования име­


ни <ИмяПриложения> . Tests test, находящей­
и создание его в папке под названием
ся на том же уровне, что и папка src. Для приложения
WorkingWithVisualStudio
именем проекта модульного тестирования будет WorkingWithVisualStudio . Tests .
На рис. 7.2 показана обычная структура проекта ASP.NET Core в Visual Studio, кото­
рый содержит модульные тесты.

Решение _____. Папка src f----+


Проект
WorkingWithVisualStudio WorkingWithVisualStudio

-- Папка test ,_______.... Проект

WorkingWithVisualStudio . Tests

Рис. 7.2. Обычная структура проектов при модульном тестировании

Создание такой структуры требует небольшой работы из-за особенностей распре­


деления средой Visual Studio содержимого решения по файлам на диске.
Первый шаг заключается в использовании проводника файлов или окна командной
строки для создания папки test внутри папки решения WorkingWithV isualStudio,
чтобы она оказалась на одном уровне с существующей папкой 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 ;;· l.: = LSearc~ 1~

Туре: Vis~
дprojectt/

i
11
t; 1NindO\ЧS
\.\/еЬ

~ЕТС?.~
1@} Sii Co n1ole App lication (.NЕТ Core) Visual (# Core class;-

1
ASP.NEТ Core Web Application (. NЕТ Co re) Visual (:<
1 Android
Cloud
1.1;
1 E:<tensibllity
1
1 iOS
1 Repor1ing
1
Silv~rlight
1
1 Test
i
1 WCF

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"
}'
"xuni t" : "2 . 1 . О" ,
"dotnet-test-xunit": "2.2.0-preview2-build1029"
),
" f r ameworks ": {
"ne tcoreappl.0":
"imports ": [ "dotnet5. 6", "portaЫ e -net 4 5+win8"]

Эта конфигурация сообщает Visual Studio о том, что требуются три пакета. Пакет
Microsoft . NETCore . Арр пр едоставля ет АРl-инт е рфейс .NET Core. Пакет xuni t
соде рж ит инфр а ст руктуру тестирования , а пакет dotnettest - x un i t обеспечива -
178 Часть 1. Введение в инфраструктуру ASP.NET Core MVC

ет интеграцию между xUnit.net и Visual Studio. На момент написания книги пакет


dot net - 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


проекта модульного тестирования

"ve rsion ": "1 . 0 . 0- *",


"t e stRunner ": "xunit ",
"de pende nc i es ": {
"Microsoft . NETCore . App ":
"type ": "platform ",
"version ": " 1 . 0 . 0"
} '
"xunit ": " 2.1 . О ",
"dotnet - test - xun i t ": "2 . 2 . 0- pr eview2 - bui l dl029 ",
"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. Это простой класс , но он содержит все, что требуется для начала модульного
те стирования.

На заметку! В методе CanChangeP 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 lOOM };
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 lOOM };
11 Действие
р . Price = 200М ;

11 Утверждение
Assert . Equal(lOOM , p.Price) ;

В классе 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) Этот метод добавляет утверждение о том , что резуль­


тат равен ожидаемому исходу. Существуют перегру­
женные версии данного метода для сравнения различ­

ных типов и для сравнения коллекций. Имеется также


версия, которая принимает дополнительный аргумент
в форме объекта, который реализует интерфейс
IEqua li tyCornparer<T> для сравнения объектов

NotEqual(expected , result) Этот метод добавляет утверждение о том, что резуль­


тат не равен ожидаемому ис х оду

True(result) Этот метод добавляет утверждение о том, что резуль­


тат равен true
False(result) Этот метод добавляет утвер ждение о том, что резуль­
тат равен false
IsType(expected, result) Этот метод добавляет утверждение о том, что резуль­
тат принадлежит указанному типу

IsNotType(expected, resu l t) Этот метод добавляет утверждение о том, что резуль­


тат не принадле жит указанному типу

IsNull (result) Этот метод добавляет утверждение о том , что резуль­


тат равен null
IsNotNull(result) Этот метод добавляет утвер ждение о том , что резуль­
тат не равен null
InRange(result, low , high) Этот метод добавляет утверждение о том , что резуль­
тат находится между l ow и high
NotinRange(result, low ,high ) Этот метод добавляет утверждение о том, что резуль­
тат не находится между l ow и high
Throws(exception,expression) Этот метод добавляет утверждение о том, что указан­
ное выражение генерирует исключение заданного типа

Каждый метод класса 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
Test Explorer • !:! х
Searcn
П: . . а · Sea.:_:h Р·

Ruo А!! 1 Run". • 1 Pi•y11>t: All Test.< •


... Failed Tests (1)
Q T!!sts.Produc tTtsts .CгnCh.!11gePtoductPri ct l lms
... Passed Tests (1)
(J Tests.ProductТest.<.CanC l1o ngoProduct№me 3 ms

Sun1mary
Last Te:st Ru n Failed (Тotel Run Timl! О 00:04)

О Foiled
1 Ttst
О 1 Test Po"<d

Рис. 7.4. Окно Test Explorer в Visual Studio

Совет. Если вы не видите модульные тесты в окне Test Explorer, тогда постройте решение.
Компиляция запускает процесс, с помощью которого обнаруживаются модульные тесты.

Выполните тесты, щелкнув на пункте Run All (Выполнить все) в окне Test Explorer.
Ср еда Visual Studio прим енит xUnit.net для прогона тестов в проекте и отобразит ре ­
зул ьтаты . Как отм е чалось, тест CanChangeProduct Pr i ce () содержит ошибку. кото­
рая приводит к тому , что те ст не проходит. Проблема связан а с аргументом метода
Assert . Equa l () , из-за чего прои сходит сравнение с исходным значением свойс ­
тва Price , а не со значением , на которое оно изменилось. В листинге 7.8 проблема
устранена.

Совет. Когда тест не проходит, всегда полезно проверить правильность самого теста, прежде
чем просматривать компонент, на который он нацелен, особенно если тест только что на­
писан или недавно модифицировался.

Листинг 7.8. Исправление теста в файле ProductTests. cs

using WorkingWithVis ualStud i o . Mode l s ;


using Xunit ;
namespace Worki ngWithVisualStudio .Tests
puЬlic class Produc t Tests {
[Fact]
puЫic void CanChangeProductName()
11 Организация
var р = new Product { Name = " Test ", Price lOOM };
11 Действие
p . Name = "New Name ";
11 Утверждение
Assert . Equal( " New Name ", p . Name) ;
182 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC

[Fac t]
p uЫi c vo id Ca nCh a ng e Pro du c tPri c e ( )
11 Ор га н иза ц и я
va r р = n ew Pr oduct { Name = " Tes t", Pr i ce l OOM };
11 Де йс т ви е
p .P rice = 200 М;

11 У т вержд е н и е
Assert.Equal(200M, p.Price);

При наличии большого количества тестов выполнение их всех может занять неко­
торое время. Таким образом, чтобы можно было работать быстро и итеративно, в окне
Test Explorer предлагаются различные варианты для выбора подмножества тестов,
подлежащих прогону. Самым полезным подмножеством является набор тестов , кото­
рые не прошли (рис. 7.5). Запустите исправленный тест снова, и в окн е Test Explorer
будет показано , что не прошедшие тесты отсутствуют.

'Test Explorer
._,

(f: '"'1 ~Jt!-~~~~


~ . - .

Run All 1 Ruo". ? 1 Playl«t: All Te.t'


г--~·---~~·-----
?
.
J. Failed Т( Run FAited Tt<Sts ~
О Testsj Run Not Run Теsн 11 " Р Tosts (2)
(j Tests.ProductTe-s1s.(.anChan9ePr~ductPrice: Sms
" Р•s.М \ Run P•ssed Test•
& Te~ts{ Repti.11 L.ist Run Ctrl... R. l

Summary
St1mmary
l a5t Test Run Failed otJI Run iin~e 0:00:04)
l ast T~ t Ru n Passe:d (Tot.al Run Time 0:00:02)
f.З; 1 Test Fi!ifed
$ 1 TostPaшd О 1 TestPaщd

Рис . 7.5. Избирательный прогон тестов

И золирование компонентов для модульного тестирова н и я


Написание модульных тестов для классов моделей вроде Prod uct особой сложнос­
тью не отличается. Класс Produ c t не только прост, он также самодостаточен , т.е . при
вьmолнении какого-то действия над объектом Pro du c t можно иметь ув е ренность в
том, что тестируется функциональность, предоставляемая классом Product .
С другими компонентами в приложении МVС ситуация сложнее , потому что между
ними е сть зависимости. Следующий набор опр еделяемых тестов будет оп ерировать
на контроллере, исследуя последовательность объектов Pr 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 ll ections.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 contro l ler = new HorneController();
11 Действие
var rnode l = (controller.Index() as ViewResult)? . ViewData.Model
as IE nurneraЫe <Pro duct > ;
11 Утверждение
Assert . Equa l (S irnple Repository . SharedRepos itory . Products , rnodel ,
Cornparer . Get<Prod uct>( (pl, р2) => pl.Narne == p2 .Narne
&& pl. Price == р2. Price)) ;

Модульный тест в листинге 7.10 проверяет, что метод действия Index () переда­
ет представлению все объекты в хранилище. (Пока что не обращайте внимания на
раздел действия: класс ViewResul t и роль, которую он играет в приложениях MVC,
будут объясняться в главе 17. В настоящий момент достаточно знать. что здесь полу­
чаются данные модели, возвращаемые методом действия Index () .)
Запустив тест, вы увидите, что он не проходит, указывая на отличие между набо­
ром объектов в хранилище и набором объектов, 1шторые возвратил метод Index () .
Но когда дело доходит до выявления причины, из-за чего т ест не прошел, возникает
проблема: предполагается, что тест действует на контроллере Home, однако класс кон­
троллера зависит от класса SimpleRepository. Это затрудняет выяснение. отра зил
ли тест проблему с 1шассом, на 1\Оторый он нацелен, или же проблему в другой части
приложения.

Пример приложения достаточно прост, чтобы можно было легко выявить пробле­
му, всего лишь взглянув на код классов HomeCont r o lle r и SimpleRepository. В ре­
альном приложении визуальный осмотр не настолько прост, т.к. цепочка зависимос­
тей способна затруднить понимание того, что привело к отказу в прохождении теста.
Обычно хранилище будет полагаться на какую-то разновидность системы постоян­
ного хранения. подобную базе данных, а также библиотеку, которая предоставляет
к ней доступ. Модульный тест может взаимодействовать со всей цепочкой сложных
компонентов, любой из которых может вызвать проблему.
Модульные тесты эффективны, когда они нацелены на небольшие части приложе­
ния, такие как отдельный метод или 1шасс. Нам необходима возможность изоляции
контроллера Home от остальной части приложения, чтобы можно было ограничить
область действия теста и исключить влияние со стороны хранилища .

Изолирование компонента
Ключом к изолированию компонентов является использование интерфейсов С# .
Чтобы отделить контроллер от хранилища, добавьте в папку Mode l s новый файл
класса по имени IReposi tory . cs с определением интерфейса, показанным в лис­
тинге 7.1 1.
Глава 7. Модульное тестирование приложений MVC 185
Листинг 7.11. Содержимое файла IReposi tory. cs из папки Models
us i ng System . Collec t ions . Gener i c ;
namespace WorkingWithVisualStudio.Models
puЬlic i nterface IRe pository {
IEnurneraЫe<Product> Products { get ; }
void AddProduct(Product р);

С этим интерфейсом не связано ничего примечательного (за исключением того. что


в н ем н е определен полный набор операций, который обычно будет нужен в веб-при­
ложении; более реалистичный и завершенный пример можно найти в главе 8). Тем не
менее, добавление интерфейса вроде IReposi t ory позволяет легко изолировать ком­
понент для тестирования. Первым делом нужно обновить класс SimpleReposi to ry,
чтобы он реали зовывал новый интерфейс (листинг 7.12).

Листинг 7.12. Реализация интерфейса в файле SimpleReposi tory. cs


using Sys te rn.Co lle ctions . Gen e ric ;
narnespace WorkingWithVisualStudio . Model s
puЫic class SimpleRepository : IRepository
private static SirnpleRepository sharedRepository new
SirnpleRepository();
priva t e Dictionary<s tr ing , Product> products
= new Dictionary<str i ng , Product>() ;
puЬlic static SirnpleRepository SharedRepository => sharedRepository ;
puЫ i c SirnpleRepository() {
var in itialiterns = new(J {
new Product { Name " Kayak ", Pri ce = 275М },
new Product { Narne " Li fejacket ", Price 48 . 95М },
new Product { Narne " Soccer bal l", Price 1 9 . 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 = 2 7 SM } ,
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 ( )
11 Организация
var control l er = new HomeControl l e r () ;
controller.Repository = new ModelCompleteFakeRepository();
11 Действие
va r mode l = (contr olle r . I nde x ( ) as ViewRe sult) ? . Vi e wData .Model
as IE nu meraЫe<Pro du ct> ;
11 Утверждение
Assert.Equal(controller.Repository.Products, model,
Compare r. Get<Prod uct> ( (pl, р2) => pl . Name == р2 . Name
&& pl.Price == p2 . Pr i ce)) ;

Мы определили фиктивную реализацию интерфейса IRe p osi t ory, в которой при­


сутствует только свойство, необходимое для теста, и применяются всегда согласован­
ные данные (чего может не быть при работе с реальной базой данных, особенно в
случае е е совместного использования с другими разработчиками, вносящими собс­
тв енные изменения) .
Исправл енный модульный тест по-прежнему не проходит, указывая на то, что при­
чиной проблемы явля ется метод действия Index ( ) в классе Home Con tro ller , а не
компоненты , от которых он зависит. Метод действия, вызываемый модульным тес­
том, в достаточной степени прост для того, чтобы проблема стала очевидной в ре­
зул ьт ате его осмотра:

puЫic IActionRes ul t I nde x () =>


View(Repos i tory .Product s. Whe r e(p => p . Pr i ce < 50)) ;

Проблема связана с применением м етода Whe r e () из LINQ, который исполь зуется


для фильтрации объектов Product со значениями свойства Pri ce , равным 50 или
больше . Зде сь уже есть серьезное указание на причину проблемы, но п е редовой опыт
предполагает создание теста, который подтвердит наличие проблемы, прежде чем
вносить корректирующую правку (листинг 7.15) .

Совет. В показанных тестах присутствует много дублированного кода. В следующем разделе


объясняется, как упростить тесты.

Листинг 7.15. Добавление теста в файле HomeControllerTests. cs

usi ng Microso ft.AspN etCor e .Mvc;


using System . Co ll ections . Gener i c ;
using WorkingWi t hVisualStudi o . Con trol l e r s ;
using WorkingWithVisualS t udio . Model s ;
using Xunit ;
188 Часть 1. Введение в инфраструктуру ASP.NET Core MVC

namespace WorkingWithVisualStudio. Te sts {


puЫic class HomeControllerTests {

class ModelCompleteFakeReposito ry : IRepository


puЫic IEnumeraЫe<Product> Products { get ; } = new Product[] {
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(Produc t р) {
//ничего не делать - для теста не требуется

[Fact]
puЫic void IndexActionModelisComplete()
11 Организация
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 IReposi tory {


puЬlic IEnumeraЫe<Product> Products { get; } new Product [ ]
new Product { Name = 11 Pl 11 , Price SM } ,
new Product { Name Р2 , Price11
4 В. 95М } ,
11

new Product { Name = 11 РЗ 11 1 Price = 19.SOM },


new Product { Name РЗ , Price11
34. 95М }
11

};
puЬlic void AddProduct(Product р)
//ничего не делать - для теста не требуется

[Fact]
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 or e.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 (Reposi tory. 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". • Playlist : All Tests •


А Passed Tests (4)
О Tests.HomeControlle rTests.lnd~xActionModell sComplete 1 ms
О Tests.HomeControllerTe.sts.lr1dexActionModellsComplete... 36 ms
() Tests . ProductTests.CanChangeProduct№me 1 ms
О Tests.ProductTests.CanCha ngeProductPrice 9ms

Summary
l ast Test Run Passed (Тotal Run Time 0:00:04)
О 4 Tesls Passed

Рис. 7.6. Прохождение всех тестов

Может показаться, что мы проделали слишком много работы для устранения та­
кой простой проблемы, однако возможность протестировать отдельный компонент
жизненно важна при построении реального приложения. Достижение точки, где вы
идентифицировали проблему и написали тесты для проверки исправления, возможна
только тогда, когда вы способны эффективно изолировать компоненты.

Улучшение модульных тестов


В предыдущем разделе был представлен базовый подход к написанию модульных
тестов и прогону тестов в Visual Studio, а также акцентировано внимание на важ­
ности изоляции тестируемого компонента. В этом разделе рассматриваются расши­
ренные инструменты и средства, которые можно применять для написания тестов

более согласованным и выразительным образом. Если вы сильно увлечетесь модуль­


ным тестированием, то можете в итоге получить настолько много тестового кода, что

его ясность становится очень важной, особенно с учетом необходимости пересмотра


тестов для отражения изменений в приложении, вносимых во время разработки и
сопровождения.

Параметризация модульного теста


Тесты, написанные для класса HomeC 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 = pricel } ,
new Product {Name "Р2", Price = price2 } ,
new Product {Name РЗ , Price = priceЗ } ,
11 11

new Product {Narne = "Р4", Price = price4 } ,

} ;
11 Действие
var model = (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 Explortr • 1:) х

Р·

PJJn AU 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.CanCh.a1'9eProductNome 2 rns
$ Tests.Prod1.1ctT~tsC~ Cl'\an.g~ProductPricl!! 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[ ]


ne w 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 ничего неделать - для теста не требуется

[T h eory ]
[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 ll er . 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> Pr od 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 ne w [] { new Product { Name 11
Pl 11 , Price 100 } } ;

puЬlic void AddProduct(Product р)


// ничего не делать - для теста не требуется

[Fact]
puЬlic void RepositoryPropertyCalledOnce() {
/ / Организация
var repo = new PropertyOnce FakeRepository();
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 в контекст

Вопрос Ответ

Что это такое? Moq - это программный пакет для создания фиктивных реали­
заций компонентов в приложении

Чем она полезна? Инфраструктура имитации облегчает создание фиктивных ком­


понентов для изоляции частей приложения в целях модульного
тестирования

Как она используется? Moq применяет лямбда-выражения для определения функцио­


нальности фиктивных компонентов и требует определения толь­
ко тех средств, которые используются при тестировании

Существуют ли какие-то Привыкание к синтаксису может потребовать некоторых усилий.


скрытые ловушки или Документация и примеры находятся по адресу
ограничения? https : //githuЬ.com/Moq/moq4

Существуют ли Доступно несколько альтернативны х инфраструктур, в том числе


альтернативы? NSubstitute (http : / /nsubstitute . githuЬ . io ) и FakeltEasy
(http : / /fakeiteasy . github. io). Все эти инфраструктуры
предлагают похожие возможности, и выбор между ними связан
только с предпочитаемым вами синтаксисом

Изменилась ли она по Применение инфраструктуры имитации специфично для модуль­


сравнению с версией ного тестирования и не является требованием ASP.NET Core MVC
MVC 5?
Глава 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.
Здесь можно сконфигурировать источники пакетов .

Sear<h Optons (Ctrl • E)

1 ~ En\rironment
1 ~ Projocts ond Solution<
1 t> Sошсе Control 0 api.nuget.org
1 ~ Т oxt Ed~or https:/iopi.nuget.org/v3/ indexJ<on
t• Debugging
~ Pelfo1n11nco Т ools
1 ~ D•tabase Т ools
Graphks Di~gnostic_'5
1
1>
А NuGet Packoge M•noger
. Generol
1 P.aclc_гge-S9urCe:s-
~ SQl Server Т ools
Machine·wide pockoge

J
~ Te>:t Templating
t> Tools for Apache Cordovв Mic rosoft and . NЕТ
1 0
~ Web https://v1Vм.nu9et.org/api/v2/cural<d·feeds/n1ic rosoftdotneV
1> W~b Forms Designer 0 Microsoft Vtsual Studio Offline Packages
~ Web Performanco Т est Т ools C:\Proarom Files (xSб)\Microsoft SOКs\NuGetPockoaes\
11 1> Windows Forms D esig neг
~ 'Norkflow Designer !':{omo: /nuget.org

~ XAML Oesigntt ~ourc'"J~~~~~2_1__ _ _ _ _ _ l[J [№р~


1

l----------------------------~~~ё;~_:~_,
Рис. 7 .8. Конфигурирование источников пакетов NuGet

Щелкните на кнопке с изображением знака "плюс" зеленого цвета и введите в по­


лях Name (Имя) и Source (Источник) детальную информацию, описанную в табл. 7.5.
Таблица 7.5. Настройки, требуемые для источника пакетов NuGet

Поле Значение

Name ASP . NET Contrib


Source 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 So ur ce, от которого з ависит moq . netcore .

Листинг 7 .21. Добавление Moq в файл proj ect. j son проекта


WorkingWithVisualStudio.Tests

" ve r sion": " 1 . 0 . 0 - * ",


" tes t Runner " : " xuni t ",
" dependencies ": {
" Microso f t. NET Core . Ap p ":
"t ype ": " p l at f o r m",
" version ": " 1 . 0 . 0 "
} 1

" xu ni t ": " 2 . 1 . 0 ",


" dot n et - test - x uni t ": " 2 . 2 . 0 - p r eview2 -bui ld1029 ",
" Wo r k i ngW i thVis u alStud i o ": "1. 0 . 0 ",
"rnoq.netcore": "4. 4. О-Ьеtа8",
"Systern.Diagnostics . TraceSource": "4 . 0.0"
} 1

" f r amewor ks": {


" netcoreappl . 0 ":
" i mport s": [ " dotnetS. 6 ", " portaЬle - net45 + wi n 8 " ]

Создание имитированного объекта


Создание имитированного объ екта озн ачает необходимость сообщения 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 udio . Controllers ;
u sing WorkingWithVi sualSt ud io . Mode l s ;
using Xunit ;
using System ;
using Moq;
names p ace Wo rkin gW i thVis u a l S tu d i o .Test s
puЫ i c class HomeControl l e r Tests {

[Theory ]
[Cl assDa t a( t ypeo f(P ro du ct Te stDa t a ) ) ]
puЫic void In d e xActionModelisComplete(Produ 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 = contro ller.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:
реальное приложение

в предшествующихMVC,
главах мы создавали очень простое приложение МVС. Был
описан паттерн основные средства языка С#, а также инструменты, не­
обходимые профессиональным разработчикам приложений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 Sto r e и щелкните на кнопке ОК.
Глава 8. SportsStore: реальное приложение 203

"'1emp14tts
4
m ASP.NEТ V/eb Applicdtion (.NfТ FramбVork) Visu.:il (# ТУР": ;
Proj«
"' Vi~ual С '*
"Windo~

i.'!•l>•
11 @ ASP.NEТ Core Wtb Applkation (.NЕТ frameowork}
Corea: ,
ondO~

9Appl~
.NfТ Core 0 Ad~
дndroid Optt
Cloud uУф

f!. Online

SportsS-tore
~ c:\p_r~~ct~ ~.
Solution name: ,Sport sSl:ore

Рис. 8. 1. Выбор типа проекта

Выберите шаблон Empty (Пустой), как проиллюстрировано на рис. 8.2, и щелкните


на кнопке ОК, чтобы создать проект Sp or ts Store .

S•l•d • l•mplat<:
1
г~;;:; C:reT;,;; i"- - - ---·-··------1 An empty project template for crtating "" ASP.NП Со1е 1
~pp l i c.5tfo n . This; template does not h~e any content in J

li
j1
~
Web API
m
App~::i;on
1
j
~ 1