Uploaded by 3D Studio

ASP.NET Core MVC 2 с примерами на C для профессионалов

advertisement
ASP.NET Core MVC 2
с примерами на С#
для профессионалов
7-е издание
ProASP.NET
CoreMVC2
Seventh Edition
Adam Freeman
лpress®
ASP.NET Core MVC 2
с примерами на С#
для профессионалов
7-е издание
Адам Фримен
11)~~
6 NIДl1aQilUl(4
Москва
• Санкт-Петербург
2019
ББК
32.973.26-018.2.75
УДК
681.3.07
Ф88
ООО "Диалектика"
Перевод с английского и редакция Ю.Н. Артеменко
По общим вопросам обращайтесь в издательство "Диалектика" по адресу:
info@dialektika.com, http://www.dialektika.com
Фримев, Адам.
Ф88
ASP.NET Core MVC 2 с примерами на С# для
- СПб.: ООО "Диалектика'', 2019. - 1008
англ.
ISBN 978-5-6041394-3-1
профессионалов. 7-е изд.
с.: ил.
-
:
Пер. с
Парал. тит. англ.
(рус.)
ББК
32.973.26-018.2.75
Все названия программных прод.уктов являются зарегистрированными торговыми мар­
ками соответствующих фирм.
Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в
какой бы то ни было форме и какими бы то ни было средствами, будь то электронные или
механические, включая фотокопирование и запись на магнитный носитель, если на это нет
APress, Berkeley, СА.
Copyright © 2019 Ьу Dialektika Computer PuЬlishiпg.
Authorized translation from the English language editioп puЬlished Ьу APress. lnc., Copyright
© 2017 Ьу Adam Freeman.
This translation is puЬlished Ьу arrangement with APress, Berkeley, СА.
All rights are reserved Ьу the PuЬlisher, whether the wl10le or part of tl1e material ls coпcerned,
specifically the rigl1ts of translation, reprinting, reuse of illustratioпs, recitatioп, broadcasting,
reproduction оп microf11ms or in any other physical way, and transmission or information storage
and retrieval, electronic adaptation, computer software. or Ьу similar or dissimilar methodology
now known or hereafter developed.
Тrademarked names, logos, and images may appear in this book. Rather than use а trademark
symbol with every occurrence of а trademarked name, logo, or image we t1se the names, logos, and
images only in an editorial fashion and to the benefit of the trademark owner, with по intention of
infringement of the trademark.
письменного разрешения издательства
Научно-популярное uздшше
АдамФримев
ASP.NET Core
МVС
2
с примерами на С#
для профессионалов
7-е издание
Подписано в печать
28.09.2018.
Гарнитура
Формат 70х100/16
Times
Усл. печ. л.
Тираж
81,27. Уч.-изд. л. 56.6
400 экз. Заказ М 10168
Отпечатано в АО "Первая Образцовая типография"
Филиал "Чеховский Печатный Двор"
142300,
Сайт:
Московская область, г. Чехов, ул. Полиграфистов. д.
www.chpd.ru, E-mail: sales@chpd.ru,
ООО "Диалектика",
195027,
ISBN 978-5-6041394-3-1
ISBN 978-1-4842-3149-4
(рус.)
(англ.)
тел.
8 (499) 270-73-59
Санкт-Петербург, Магнитогорская ул" д.
30.
лит. А. пом.
848
© ООО "Диалектика", 2019
© Ьу Adam Freeman, 201 7
Оглавление
Часть
Введение в инфраструктуру ASP.NET
1.
Core MVC 2
21
Глава
1. Основы ASP. NET Core MVC
Глава
2.
Ваше первое приложение
Глава
3.
Паперн, проекты и соглашения
Глава
4.
Важные функциональные возможности языка С#
Глава
5. Работа с Razor
122
Глава
6. Работа с Visual Studio
143
Глава
7. Модульное тестирование приложений MVC
176
Глава
8. SportsStore:
Глава
9. SportsStore: навигация
Глава
10. SportsStore:
завершение построения корзины для покупок
281
Глава
11. SportsStore:
администрирование
303
Глава
12. SportsStore:
защита и развертывание
330
22
MVC
31
MVC
72
реальное приложение
205
249
Глава 13. Работа с Visual Studio Code
Часть
11.
87
Подробные сведения об инфраструктуре
356
ASP.NET Core MVC 2
379
Глава
14.
Конфигурирование приложений
380
Глава
15.
Маршрутизация
435
Глава
16. Дополнительные
Глава
17. Контроллеры
Глава
18.
URL
возможности маршрутизации
и действия
477
513
Внедрение зависимостей
556
Глава 19. Фильтры
589
Глава
20.
Контроллеры
Глава
21.
Представления
658
Глава
22.
Компоненты представлений
693
Глава
23.
Вспомогательные функции дескрипторов
723
Глава
24.
Использование вспомогательных функций дескрипторов для форм
755
Глава
25.
API
627
Использование других встроенных вспомогательных
функций дескрипторов
779
809
Глава
26.
Привязка моделей
Глава
27.
Проверка достоверности моделей
844
Глава
28.
Введение в
877
Глава
29.
Применение
Глава
30.
Расширенные средства
Глава
31.
Соглашения по модели и ограничения действий
ASP.NET Core
ldeпtity
ASP.NET Core ldentity
Предметный указатель
ASP. NET Core ldentity
913
941
973
1оо1
Содержание
Об авторе
Часть
Глава
19
Введение в инфраструктуру
1.
ASP.NET Core MVC 2
1. Основы ASP.NET Core MVC
История развития ASP.NEТ
ASP.NEТWeb
22
Core МVС
22
Forms
Первоначальная инфраструктура МVС
Framework
Обзор ASP.NEТ Core
Что нового в
ASP. NЕТ Core МVС 2?
Основные преимущества ASP. NЕТ
Core МVС
Что необходимо знать?
Какова структура книги?
Часть
1. Введение в инфраструктуру ASP.NEТCore МVС
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core МVС
[Де можно получить код примеров?
Резюме
Глава
2.
Ваше первое приложение
21
MVC
23
24
25
26
26
29
30
30
30
30
30
31
Установка
Visual Studio
Установка .NЕТ Core 2.0 SDK
Создание нового проекта ASP.NET Core МVС
Добавление контроллера
Понятие маршрутов
Визуализация веб-страниц
Создание и визуализация представления
Добавление динамического вывода
Создание простого приложения для ввода данных
31
32
32
37
39
39
39
43
44
Предварительная настройка
44
Проектирование модели данных
Ссьшка на методы действий
46
46
48
Построение формы
49
Получение данных формы
51
59
65
Создание второго действия и строго типизированного представления
Добавление проверки достоверности
Стилизация содержимого
Резюме
71
Глава З. Паттерн, проекты и соrлашения
История создания
MVC
MVC
Особенности паттерна
MVC
Понятие моделей
Понятие контроллеров
Понятие представлений
Реализация паттерна МVС в ASP.NEТ
Core
Сравнение МVС с другими паттернами
Паттерн "Интеллектуальный пользовательский интерфейс"
72
72
72
73
74
74
74
76
76
Содержание
80
80
ПроектыАSР.NЕТСоrе МVС
Создание проекта
Соглашения в проекте МVС
84
86
Резюме
Глава
4.
7
Важные функциональные возможности языка С#
87
Подготовка проекта для примера
Включение ASP.NET Core МVС
Создание компонентов приложения МVС
Использование null-условной операции
Связывание в цепочки null-условных операций
Комбинирование null-условной операции и операции объединения с
null
Использование автоматически реализуемых свойств
Использование инициализаторов автоматически реализуемых свойств
Создание автоматически реализуемых свойств только для чтения
Использование интерполяции строк
Использование инициализаторов объектов и коллекций
Использование инициализатора индексированной коллекции
88
89
89
91
92
94
95
95
96
98
99
100
1О1
Сопоставление с образцом
Сопоставление с образцом в операторах
swi tch
Использование расширяющих методов
102
103
Применение расширяющих методов к интерфейсу
105
Создание фильтрующих расширяющих методов
106
108
Использование лямбда-выражений
Определение функций
109
Использование методов и свойств в форме лямбда-выражений
112
114
114
116
Использование автоматического выведения типа и анонимных типов
Использование анонимных типов
Использование асинхронных методов
Работа с задачами напрямую
Применение ключевых слов
async и awai t
Получение имен
Резюме
Глава
5.
Работа с
Razor
Определение модели
Создание контроллера
Создание представления
Работа с объектом модели
Использование файла импортирования представлений
123
124
124
125
126
128
129
Работа с компоновками
Создание компоновки
Применение компоновки
Использование файла запуска представления
Razor
Вставка значений данных
Установка значений атрибутов
Использование условных операторов
Проход по содержимому массивов и коллекций
Резюме
118
120
121
122
Подготовка проекта для примера
Использование выражений
11 7
130
132
133
135
136
137
138
140
142
8
Содержание
Глава
6.
Работа с Visual
Studio
143
Подготовка проекта для примера
Создание модели
Создание контроллера и представления
Управление программными пакетами
Инструмент
Инструмент
NuGet
Bower
Итеративная разработка
Внесение изменений в представления
Razor
Внесение изменений в классы С#
Browser Link
JavaSciipt и CSS для развертывания
Использование средства
Подготовка файлов
Включение доставки статического содержимого
Добавление в проект статического содержимого
Обновление представления
Пакетирование и минификация в приложениях МVС
Резюме
Глава
7.
Модульное тестирование приложений
MVC
176
Подготовка проекта для примера
Включение встроенных вспомогательных функций дескрипторов
Добавление действий к контроллеру
Создание формы для ввода данных
Обновление представления
Index
Модульное тестирование приложений МVС
Создание проекта модульного тестирования
Создание ссылки на проект
Изолирование компонентов для модульного тестирования
Улучшение модульных тестов
Параметризация модульного теста
Улучшение фиктивных реализаций
Резюме
Глава
8. SportsStore:
реальное приложение
Создание проекта МVС
Создание проекта модульного тестирования
Проверка и запуск приложения
Начало работы с моделью предметной области
Создание хранилища
Создание фиктивного хранилища
Регистрация службы хранилища
Отображение списка товаров
Добавление контроллера
Добавление и конфигурирование представления
Установка стандартного маршрута
Запуск приложения
Подготовка базы данных
Создание классов базы данных
Создание класса хранилища
177
177
178
178
179
180
181
182
187
195
195
199
204
205
Начало работы
Установка пакета инструментов командной строки
143
144
145
146
14 7
149
153
153
154
162
167
168
168
170
172
175
Entity Framework Core
206
206
209
21 1
212
2 12
2 13
214
215
216
21 7
219
220
221
22 1
222
223
Содержание
Определение строки подключения
Конфигурирование приложения
Создание миграции базы данных
Создание начальных данных
Добавление поддержки разбиения на страницы
Отображение ссылок на страницы
Улучшение
URL
Стилизация содержимого
Установка пакета
Bootstrap
Bootstrap к компоновке
Применение стилей
Создание частичного представления
Резюме
Глава
навиrация
9. SportsStore:
Фильтрация списка товаров
URL
Построение меню навигации по категориям
Корректировка счетчика страниц
Построение корзины ДJIЯ покупок
Определение модели корзины
Создание кнопок добавления в корзину
Включение поддержки сеансов
Реализация контроллера ДJIЯ корзины
Отображение содержимого корзины
Резюме
Глава
1О. SportsStore:
завершение построения корзины для покупок
Усовершенствование модели корзины с помощью службы
Создание класса корзины. осведомленного о хранилище
Регистрация службы
Упрощение контроллера
Cart
Завершение функциональности корзины
Удаление элементов из корзины
Добавление виджета с итоговой информацией по корзине
Отправка заказов
Создание класса модели
Добавление реализации процесса оплаты
Реализация обработки заказов
Завершение построения контроллера
Order
Отображение сообщений об ошибках проверки достоверности
Отображение итоговой страницы
Резюме
Глава
11. SportsStore: администрирование
Управление заказами
Расширение модели
Добавление действий и представления
Добавление средств управления каталогом
Создание контроллера
CRUD
Реализация представления списка
223
224
227
227
231
232
241
242
243
243
246
248
249
Добавление навигационных элементов управления
Улучшение схемы
9
249
249
253
257
265
267
268
271
273
274
277
280
281
281
281
282
283
284
284
286
289
289
290
293
297
300
301
302
303
303
303
304
307
308
309
1О
Содержание
Редактирование сведений о товарах
311
324
326
329
Создание новых товаров
Удаление товаров
Резюме
Глава
12. SportsStore:
защита и развертывание
330
Защита средств администрирования
Резюме
330
330
335
337
341
341
341
343
34 7
347
351
355
Глава
356
Создание базы данных
Iden ti ty
Применение базовой политики авторизации
Создание контршшера
Accoun t
и представлений
Тестирование политики безопасности
Развертывание приложения
Создание баз данных
Подготовка приложения
Применение миграций к базам данных
Управление начальным заполнением баз данных
Развертывание приложения
13. Работа с Visual Studio Code
Настройка среды разработки
Установка
Проверка установки
Установка
356
356
358
358
359
359
359
360
360
361
361
362
363
364
366
366
367
367
370
372
376
377
378
378
Node.js
Node
Git
Проверка установки
Git
Установка
Bower
Установка .NЕТ Core
Проверка установки .NЕТ
Core
Visual Studio Code
Проверка установки Visual Studio Code
Установка расширения С# для Visual Studio Code
Создание проекта ASP.NET Core
Подготовка проекта с помощью Visual Studio Code
Установка
Управление пакетами клиентской стороны
Конфигурирование приложения
Построение и запуск проекта
Воссоздание приложения
Partylnvi tes
Создание модели и хранилища
Создание базы данных
Создание контршшеров и представлений
Модульное тестирование в
Visual Studio Code
Создание модульного теста
Прогон тестов
Резюме
Часть
Глава
11. Подробные сведения об
14. Конфигурирование
инфраструктуре
приложений
Подготовка проекта для примера
Конфигурирование проекта
Добавление пакетов в проект
Добавление пакетов инструментов в проект
ASP.NET Core MVC 2
379
380
382
383
384
386
Содержание
Класс
Program
11
Резюме
387
388
391
394
397
407
41 1
416
418
420
421
426
427
428
429
430
432
434
Глава
435
Анализ деталей конфигурации
Класс
Startup
Службы
ASP.NET
Промежуточное программное обеспечение
Особенности вызова метода
ASP.NET
Conf igure ()
Добавление оставшихся компонентов промежуточного программного обеспечения
Конфиrурирование приложения
Создание конфиrурационного файла JSON
Использование данных конфигурации
Конфигурирование регистрации в журнале
Конфигурирование внедрения зависимостей
Конфиrурирование служб МVС
Работа со сложными конфиrурациями
Создание разных внешних конфиrурационных файлов
Создание разных методов конфиrурирования
Создание разных классов конфигурирования
15. Маршрутизация URL
Подготовка проекта для примера
Создание класса модели
Создание примеров контроллеров
Создание представления
Введение в шаблоны
URL
Создание и регистрация простого маршрута
Определение стандартных значений
Определение встраиваемых стандартных значений
Использование статических сегментов
URL
Определение специальных переменных сегментов
Использование специальных переменных в качестве параметров метода действия
Определение необязательных сегментов
URL
Определение маршрутов переменной длины
Ограничение маршрутов
Ограничение маршрута с использованием реrулярного выражения
Использование ограничений на основе типов и значений
Объединение ограничений
Определение специального ограничения
Использование маршрутизации с помощью атрибутов
Подготовка для маршрутизации с помощью атрибутов
Применение маршрутизации с помощью атрибутов
Применение ограничений к маршрутам
Резюме
Глава
16. Дополнительные возможности
маршрутизации
Подготовка проекта для примера
fенерирование исходящих
URL в представлениях
rенерирование исходящих ссьшок
rенерирование
rенерирование
URL (без ссылок)
URL в методах действий
437
437
438
439
440
442
443
444
447
452
454
455
458
460
464
465
466
468
4 71
4 71
472
475
476
477
4 78
4 79
480
490
491
Содержание
12
Настройка системы маршрутизации
Резюме
492
492
493
496
497
504
505
505
506
509
510
510
512
512
Глава
513
Изменение конфигурации системы маршрутизации
Создание специального класса маршрута
Применение специального класса маршрута
Маршрутизация на контроллеры МVС
Работа с областями
Создание области
Создание маршрута для области
Заполнение области
Генерирование ссылок на действия в областях
URL
URL чистыми и понятными человеку
POST: выбор правильного запроса
Полезные советы относительно схемы
Делайте
GET
и
17. Контроллеры и действия
Подготовка проекта для примера
514
515
517
Подготовка представлений
Понятие контроллеров
Создание контроллеров
518
518
Создание контроллеров РОСО
Использование базового класса
Controller
Получение данных контекста
Получение данных из объектов контекста
Использование параметров метода действия
Генерирование ответа
Генерирование ответа с использованием объекта контекста
Понятие результатов действий
Генерирование НТМL-ответа
Передача данных из метода действия в представление
Выполнение перенаправления
Возвращение разных типов содержимого
Реагирование с помощью содержимого файлов
Возвращение ошибок и кодов НТТР
Другие классы результатов действий
Резюме
Глава
18. Внедрение зависимостей
Подготовка проекта для примера
Создание модели и хранилища
Создание контроллера и представления
Создание проекта модульного тестирования
Создание слабо связанных компонентов
Исследование сильно связанных компонентов
Введение в средство внедрения зависимостей ASP.NEТ
Подготовка к внедрению зависимостей
Конфигурирование поставщика служб
Модульное тестирование контроллера с зависимостью
Использование цепочек зависимостей
Использование внедрения зависимостей для конкретных типов
521
522
522
527
528
529
530
532
535
541
548
551
552
555
555
556
557
558
559
561
561
562
568
569
570
572
573
575
Содержание
:жизненные ЦИIUIЫ служб
Использование переходного жизненного ци1U1а
Использование жизненного ци1U1а, ограниченного областью действия
Использование жизненного ци1U1а одиночки
Использование внедрения в действия
Использование атрибутов внедрения в свойства
Запрашивание объекта реализации вручную
Резюме
Глава
19. Фильтры
Подготовка проекта Д11Я примера
ВIUlючение
SSL
Создание контроллера и представления
Использование фильтров
Понятие фильтров
Получение данных контекста
Использование фильтров авторизации
Создание фильтра авторизации
Использование фильтров действий
Создание фильтра действий
Создание асинхронного фильтра действий
Использование фильтров результатов
Создание фильтра результатов
Создание асинхронного фильтра результатов
Создание гибридного фильтра действий/ результатов
Использование фильтров ис1U1ючений
Создание фильтра ис1U1ючений
Использование внедрения зависимостей Д11Я фильтров
Распознавание зависимостей в фильтрах
Управление жизненными ци1U1ами фильтров
Создание глобальных фильтров
Порядок применения фильтров и его изменение
Изменение порядка применения фильтров
Резюме
Глава
20. Контроллеры API
Подготовка проекта Д11Я примера
Создание модели и хранилища
Создание контроллера и представлений
Роль контроллеров
REST
Проблема скорости
Проблема эффективности
Проблема открытости
Введение в
REST и контроллеры API
Создание контроллера API
Тестирование контроллера API
Использование контроллера API в браузере
Форматирование содержимого
Стандартная политика содержимого
Согласование содержимого
Указание формата данных Д11Я действия
13
577
579
583
585
585
587
587
588
589
590
591
591
593
596
597
598
599
601
602
604
605
606
608
609
611
612
614
614
618
621
624
626
626
627
628
628
630
633
633
634
635
635
637
641
645
648
648
649
652
Содержание
14
Получение формата данных из маршрута или строки запроса
Включение полного согласования содержимого
Получение разных форматов данных
Резюме
Глава
21.
Представления
Подготовка проекта для примера
Создание специального механизма визуализации
I Vi ew
IViewEngine
Создание специальной реализации интерфейса
Создание реализации интерфейса
Регистрация специального механизма визуализации
Тестирование механизма визуализации
Работа с механизмом
Razor
Подготовка проекта для примера
Прояснение представлений
Razor
Добавление динамического содержимого к представлению
Razor
Использование разделов компоновки
Использование частичных представлений
Добавление содержимого
JSON в представления
Razor
Конфигурирование механизма
Расширители местоположений представлений
Резюме
Глава
22.
Компоненты представлений
Подготовка проекта для примера
Создание моделей и хранилищ
Создание контроллера и представлений
Конфигурирование приложения
Понятие компонентов представлений
Создание компонента представления
Создание компонентов представлений РОСО
Наследование от базового класса
ViewComponent
Понятие результатов компонентов представлений
Получение данных контекста
Создание асинхронных компонентов представлений
Создание гибридных классов контроллеров и компонентов представлений
Создание гибридных представлений
Применение гибридного класса
Резюме
Глава
23.
Вспомогательные функции дескрипторов
Подготовка проекта для примера
Создание модели и хранилища
Создание контроллера, компоновки и представлений
Конфигурирование приложения
Создание вспомогательной функции дескриптора
Определение класса вспомогательной функции дескриптора
Регистрация вспомогательных функций дескрипторов
Использование вспомогательной функции дескриптора
Управление областью действия вспомогательной функции дескриптора
653
655
656
657
658
659
660
662
663
664
665
667
668
670
674
675
680
683
685
686
692
693
694
694
696
699
700
701
701
703
704
710
715
718
719
720
722
723
724
725
725
728
729
729
733
734
736
Содержание
Усовершенствованные возможности вспомогательных функций дескрипторов
Создание сокращающих элементов
Вставка перед и после содержимого и элементов
15
740
741
7 43
Получение данных контекста представления и использование внедрения
747
7 49
751
753
754
зависимостей
Работа с моделью представления
Согласование вспомогательных функций дескрипторов
Подавление выходного элемента
Резюме
Глава
24. Использование вспомогательных функций дескрипторов дпя форм
Подготовка проекта для примера
Переустановка представлений и компоновки
Работа с элементами
f о rm
Установка цели формы
Использование средства противодействия подделке
Работа с элементами
inpu t
Конфигурирование элементов
input
Форматирование значений данных
Работа с элементами
Работа с элементами
label
select и option
Использование источника данных для заполнения элемента
s е 1 ее t
option из перечисления
textarea
генерирование элементов
Работа с элементами
Вспомогательные функции дескрипторов для проверки достоверности форм
Резюме
Глава
25. Использование других встроенных вспомогательных
функций дескрипторов
Подготовка проекта для примера
Использование вспомогательной функции дескриптора для среды размещения
Использование вспомогательных функций дескрипторов для
Управление файлами J avaScгipt
Управление таблицами стилей
CSS
Работа с якорными элементами
Работа с элементами
img
Использование кеша данных
Установка времени истечения для кеша
Использование вариаций кеша
Использование
URL,
относительных к приложению
Резюме
Глава
755
756
757
759
759
760
762
763
765
768
770
771
772
776
778
778
26. Привязка моделей
Подготовка проекта для примера
Создание модели и хранилища
Создание контроллера и представления
Конфигурирование приложения
Понятие привязки моделей
Стандартные значения привязки
Привязка простых типов
JavaScrtpt и CSS
779
780
781
782
782
792
795
797
798
801
803
805
808
809
81 О
81 О
812
813
814
816
818
16
Содержание
Привязка сложных типов
Привязка массивов и коллекций
Привязка коллекций сложных типов
Указание источника данных привязки моделей
Выбор стандартного источника данных привязки
Использование заголовков в качестве источников данных привязки
Использование тел запросов в качестве источников данных привязки
Резюме
Глава
27. Проверка достоверности моделей
Подготовка проекта для примера
Создание модели
Создание контроллера
Создание компоновки и представлений
Необходимость в проверке достоверности модели
Явная проверка достоверности модели
Отображение пользователю ошибок проверки достоверности
Отображение сообщений об ошибках проверки достоверности
Отображение сообщений об ошибках проверки достоверности на уровне свойств
Отображение сообщений об ошибках проверки достоверности на уровне модели
Указание правил проверки достоверности с помощью метаданных
Создание специального атрибута проверки достоверности для свойства
Выполнение проверки достоверности на стороне клиента
Выполнение удаленной проверки достоверности
Резюме
Глава
28. Введение в ASP.NET Core ldentity
Подготовка проекта для примера
Создание контроллера и представления
Настройка ASP.NEТ Core Ideпtity
Создание класса пользователя
Создание класса контекста базы данных
Конфигурирование настройки строки подключения к базе данных
Создание базы данных Ideпtity
Использование ASP.NEТ Core
Identity
Перечисление пользовательских учетных записей
Создание пользователей
Проверка паролей
Проверка деталей, связанных с пользователем
Завершение построения средств администрирования
Реализация возможности удаления
Реализация возможности редактирования
Резюме
Глава
29. Применение ASP. NET Core ldentity
Подготовка проекта для примера
Аутентификация пользователей
Подготовка к реализации аутентификации
Добавление аутентификации пользователей
Тестирование аутентификации
819
829
832
835
837
838
841
843
844
844
846
846
84 7
850
850
853
855
860
861
864
867
870
873
876
877
879
880
882
882
883
884
886
886
887
889
893
900
905
906
907
912
913
913
914
916
919
922
Содержание
Авторизация пользователей с помощью ролей
Создание и удаление ролей
Управление членством в ролях
Использование ролей для авторизации
Помещение в базу данных начальной информации
Резюме
Глава
30.
Расширенные средства ASP.NET Core
ldentity
Подготовка проекта для примера
Добавление специальных свойств в класс пользователя
Подготовка миграции базы данных
Тестирование специальных свойств
Работа с заявками и политиками
Понятие заявок
Создание заявок
Использование политик
Использование политик для авторизации доступа к ресурсам
Использование сторонней аутентификации
Регистрация приложения в
Google
Google
Включение аутентификации
Резюме
Глава
31. Соrлашения по модели и оrраничения действий
17
922
923
928
933
937
940
941
942
942
946
947
947
948
952
955
962
967
967
968
972
973
Резюме
973
974
976
977
981
982
987
988
989
990
992
993
998
1000
Предметный указатель
1001
Подготовка проекта для примера
Создание модели представления. контроллера и представления
Использование модели приложения и соглашений по модели
Модель приложения
Роль соглашений по модели
Создание соглашения по модели
Порядок применения соглашений по модели
Создание глобальных соглашений по модели
Использование ограничений действий
Подготовка проекта для примера
Ограничения действий
Создание ограничения действия
Распознавание зависимостей в ограничениях действий
Посвящается моей прекрасной жене Джеки Гриффитс.
Об авторе
Адам Фримев
-
опытный специалист в области информационных технологий,
занимавший ведущие позиции во многих компаниях, последней из которых был гло­
бальный банк, где он работал на должностях директора по внедрению технологий и
руководителя административной службы. После ухода из банка Адам уделяет все свое
время писательской деятельности и бегу на длинные дистанции.
О техническом рецензенте
Фабио КJiаудво Ферраччати
-
ведущий консультант и главный аналитик/раз­
BluArancio
Microsoft разработчи­
ком решений для .NЕТ (Microsoft Certified Solution Developer for .NЕТ). сертифициро­
ванным Microsoft разработчиком приложений для .NЕТ (Microsoft Certified Application
Developer for .NET), сертифицированным Microsoft профессионалом (Microsoft Certified
Professional), а также продуктивным автором и техническим рецензентом. За пос­
работчик, использующий технологии
(www .Ыuarancio. com).
Microsoft.
Он работает в компании
Фабио является сертифицированным
ледние десять лет он написал множество статей для итальянских и международных
журналов и выступал в качестве соавтора в более чем
темам, связанным с компьютерами.
1О
книгах по разнообразным
Ждем ваших отзывов!
Вы, читатель этой книги, и есть главный ее критик. Мы ценим ваше мнение и хо­
тим знать, что было сделано нами правильно, что можно было сделать лучше и что
еще вы хотели бы увидеть изданным нами. Нам интересны любые ваши замечания в
наш адрес.
Мы ждем ваших комментариев и надеемся на них. Вы можете прислать нам бу­
мажное или электронное письмо либо просто посетить наш веб-сайт и оставить свои
замечания там. Одним словом, любым удобным для вас способом дайте нам знать,
нравится ли вам эта книга, а также выскажите свое мнение о том, как сделать наши
книги более интересными для вас.
Отправляя письмо или сообщение, не забудьте указать название книги и ее авто­
ров, а также свой обратный адрес. Мы внимательно ознакомимся с вашим мнением и
обязательно учтем его при отборе и подготовке к изданию новых книг.
Наши электронные адреса:
E-mail:
info@dialektika.com
WWW:
http://www.dialektika.com
ЧАСТЬ 1
Введение
в инфраструктуру
ASP.NET Core
МVС
2
разработчиков веб-приложений, использующих платформу Microsoft,
д ля
инфраструктура ASP.NET Core MVC представляется как радикальное изме­
нение. В ней особое значение придается чистой архитектуре, паттернам проек­
тирования и удобству тестирования, а также не предпринимается попытка скры­
вать, каким образом работает веб-среда.
Первая часть книги поможет понять в общих чертах основополагающие идеи
разработки приложений
MVC,
включая новые возможности
увидеть на деле, как применяется инфраструктура.
ASP.NET Core MVC,
и
ГЛАВА
1
Основы ASP.NEТ
A
SP.NET
Саге
изводства
MVC Microsoft.
Core МVС
это инфраструктура для разработки веб-приложений про­
которая сочетает в себе эффективность и аккуратность ар­
(mode\-view-controller - MVC). идеи
.NET. В настоящей
Microsoft создала инфраструктуру ASP.NET Соге
хитектуры "модель-представление-контроллер"
и приемы гибкой разработки. а также лучшие части платформы
главе вы узнаете. почему компания
MVC,
увидите, как она соотносится со своими предшественниками и альтернативами.
а также ознакомитесь с обзором нововведений
ASP.NET
Саге
MVC
и с тем . что будет
рассматриваться в книге.
История развития
ASP.NET Core MVC
ASP.NET появилась в 2002 году . в то время.
Microsoft стремилась сохранить господствующее положение в области
Первоначальная версия платформы
когда компания
разработки традиционных настольных приложений и видела в Интернете опасность.
На рис .
1. 1 показано.
как выглядел на то время стек технологий
Microsoft.
ASP.NETWeb Forms
Набор компонентов пользовательского интерфейса (страниц, кнопок и т.д.)
плюс объектно-ориентированная программная модель
графического пользовательско го
интерфейса с сохранением состояния
ASP. N EТ
Способ размещения приложений . NЕТ в
llS (веб-серверный продукт Microsoft),
позволяющий взаимодействовать с запросами и ответами НТТР
.NET
-
Многоязычная платформа управляемого кода
(совершенно новая на то время и по праву ставшая поворотным пунктом в развитии)
Рис.
1.1.
Стек технологий
ASPNET Web Forms
Глава 1. Основы ASP.NET Соге MVC
23
ASP.NET Web Forms
В инфраструктуре
Web Fonns разработчики из Microsoft пытались скрыть и прото­
HTML (которым на тот мо­
кол НТГР (с присущим ему отсутствием состояния), и язык
мент не владели многие разработчики) за счет моделирования пользовательского ин­
терфейса как иерархии объектов, представляющих серверные элементы управления.
Каждый элемент управления отслеживал собственное состояние между запросами,
по мере необходимости визуализируя себя в виде НТМL-разметки и автоматически
соединяя события клиентской стороны (например, щелчки на кнопках) с соответс­
твующим кодом их обработки на стороне сервера. Фактически
Web Fonns
является
гигантским уровнем абстракции, предназначенным для доставки классического уп­
равляемого событиями графического пользовательского интерфейса через веб-среду.
Идея заключалась в том, чтобы разработка веб-приложений выглядела почти так
же, как разработка настольных приложений. Разработчики могли оперировать поня­
тиями пользовательского интерфейса, запоминающего состояние, и не иметь дело с
последовательностями независимых запросов и ответов НТГР. У компании
Microsoft
появилась возможность плавно переместить армию разработчиков настольных
Windоws-приложений в новый мир веб-приложений.
Что было не так с
ASP.NET Web Forms?
Разработка с использованием традиционной технологии ASP.NEТ Web
Forms в при­
нципе была хорошей, но реальность оказалась более сложной.
•
Тяжеловесность состояния представления
(View State).
Действительный меха­
низм поддержки состояния между запросами, известный как
View State,
вызвал
необходимость передачи крупных блоков данных между клиентом и сервером.
Объем таких данных мог достигать сотен килобайтов даже в скромных веб-при­
ложениях. Эти данные путешествуют туда и обратно с каждым запросом, при­
водя к замедлению реакции и увеличивая требование к ширине полосы пропус­
кания сервера.
•
Жизненный цикл страницы. Механизм соединения событий клиентской сто­
роны с кодом обработчиков событий на стороне сервера, являющийся частью
жизненного цикла страницы, мог быть сложным и хрупким. Лишь немногим
разработчикам удавалось успешно манипулировать иерархией элементов уп­
равления во время выполнения, не получая ошибки состояния представления
или не сталкиваясь с ситуацией, когда некоторые обработчики событий зага­
дочным образом отказывались запускаться.
•
Ложное ощущение разделения обязанностей. Модель отделенного кода ASP.NET
Web Forms предоставляла способ вынесения кода приложения из НТМL-разметки
в специальный файл отделенного кода. Это было сделано с целью разделения
логики и представления, но в действительности попустительствовало смеши­
ванию кода представления (например, манипуляций деревом элементов уп­
равления серверной стороны) с прикладной логикой (скажем, обработкой ин­
формации из базы данных) в гигантских классах отделенного кода. Конечный
результат мог оказаться хрупким и непонятным.
•
Ограниченный контроль над НТМL-разметкой. Серверные элементы управле­
ния визуализировали себя в виде НТМL-разметки, но она не обязательно была
такой, как хотелось. В ранних версиях
Web Fonns
выходная НТМL-разметка не
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
24
соответствовала веб-стандартам или неэффективно применяла каскадные таб­
лицы стилей
(Cascading Style Sheets - CSS),
а серверные элементы управления
генерировали непредсказуемые и сложные атрибуты идентификаторов, к кото­
рым было трудно получать доступ с помощью
JavaScript. Указанные проблемы
Web Forms, но получение
были значительно смягчены в последних выпусках
ожидаемой НТМL-разметки по-прежнему могло быть затруднительным.
Негерметичная абстракция. Инфраструктура
•
Web Forms,
где только возможно,
пыталась скрывать детали, связанные с НТМL и НТГР. При попытке реализовать
специальное поведение абстракция часто отбрасывалась, поэтому Д11Я генериро­
вания желаемой НТМL-разметки приходилось воссоздавать механизм событий
обратной отправки или предпринимать другие сложные действия.
Низкая тестируемость. Проектировщики
•
Web Forms даже не предполагали,
что
автоматизированное тестирование станет неотъемлемой частью процесса раз­
работки программного обеспечения. Построенная ими тесно связанная архи­
тектура не бьша приспособлена ДllЯ модульного тестирования. Интеграционное
тестирование также могло превратиться в сложную задачу.
С технологией
Web Forms
не все бьшо плохо, и со временем в
Microsoft приложили
немало усилий по улучшению степени соответствия стандартам, упрощению процес­
са разработки и даже заимствованию ряда возможностей из первоначальной инфра­
структуры ASP.NEТ МVС
Forms
Framework ДllЯ
их применения в
Web Forms.
Технология
Web
превосходна, когда необходимы быстрые результаты, и с ее помощью вполне
реально построить и запустить веб-приложение умеренной сложности всего за один
день. Но если не проявлять осторожность во время разработки, то обнаружится, что
созданное приложение трудно тестировать и сопровождать.
Первоначальная инфраструктура МУС
В октябре
2007
года компания
Framework
Microsoft объявила
о выходе новой платформы раз­
работки, построенной на основе существующей платформы ASP.NEТ, которая бьша
задумана как прямая реакция на критику
платформ наподобие
MVC Framework
Ruby
оп
Rails.
Web Forms и
популярность конкурирующих
Новая платформа получила название
ASP.NET
и отражала формирующиеся тенденции в разработке веб-приложе­
ний, такие как стандартизация
HTML и CSS,
веб-службы
REST,
эффективное модуль­
ное тестирование. а также идея о том, что разработчики должны принять факт от­
сутствия состояния у НТГР.
Концепции,
которые
подкрепляли
первоначальную
MVC
2007 году мир разра­
ботки веб-приложений .NЕТ бьш их лишен. Появление ASP.NEТ MVC Framework воз­
вратило платформу разработки веб-приложений Microsoft в современную эпоху.
Инфраструктура МVС Framework также сигнализировала о важном изменении в
отношении компании Microsoft, которая ранее пыталась контролировать каждый ком­
Framework,
инфраструктуру
теперь кажутся естественными и очевидными, но в
понент в инструментальных средствах, необходимых ДllЯ веб-приложений. Компания
Microsoft
встроила в инфраструктуру МVС
дом, такие как
jQuery. учла проектные
Framework
инструменты с открытым ко­
соглашения и передовой опыт конкурирующих
(и более успешных) платформ. а также выпустила в свет исходный код МVС
ДllЯ изучения разработчиками.
Framework
Глава 1. Основы ASP.NET Core MVC
Что было не так с первоначальной инфраструктурой
На то время для
Microsoft имело
25
MVC Framework?
смысл создавать инфраструктуру МVС
Framework
поверх существующей платформы, содержащей большой объем монолитной низ­
коуровневой функциональности, которая обеспечила преимущество в начале про­
цесса разработки и которую хорошо знали и понимали разработчики приложений
ASP.NET.
MVC Framework на платформе, которая из­
Web Forms. Разработчики MVC Framework привыкли
Но компромиссы потребовали основать
начально предназначалась для
использовать конфиrурационные параметры и кодовые настройки, отключающие или
реконфиrурирующие средства, которые не имели ни малейшего отношения к их веб­
приложениям, но требовались для того, чтобы все заработало.
С ростом популярности
MVC Framework компания Microsoft приступила к добав­
Web Forms. Результат был все более и более
лению ряда ее основных возможностей к
странным, когда индивидуальные особенности проекта, требуемые для поддержки
MVC Framework,
расширялись с целью поддержки
Web Forms,
и предпринимались до­
полнительные проектные ухищрения, чтобы подогнать все друг к друrу. Одновременно
в
Microsoft начали расширять ASP.NET новыми
(Web API) и организации коммуникаций
служб
инфраструктурами для создания веб­
в реальном времени
(SignalR).
Новые
инфраструктуры добавили собственные соглашения по конфигурированию и разра­
ботке, каждое из которых обладало своими преимуществами и причудами, породив в
результате фрагментированную смесь.
Обзор
В
ASP.NET Core
2015 году Microsoft заявила о новом направлении для ASP.NEТ и МVС Framework,
Core МVС, рассматривае­
что в итоге привело к появлению инфраструктуры ASP.NEТ
мой в настоящей книге.
.NET Core, которая представляет
Framework без интерфейсов программирова­
ния приложений (applicatioп programming interface - API), специфичных для Windows.
Тhсподствующей операционной системой по-прежнему является Windows, но веб-при­
Платформа ASP.NEТ
Core
построена на основе
собой межплатформенную версию .NЕТ
ложения все чаще размещаются в небольших и простых контейнерах на облачных
платформах. За счет принятия межплатформенного подхода компания
ширила область охвата
Core
.NET,
Microsoft рас­
ASP.NET
сделав возможным развертывание приложений
на более широком наборе сред размещения, а в качестве бонуса предоставила
разработчикам возможность создавать веб-приложения
macOS.
ASP.NEТ Core -
Linux
ASP.NET Core
на машинах
и
совершенно новая инфраструктура. Она проще, с нею легче рабо­
тать, и она свободна от наследия, происходящего из
на .NЕТ
Core,
Web Forms.
Будучи основанной
она поддерживает разработку веб-приложений для ряда платформ и
контейнеров.
Инфраструктура
ASP.NET Core MVC предлагает функциональность первоначаль­
Framework, построенной поверх новой платформы
ной инфраструктуры ASP.NEТ МVС
ASP.NET Core. Она интегрирует
лялись Web API, поддерживает
функциональные средства, которые ранее предостав­
более естественный способ генерирования сложного
содержимого и делает основные задачи разработки, такие как модульное тестирова­
ние. более простыми и предсказуемыми.
26
Часть 1. Введение в инфраструктуру ASP.NET Соге MVC 2
Что нового в
Выпуск
ASP.NET Core MVC 2?
ASP.NET Core MVC 2
концентрируется на консолидации за счет того, что
задействует в своих интересах изменения в инструментах и платформе, которые
были введены в более ранних версиях. Для работы
ся платформа
.NET Core 2,
ASP.NET Core MVC 2
требует­
которая имеет значительно расширенное покрытие АРI­
интерфейсами и теперь поддерживается дополнительными дистрибутивами
Linux.
Полезные изменения включают новую систему мета-пакетов, упрощающую управле­
NuGet. новую систему конфигурации для ASP.NET Core и поддержку для
Entity Framework Core 2. Крупнейшим новым средством стала инфраструктура Razor
Pages, которая представляет собой попытку воссоздания стиля разработки. ассоци­
ированного с инфраструктурой Web Pages, с применением более современной плат­
формы, но Razor Pages не будет интересна разработчикам МVС (и в данной книге не
ние пакетами
рассматривается).
Основные преимущества
ASP.NET Core MVC
В последующих разделах кратко описано, чем новая платформа
унаследованную инфраструктуру
Framework,
Web Forms
и что именно позволит снова вывести
Архитектура
MVC
превосходит
и первоначальную инфраструктуру
ASP.NET на
MVC
передний край.
MVC
Инфраструктура
ASP.NET Core MVC следует паттерну под названием "модель­
(model-view-controller - МVС), который управляет фор­
веб-приложения ASP.NET и взаимодействиями между содержащимися в нем
предсmавление-конmроллер"
мой
компонентами.
Важно различать архитектурный паттерн
MVC и реализацию ASP.NET Core MVC.
MVC далеко не нов (его появление датируется 1978 годом и связано с про­
Smalltalk в Xerox PARC), но в наши дни он завоевал популярность в качестве
Паттерн
ектом
паттерна для веб-приложений по перечисленным ниже причинам.
•
Взаимодействие пользователя с приложением, которое придерживается паттер­
ном
MVC,
следует естественному циклу: пользователь предпринимает действие,
а приложение в ответ изменяет свою модель данных и доставляет обновленное
представление пользователю. Затем цикл повторяется. Это удобно укладывает­
ся в схему веб-приложений, предоставляемых в виде последовательностей за­
просов и ответов НТТР.
•
Неб-приложения, нуждающиеся в сочетании нескольких технологий (например,
баз данных, НТМL-разметки и исполняемого кода), обычно разделяются на на­
бор слоев или уровней. Полученные в результате таких сочетаний комбинации
естественным образом отображаются на концепции в паттерне МVС.
Инфраструктура
ASP.NET Core
МVС реализует паттерн МVС и вдобавок обеспечи­
вает гораздо лучшее разделение обязанностей по сравнению с
деле в
ASP.NET Core MVC
внедрена разновидность паттерна
Web Forms. На самом
MVC, которая особенно
хорошо подходит для веб-приложений. Дополнительные сведения по теории и прак­
тике применения такой архитектуры приведены в главе
3.
Глава 1. Основы ASP.NET Соге MVC
27
Расширяемость
Инфраструктуры
ASP.NET Core
и
ASP.NET Core MVC
построены в виде последова­
тельности независимых компонентов, которые имеют четко определенные характе­
ристики, удовлетворяют интерфейсу .NЕТ или созданы на основе абстрактного базо­
вого класса. Основные компоненты можно легко заменять другими компонентами с
собственной реализацией. В общем случае для каждого компонента инфраструктура
ASP.NET Core MVC
предлагает три возможности.
Использование стандартной реализации компонента в том виде, как есть (чего
•
должно быть достаточно для большинства приложений).
Создание подкласса стандартной реализации с целью корректировки существу­
•
ющего поведения.
Полная замена компонента новой реализацией интерфейса или абстрактного
•
базового класса.
Разнообразные компоненты, а также способы и причины их возможной настройки
или замены будут рассматриваться, начиная с главы
Жесткий контроль над
Инфраструктура
HTML
14.
и НТТР
ASP.NET Core MVC
генерирует ясную и соответствующую стан­
дартам разметку. Ее встроенные вспомогательные функции дескрипторов
(tag helper)
Web
Foпns име­
производят соответствующий стандартам вывод, но по сравнению с
ется более значимое философское изменение. Вместо генерации громадного объема
НТМL-разметки, над которой вы имеете очень небольшой контроль, инфраструктура
ASP.NEТ
Core
с помощью
МVС поощряет создание простой и элегантной разметки, стилизованной
css.
Конечно, если вы действительно хотите добавить готовые виджеты для таких слож­
ных элементов пользовательского интерфейса, как окна выбора даты или каскадные
ASP.NET Core МVС,
jQuery,
ASP.NET Core MVC настолько тес­
меню, то подход "никаких специальных требований", принятый в
позволяет легко использовать наилучшие клиентские библиотеки, подобные
Aпgular,
React
или
Bootstrap CSS.
Инфраструктура
но сплетена с этими библиотеками, что в
Microsoft
предусмотрели шаблоны, которые
включают их, чтобы придать ускорение процессу разработки новых проектов.
Инфраструктура ASP.NET Core MVC работает в гармонии с НТТР. Вы имеете кон­
троль над запросами, передаваемыми между браузером и сервером, так что можете
точно настраивать пользовательский интерфейс по своему усмотрению. Технология
Ajax
сделана легкой в применении, и создание веб-служб для получения браузерных
НТТР-запросов выливается в простой процесс, который описан в главе
20.
Тестируемость
Естественное разнесение различных обязанностей приложения по независимым
друг от друга частям, которое поддерживается архитектурой
ASP.NET Core
МVС, поз­
воляет с самого начала делать приложение легко сопровождаемым и удобным для
тестирования. Вдобавок каждый фрагмент платформы
туры
ASP.NET Core MVC
ASP.NET Core
и инфраструк­
может быть изолирован и заменен в целях модульного тес­
тирования, которое допускается выполнять с использованием любой популярной ин­
фраструктуры тестирования с открытым кодом, такой как
главе
7.
xUnit,
рассматриваемой в
28
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
В настоящей книге вы увидите примеры написания ясных и простых модульных
тестов для контроллеров и действий
MVC,
которые предоставляют фиктивные либо
имитированные реализации компонентов инфраструктуры для эмуляции любого сце­
нария с применением разнообразных стратегий тестирования и имитации. Даже если
вам никогда ранее не приходилось создавать модульные тесты, у вас будет все необ­
ходимое для успешного старта.
Тестируемость
Приложения
имеет
отношение
ASP.NET Core MVC успешно
не
только
к
модульному
тестированию.
работают также с инструментами тестирова­
ния, встроенными в средства автоматизации пользовательского интерфейса. Можно
создавать тестовые сценарии, которые имитируют взаимодействие с пользователем,
не выдвигая догадки о том, какие структуры НТМL-элементов, классы
CSS
или иден­
тификаторы сгенерирует инфраструктура. и не испытывая беспокойства по поводу
неожиданных изменений структуры.
Мощная система маршрутизации
По мере совершенствования технологии построения веб-приложений эволюциони­
ровал стиль унифицированных указателей ресурсов
Адреса
URL
/Арр
(uniform resource locator - URL).
вроде приведенного ниже:
v2/User/Page.aspx?action=show%20prop&prop id=82742
встречаются все реже, а им на смену приходит более простой и понятный формат
следующего вида:
/to-rent/chicago/2303-silver-street
Существует ряд веских причин для того, чтобы заботиться о структуре
URL-
aдpecoв. Во-первых. поисковые механизмы придают вес ключевым словам, содер­
жащимся в
URL.
Поиск по фразе "reпt
in Chicago" ("аренда в Чикаго") с большей
URL. Во-вторых, многие веб-пользователи
понимать URL, и ценят возможность навигации
вероятностью обнаружит более простой
достаточно сообразительны. чтобы
путем ввода запроса в адресной строке своего браузера. В-третьих. когда структура
URL-aдpeca понятна, люди с большей вероятностью пройдут по нему. поделятся им
с другими или даже продиктуют его по телефону. В-четвертых. при таком подходе в
Интернете не раскрываются технические детали. структура каталогов и имен файлов
приложения; следовательно, вы вольны изменять лежащую в основе сайта реализа­
цию. не нарушая работоспособности всех входящих ссылок.
В более ранних инфраструктурах понятные URL-aдpeca реализовать было трудно,
ASP.NET Core
но в
МVС используется средство, известное как маршрутизация
URL.
ко­
торое обеспечивает предоставление понятных URL-aдpecoв по умолчанию. В результате
появляется контроль над схемой
URL и
ее взаимосвязью с приложением, что обеспечи­
вает свободу создания понятного и удобного для пользователей шаблона
URL без
необ­
ходимости следования какому-то заранее определенному шаблону. И. разумеется, это
URL в стиле REST, если
URL приведено в главах 15 и 16.
означает также простоту определения современной схемы
нужна. Подробное описание маршрутизации
она
Современный АРl-интерфейс
Платформа
вая
-
ния.
Microsoft .NET развивалась с каждым крупным выпуском, поддержи­
- многие передовые аспекты современного программирова­
Инфраструктура ASP.NET Core MVC построена для платформы .NET Core, поэтои даже определяя
Глава 1. Основы ASP.NET Core MVC
29
му ее АРI-интерфейс может в полной мере задействовать последние новшества языка
и исполняющей среды, знакомые программистам на С#, в том числе ключевое слово
awai t,
расширяющие методы, лямбда-выражения, анонимные и динамические типы,
а также язык интегрированных запросов
(Language lntegrated Query - LINQ).
ASP.NET Core MVC
Многие методы и паттерны кодирования АРI-интерфейса
следу­
ют более четкой и выразительной композиции, чем было возможно в ранних версиях
инфраструктуры. Не переживайте, если вы пока не в курсе последних функциональ­
ных возможностей языка С#: в главе
твам С# для разработки приложений
4
представлена сводка по самым важным средс­
MVC.
Межплатформенная природа
ASP.NET были специфичными для Windows, требуя на­
Windows при написании веб-приложений и сервер Windows
и выполнении. Компания Microsoft сделала инфраструктуру
Предшествующие версии
стольный компьютер с
при их развертывании
ASP.NET Core
межплатформенной, как в отношении разработки, так и в плане раз­
macOS
Linux. Межплатформенная поддержка облегчает
развертывание приложений ASP.NET Core MVC, к тому же имеется хорошая подде­
ржка работы с платформами контейнеров приложений, такими как Docker.
Вполне вероятно, большая часть разработки приложений ASP.NET Core MVC в
ближайшем будущем будет выполняться с применением Visual Studio, но компания
Microsoft также создала межплатформенный инструмент разработки под названием
Visual Studio Code, появление которого означает, что разработка ASP.NET Core МVС
больше не ограничена средой Windows.
вертывания. Продукт
.NET Core
доступен для различных платформ, включая
и набор популярных дистрибутивов
Инфраструктура ASP.NEТ
Core MVC
имеет открытый код
В отличие от предшествующих платформ для разработки веб-приложений произ­
водства
Microsoft
вы можете загрузить исходный код
ASP.NET Core
и
ASP.NET Core
МVС и даже модифицировать и компилировать его с целью получения собственных
версий инфраструктур. Это бесценно при отладке кода, обращающегося к системно­
му компоненту, когда требуется пошагово выполнить его код (и даже почитать ори­
гинальные комментарии программистов). Это также полезно, если вы создаете усо­
вершенствованный компонент и хотите посмотреть, какие существуют возможности
разработки, или узнать, как действительно работают встроенные компоненты.
Исходный код
и
ASP.NET Core
ASP.NET Core MVC
доступен для загрузки по адресу
https://github.com/aspnet.
Что необходимо знать?
Чтобы получить от книги максимальную пользу, вы должны быть знакомы с осно­
вами разработки веб-приложений, понимать
HTML
и
CSS,
а также иметь практичес­
кий опыт работы с языком С#. Не беспокойтесь, если детали разработки клиентской
стороны, такие как
JavaScript,
для вас несколько туманны. Основной акцент в книге
делается на разработке серверной стороны, и благодаря примерам вы сможете подоб­
сводка по наиболее полезным средствам
рать то, что вам нужно. В главе
4 приводится
языка С# для разработки
которую вы сочтете удобной, если переходите на пос­
ледние версии
.NET с
MVC,
более раннего выпуска.
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
30
Какова структура книги?
Книга разделена на две части, в каждой из которых раскрывается набор связан­
ных тем.
Часть
1.
Введение в инфраструктуру
Книга начинается с помещения
ASP.NET Core MVC
ASP.NET Core
МVС в контекст разработки. Здесь
объяснены преимущества и практическое влияние паттерна МVС, рассмотрен способ,
которым инфраструктура
ASP.NET Core
МVС вписывается в современную разработку
веб-приложений, а также описаны инструменты и средства языка С#, необходимые
каждому программисту
В главе
2
ASP.NET Core MVC.
мы углубимся в детали и создадим простое веб-приложение, чтобы по­
лучить представление о том, каковы основные компоненты и строительные блоки, и
каким образом они сочетаются друг с другом. Однако большинство материала этой
части посвящено разработке проекта под названием
SportsStore.
посредством которо­
го демонстрируется реалистичный процесс разработки, начиная с постановки задачи
и заканчивая развертыванием, с привлечением основных функциональных возмож­
ностей
Часть
ASP.NET Core
11.
МVС.
Подробные сведения об инфраструктуре
ASP.NET Core MVC
ASP.NET Core МVС, ко­
SportsStore. Будет показано, как
Во второй части объясняется внутренняя работа средств
торые использовались при построении приложения
работает каждое средство, объяснена его роль и описаны доступные варианты конфи­
гурирования и настройки. Широкий контекст, представленный в первой части, под­
робно раскрывается во второй части.
Где можно получить код примеров?
Все примеры, рассмотренные в книге, доступны для загрузки по адресу
gi thub. com/apress/pro-asp. net-core-mvc-2
https: / /
и на веб-сайте издательства. Загру­
жаемый файл доступен бесплатно и включает все поддерживающие ресурсы, кото­
рые требуются для воссоздания примеров без необходимости в их ручном наборе.
Загружать код необязательно, но это самый простой путь для экспериментирования
с примерами и способ использования фрагментов кода в собственных проектах.
Резюме
В главе был объяснен контекст, в котором существует инфраструктура ASP.NEТ
Web Forms и первоначальной инфраструк­
ASP.NET MVC Framework. Кроме того, рассматривались преимущества приме­
нения ASP.NET Core MVC и структура книги. В следующей главе вы увидите инфра­
структуру ASP.NET Core МVС в действии благодаря простой демонстрации средств,
Core
МVС, а также описано ее развитие от
туры
которые обеспечивают все ее преимущества.
ГЛАВА
2
Ваше первое
приложение МVС
л учший способ оценки инфраструктуры. предназначенной для разработки про­
граммного обеспечения. заключается в том, чтобы приступить непосредствен­
но к ее использованию. В настоящей главе мы создадим простое приложение ввода
данных с применением
ASP.NET
Соге
MVC.
Мы будем решать эту задачу пошагово,
чтобы вы поняли, каким образом строится приложение МVС. Для простоты мы пока
опускаем некоторые технические подробности. Но не беспокойтесь
-
если вы только
начинаете знакомство с МVС, то узнаете много интересного. Когда что-либо исполь­
зуется без пояснения, то приводится ссылка на главу, в которой находятся все необ­
ходимые детали.
Установка
Visual Studio
Настоящая книга опирается на продукт
ет среду разработки для проектов
Visual Studio 2017, который предоставля­
ASP.NET Core MVC. Мы будем применять бесплат­
ную редакцию
Visual Studio 2017 Community, доступную для загрузки на веб-сайте
www. visualstudio. сот. При установке Visual Studio 2017 вы должны выбрать ра­
бочую нагрузку .NET Core cross-platform development (Межплатформенная разработка
.NET Core).
как показано на рис.
2.1.
На заметку! Версия
чае установки
Visual Studio 2017 предшествовала выпуску ASP.NET Core MVC 2. В слу­
Visual Studio для более ранних версий ASP.NET Core MVC понадобится при­
менить последние обновления. Применить обновления можно, запустив программу уста­
новки
Visual Studio и щелкнув на кнопке Update (Обновить).
Совет. Среда
Visual Studio поддерживает только Windows. Создавать приложения ASP.NET
Core MVC на других платформах можно с использованием Visual Studio Code. Продукт
Visual Studio Code не обладает всеми возможностями Visual Studio, но он предлагает ве­
ликолепный редактор и любые средства, требующиеся для разработки приложений MVC.
Детали ищите в главе 13.
32
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
1::1 х
-
Modifylng-V>Sual Stud10 Commun1ty 2017 -
Workloads
х
15.3 О
lndividual components
language packs
Summary
Other Toolsets (3)
> Visual Studio core editor
Vlsual Studlo extension development
Create add·.oм and tr~ns1~ns •or V:sual Stud10 1ncluding
ntW commal3ds, codt .naly:"ers and tool w1ndows.
v .NET Core cross-platform development
1ncluded
.,, .NEТCore 1.0- 1.1 developmenttock:
Unux d~etopment with С ... •
.,,
.NЕТ
.J
ASP.NH ind wеЬ di'Velopment tools
Framewortc 4.6. 1 d~elopmft'\t tools
.,, De'IQfoper Anatyt1cs tools
Opt1onal
~ Contau-,er dtv2lopm~nt tools
L!.11 Cloud Е.<рlош
.NП Cort
c1oss·pLatfor·m deve\opment
Зutld cross·platform •ppltc•t1ons usin9 .NET Core ASPJ..fТ
Cort , НТМ!., Ja...1Scnp ьnd c.ontьintt dt"elopment tools..
L!.11 .NЕТ prof11in9 tools
LOcatIOn
,..
·""
(
\f1
f'j
Total 1nstaH s1ze:
Ву continuing. you egrt-t t0 the lic~ for the Visual Stud.IO ~ition yot.t s.d«.tIO. We also offer ~ 1Ь.1 1tу to
dO'tonJo&d cthl!r so~ with VtSu1I StudIO. Тhts softwart is Jкmse<f я-p..v1ttly. ..s ш out 111 tМ 3rd Party tilooc~
or in rt5 .J(Cornpan)'lng l1c~e. 8у cont1nuing уоо .Jlso ьgrt-e to tho~ bc~s.
Рис.
Установка
2. 1.
Выбор рабочей нагрузки
О kB
Mod1fy
Visual Studio
.NET Core 2.0 SDK
Установка
ложений
Visual Studio содержит все средства. необходимые для разработки при­
ASP.NET Core MVC. но не включает комплект .NET Core 2.0 SDK. который
должен быть загружен и установлен отдельно.
Перейдем по ссылке
https: / / www. microsoft.com/ net / core. загрузим програм­
SDK для Windows и запустим ее. После завершения работы
программы установки откроем окно командной строки или окно PowerShell и введем
следующую команду для отображения установленной версии платформы .NET:
му установки
.NET
Соге
dotnet -- versi on
Если установка прошла успешно. тогда результирующим выводом команды будет
2.
о. о.
Создание нового проекта
ASP.NET Core MVC
Мы собираемся начать с создания нового проекта
Studio.
ASP.NET Core MVC в среде Visual
File (Файл) пункт NewqProject (СоздатьqПроект) . чтобы от ­
New Project (Новый проект) . Перейдя в раздел TemplatesqVisual
Выберем в меню
крыть диалоговое окно
C#qWeb (WaблoныqVisual С#9Веб) в панели слева, можно заметить шаблон проекта
ASP.NET Core Web Application (Веб-приложение ASP.NET Core). Выберем этот тип про­
екта (рис. 2.2).
Глава 2. Ваше nервое nриложение MVC
~
·! П' [@
"' f .NEТfr•mework~ SortЬy.@<f!U'it
i> Recent
~ lмtolled
....~
ASP.NH CoreWeb
~р[ц:оtюn
vtsщl (#
Туре:
33
Sшch(Ctrl•E)
Р·
V'isu•I (:
"~=====-----------.;...i Proje:cttempl1tesforcreatin9 ASP. NП
Corrapplications for Windows, Linux and
ASP .NEТ Yleb Applic•lюn (.NЕТ Frame.. Visual ("
Vtsual (;:
Windows Cfassic D~ktop
macOS us1n9
w.ь
.NП
Core or .NЕТ
Fr1mework.
.NE'!Core
е>
. NП St•nd•rd
Cloud
Test
Visu.1l81sic
<;()! ,,_,,.,
Not find1ng wh1t you .are toolang fot?
Open Vrsual Studio lnstoller
Name:
location:
Sotution mme:
P•rtylnvites
@u.e~~ ----------~-:::=3
P•rtylnvites
0
Brows".
Crtate directory f or solution
О ,Add to Sourct Control
Г
Рис.
2.2.
Выбор шаблона nроекта
_o!:._::J
C•ncel
ASP.NET Core Web Application
Совет. При выборе шаблона проекта может возникать путаница из-за сильно похожих на­
званий шаблонов. Шаблон ASP.NET Web Application (.NET Framework) (Веб-приложение
ASP.NET (.NET Framework)) предназначен для создания проектов с использованием унас­
ледованных версий ASP.NET и MVC Framework, которые предшествовали ASP.NET Core.
Указанные два шаблона позволяют создавать приложения ASP.NET Core и отличаются
применяемой исполняющей средой, предлагая на выбор .NET Framework или .NET Core.
Разница между ними объясняется в главе 6, но в книге повсеместно используется вари­
ант .NET Core, поэтому для получения идентичных результатов при работе с примерами
приложений вы должны выбирать именно его.
В поле
Name
(Имя) для нового проекта введем
Partyinv ites.
Для продолжения
щелкнем на кнопке ОК. Откроется еще одно диалоговое окно. которое предложит
установить начальное содержимое проекта. Удостоверимся, что в раскрывающихся
списках слева вверху выбраны элементы
Для шаблона
.NET Core и ASP.NET Core 2.0 (рис. 2.3).
ASP.NET Core Web Application доступно несколько вариантов, каждый
из которых приводит к созданию проекта с отличающимся начальным содержимым.
Для целей данной главы выберем вариант
Web Application (Model-View-Controller)
приложение (модель-представление-контроллер)). который создаст приложение
(Веб­
MVC
заранее определенным содержимым, чтобы немедленно приступить к разработке.
На заметку! Шаблон проекта
Web Application (Model-View-Controller)
применяется только
в настоящей главе. Мне не нравится пользоваться заранее определенными шаблонами ,
поскольку они потворствуют интерпретации ряда важных средств наподобие аутентифи­
кации как черных ящиков. Моя цель здесь
-
предоставить вам достаточный объем знаний
для понимания и управления каждым аспектом приложения
материалах книги будет применяться шаблон
Empty
MVC,
так что в оставшихся
(Пустой). Текущая глава посвящена
быстрому началу процесса разработки , для чего хорошо подходит шаблон
(Model-View-Controller) .
Web Application
с
34
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
А project tempf1te for
Empty
creating an ASP.NEТ Core
•pplicillion with e><•mple ASP .NЕТ Core МVС Views •nd
Controller>. This templ•t• can •lso bt used for RESПul
НТТР services.
Web API
Wш..!!!ш
React.js
React.js •nd
Redux
Ch•nge Authentication
Authenticгtion
No Authentiation
О En•ЬI• Docker Support
OS:
Windows
Requires Docktr for Windom
Docker support can •lso Ь• en•Ьled l•t•r
~
OIC
Рис.
2.3.
11
C•nc•I
Выбор начальной конфигурации проекта
Щелкнем на кнопке
Change Authentication (Изменить аутентификацию) и в открыв­
Change Authentication (Изменение аутентификации) проверим.
что выбран переключатель No Authentication (Аутентификация отсутствует) , как пока­
зано на рис. 2.4. Данный проект не требует какой-либо аутентификации , а в главах
28- 30 будет объясняться, как защищать приложения ASP.NET.
Щелкнем на кнопке ОК, чтобы закрыть диалоговое окно Change Authentication.
Удостоверимся в том, что флажок ЕnаЫе Docker Support (Включить поддержку Docker)
шемся диалоговом окне
не отмечен, и затем щелкнем на кнопке ОК для создания проекта
Change Autl1~nticat1on
·
• .
For applications that don't require any user authentication.
@ No Authenticotion
О lndividual User Accounts
О Work or School Accounts
О Windows Authentication
bel!m mgre obout thjrd-party op~n source autbenticotion options
Рис.
2.4.
Выбор настроек аутентификации
Pa rtyinv i tes.
Х
Глава 2. Ваше первое приложение MVC
После того как среда
Visual Studio
35
создала проект, в окне
(Проводник решения) появится множество файлов и папок (рис.
Solution Explorer
2.5). Они представ­
ляют собой стандартную структуру для проекта
MVC. созданного с использованием
Web Application (Model-View-Controller), и вскоре вы узнаете назначение каж­
файла и папки, которые были созданы Visual Studio.
шаблона
дого
Solution Explorer
Р·
G'> Connected Services
1>
1>
1>
1>
1>
.~i'
Dependencies
Properties
@ wwwroot
Controllers
Models
JI
Views
1>
1>
1>
Рис.
2.5.
61
61
61
1>
С"
1>
С"
appsettingsJson
bower.json
bundleconfig.json
Program.cs
Startup.cs
Начальная структура файлов и папок проекта
Совет. Если вместо папок
Controllers, Models
и
Views
ASP.NET Core MVC
вы видите папку
Pages,
то это
означает, что вы выбрали шаблон
Web Apptication (Веб-nриложение), а не шаблон Web
Apptication (Model-View-Controller). Я не имею ни малейшего понятия о том, почему в
Microsoft решили, что настолько похожие названия шаблонов будут удачной идеей, но в
подобной ситуации понадобится удалить созданный проект и начать все заново.
Теперь приложение можно запустить, выбрав в меню
Debugging
Debug
(Отладка) пункт
нужно просто щелкнуть на кнопке ОК. Среда
с помощью сервера приложений
IIS Express
Visual Studio
скомпилирует приложение.
запустит его и откроет окно веб-браузера
для запроса содержимого приложения. При запуске проекта в первый раз среде
Studio
Start
(Запустить отладку); если появится запрос на включение отладки, тогда
Visual
потребуется некоторое время; по завершении процесса будет получен резуль­
тат. показанный на рис.
2.6.
Visual Studio создает проект с применением шаблона Web Apptication
(Model-View-Controtter), она добавляет базовый код и содержимое, которое вы наблю­
Когда среда
даете после запуска приложения. Далее в главе мы заменим такое содержимое, чтобы
создать простое приложение
MVC.
По завершении понадобится остановить отладку. для чего закрыть окно брау ­
зера или вернуться в
(Остановить отладку).
Visual Studio
и выбрать в меню
Debug
пункт
Stop Debugging
36
Часть 1. Введение в инфраструктуру ASP NET Саге MVC 2
Application uses
_,,,,,r
Overview
How to
'*' °'
Run & Deploy
11i
• Concepfuo{ оvем w uf
• Add а Controi;er and '\/tri1N
• ~'l'JP'!!~s~t_,,-.ASP.NEТ t ; i . - J
• Samplt pagu utmg ASP.NET
*
Рис.
2.6.
• Runy0urapp
.,.,~~~'"""
Выполнение примера проекта
Вы видели. что для отображения проекта среда
Visual Studio
открывает окно бра­
узера. Можно указать любой установленный браузер. щелкнув на кнопке со стрелкой
llS Express в панели инструментов и выбрав нужный
Web Browser (Веб-браузер) . как показано на рис. 2. 7.
правее кнопки
ка в меню
Jert
CodeMaid
Anгlyze
Window
вариант из спис­
Help
~· i .P;
115 Expre:ss
llS Express
Partylnvites
Web Browser (Google Chrome)
Google Chrome
Google Chrome Canary
~
lnternet Explorer
Microsoft Edge
Рис.
2.7.
Выбор браузера
В дальнейшем во всех примерах будет использоваться браузер
Google Chrome Canary.
Microsoft Edge.
Google Chrome
или
но вы можете применять любой современный браузер . включая
Глава 2. Ваше первое приложение MVC
37
Добавление контроллера
В рамках паттерна
В
ASP.NET
от класса
Саге
MVC
MVC
входящие запросы обрабатываются контроллерами.
контроллеры
-
это просто классы С# (обычно унаследованные
Microsoft .AspNetCore .Mvc. Controller,
базовым классом контроллера
MVC).
который является встроенным
Каждый открытый метод в контроллере назы­
вается методом действия, что означает возможность его вызова из веб-среды через
URL для выполнения какого-то действия. В
MVC контроллеры помещаются в папку Controllers,
Visual Studio при настройке проекта.
некоторый
соответствии с соглашением
автоматически создаваемую
Совет. Вы вовсе не обязаны соблюдать указанное или большинство других соглашений MVC,
но рекомендуется его придерживаться
-
и не в последнюю очередь потому, что это по­
может уяснить примеры, приведенные в настоящей книге.
Среда
Visual Studio
добавляет в проект класс стандартного контроллера, который
Controllers в окне Solutioп Explorer. Файл называет­
HomeController. cs. Файлы классов контроллеров имеют имена, завершающиеся
словом Controller, т.е. в файле HomeController. cs содержится код контроллера по
можно увидеть. раскрыв папку
ся
имени Ноте
-
стандартного контроллера. используемого в приложениях МVС. Щелкнем
на имени файла
HomeController. cs
в окне
Solution Explorer.
чтобы среда
Visual Studio
2.1.
открыла его для редактирования. Отобразится код С#, приведенный в листинге
Листинг
2.1.
Первоначальное содержимое файла
из папки
using
using
using
using
using
using
using
HomeController. cs
Controllers
System;
System.Collections.Gener ic;
System.Diagnostics;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Mvc ;
Partyinvites.Models;
namespace Partyinvites.Controllers
puЫic class HomeController : Controller
puЫic IActionResult Index() {
return View () ;
IActionResult About() {
ViewData [ "Message") = "Your application description page. ";
return View ();
puЫic
IActionResult Contact() {
ViewData["Message"J = "Your contact page.";
return View ();
puЫic
IActionResult Error() {
return View(new ErrorViewModel { Requestid
?? HttpContext.Traceidentif ier )) ;
puЫic
Activity.Current?.Id
38
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Заменим код в файле
HomeContr o ller. cs
кодом. показанным в листинге
2 .2.
Здесь были удалены все методы кроме одного, у которого изменен возвращаемый
тип и его реализация, а также удалены операторы
usi n g
для неиспользуемых про­
странств имен.
Листинг
2.2.
Изменение содержимого файла
из папки
Homecontroller. cs
Controllers
using Microsoft.AspNetCo r e .Mvc;
namespace Partylnvites.Controllers
puЫic class HomeController : Con troller
puЫic string Index () {
return "Hell o World";
Изменения не приводят к особо впечатляющим результатам. но их вполне доста­
точно для хорошей демонстрации . Метод по имени
он возвращает строку
Debugging
из меню
"Hello World".
Debug
в
Index ()
изменен так. что теперь
Снова запустим проект, выбрав пункт
Start
Visua\ Studio.
Совет. Если вы оставили в функционирующем состоянии приложение из предыдущего раз­
дела, тогда выберите в меню
Stop
Debuggiпg и затем
Debug пункт Restart (Перезапустить)
Start Debuggiпg.
или при желании пункт
Браузер сделает НТТР-запрос серверу. Стандартная конфигурация
MVC
матривает, что данный запрос будет обрабатываться с применением метода
предус­
I ndex ( ),
называемого методом действия или просто действием . а результат. полученный из
этого метода . будет отправлен обратно браузеру (рис.
2.8).
С 1 <D localhost:57628
Hello World
Рис.
2.8.
Совет. Обратите внимание, что среда
URL,
Вывод из метода действия
Visual Studio направляет браузер
на порт
57628.
Внутри
который будет запрашивать ваш браузер , почти наверняка будет присутствовать
другой номер порта, т.к.
Visual Studio при
создании проекта выделяет произвольный порт.
Если вы заглянете в область уведомлений панели задач
Windows, то найдете там значок
llS Express. Данный значок представляет усеченную версию полного сервера прило­
жений llS, которая входит в состав Visual Studio и используется для доставки содержимого
и служб ASP.NEТ во время разработки . Развертывание проекта MVC в производственной
среде будет описано в главе 12.
для
Глава 2. Ваше первое приложение MVC
39
Понятие маршрутов
В дополнение к моделям, представлениям и контроллерам в приложениях
применяется система маршрутизации ASP.NEТ, которая определяет, как
ражаются на контроллеры и действия. Маршрут
начальных. Можно запрашивать любой из следующих
.
•
Visual Studio
создает
она добавляет ряд стандартных маршрутов, выступающих в качестве
MVC,
на действие
MVC
отоб­
это правило, которое использу­
-
ется для решения о том, как обрабатывать запрос. Когда среда
проект
URL
Index
класса
URL,
и они будут направлены
HomeController:
/
/Ноте
• /Home/Index
Таким образом, когда браузер запрашивает httр://ваш-сайт/ или
ваш_ сайт/Ноте, он получает вывод из метода
Index ()
Можете проверить сказанное самостоятельно, изменив
момент он будет выглядеть как
тат
-
строку
<Enter>.
URL в
URL
порцию
http://
HomeController.
браузере. В настоящий
ht tp: / /localhost: 5 7 62 8 /,но
часть может быть другой. Если вы добавите к
и нажмете клавишу
класса
представляющая порт
/Home
или
/Home/Index
то получите от приложения МVС тот же самый резуль­
"Hello World".
Это хороший пример получения выгоды от соблюдения соглашений. поддержива­
емых
ASP.NET Core MVC.
В данном случае соглашение заключается в том, что сущес­
твует класс контроллера по имени
HomeController,
который будет служить старто­
вой точкой приложения МVС. Стандартная конфигурация, создаваемая средой
Studio для нового проекта,
Visual
предполагает, что мы будем следовать такому соглашению.
И поскольку мы действительно соблюдаем соглашение, то автоматически получаем
поддержку всех
URL
из приведенного выше списка. Если не следовать соглашению,
тогда конфигурацию пришлось бы модифицировать для указания на контроллер, со­
зданный взамен стандартного. В рассматриваемом простом примере стандартной
конфигурации вполне достаточно.
Визуализация веб-страниц
Выводом предыдущего примера была не НТМL-разметка. а просто строка
World".
"Hello
Чтобы сгенерировать НТМL-ответ на запрос браузера, понадобится создать
представление, которое сообщает
MVC,
каким образом генерировать ответ на запрос,
поступивший из браузера.
Создание и визуализация представления
Прежде всего. необходимо модифицировать метод действия
но в листинге
2.3.
Index
(),как показа­
Для простоты восприятия в этом и во всех будущих листингах из­
менения выделяются полужирным.
Листинг
2.3.
Визуализация представления в файле
из папки
Controllers
using Microsoft.AspNetCore.Mvc;
namespace Partyinvites.Controllers
Homecontroller. cs
40
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
puЫi c
class HomeContro ller : Co ntro ller {
ViewResul t Index ()
return View ( "МyView") ;
puЬlic
Возвращая из метода действия объект
визуалuзацuu представления. Объект
ViewResul t . мы инструктируем MVC о
ViewResult создается посредством вызова
метода View () с указанием имени представления . которое должно применяться, т.е.
MyView. Запустив приложение, можно заметить. что инфраструктура MVC пытается
найти представление, как отражено в сообщении об ошибке на рис.
2.9.
An unhandled exception occurred while processing the request.
lnvalidOpe1·at,onExceptюn: Т11е
ve\v 'MyView· was not found. TN: follo\чing locations
\-vere searched:
/Vie~vs/1-iome/MyV'ew.cshtml
/Views/Sha1·ed/MyView.cshtm'
Microso~Asp etCore.MV<:.Vie•'IEng1nes.Vi€\vEngiмResult.EnsureSuccess'ul(IEnum~rable<stnng>
Q11ery
Рис.
Cnokies
2.9.
original ocations)
HPadPr<
Инфраструктура
MVC
пытается найти представление
Сообщение об ошибке очень полезно. Оно объясняет. что инфраструктура
MVC
не
смогла найти представление. указанное для метода действия. и показывает. где произ­
водился поиск . Представления хранятся в подпапках внутри папки
представления. которые связаны с контроллером
Views / Home.
Vi ews .
Например.
содержатся в папке по имени
Представления, не являющиеся специфическими для отдельного конт­
роллера, хранятся в папке под названием
ет папки
Home ,
Home
и
Sha red
(Model-View-Controller).
Views/Shared.
Среда
Visual Studio созда­
Web Application
автоматически, когда используется шаблон
и в рамках начальной подготовки проекта помещает в них не­
сколько представлений-заполнителей.
Чтобы создать представление. необходимое для текущего примера. раскроем пап­
ку
Vi e ws
в окне Solutioп
Explorer.
щелкнем правой кнопкой мыши на папке
и выберем в контекстном меню пункт Add'~
Среда
New ltem
Ho me
(Добавитьq Новый элемент).
Visual Studio предложит список шаблонов элементов. Добравшись до катего­
ASP.NET CoreqWebqдSP.NET в панели слева, выберем элемент MVC View Page
(Страница представления МVС) в центральной панели (рис. 2. 1О). (Не следует приме­
нять шаблон Razor Page (Страница Razor), который не относится к МVС Framework.)
рии
Глава 2. Ваше первое приложение MVC
Add №w Jtem - Portylnv•t«
'
Sortby:~
" lnstalled
~ ASP .NП
~·
!.;
Cort
Code
w.ь
ASP.NEТ
Ш)'
General
S<ripts
Content
~
Controller Сlш
~· W•b API Controller Сlш
!.;
Gener4I
"
МVС
-- [@
··1"'
Online
ASP.NET Coro
Core
Туре: ASP.NfТ
MVC View P•g•
ASP.NET Core
ASP.NET Core
R.zor P•g•
~ М'/С Vi•w Р•9•
ASP.NEТ Core
Ш)'
MVC View Layout P•g•
ASP .NEТ
~·
•
MVC V1ew Storl P•ge
ASP .NEТCoro
lfl"
'
MVC V.ow lmports Poge
ASP.NEТ
~-
Razor Tag Helper
ASP .NEТCoro
~
><:
Р·
Se•rch (Ctrl•f)
...
41
Core
Coro
MyV.ew.<Shtml
№me:.
Рис.
Совет. В папке
Views
2. 1О.
Создание представления
уже есть несколько файлов, которые были добавлены
целью предоставления начального содержимого, показанного на рис.
Visual Studio
2.6.
с
Такие файлы
можно спокойно проигнорировать.
Введем в поле
Name (Имя) имя My Vi ew . cshtml и щелкнем на кнопке Add (Добавить)
Visual Studio создаст файл V i ew s / Ho me /
для создания представления. Среда
MyVi e w. cs h t ml
и откро ет его для р едактиров ания. Н ачальное содержимо е ф айла
-
это просто ряд ком м ентариев и заполнитель. Заменим его с одер ­
представления
жимым, приведенным в листинге
2.4.
Совет. Довольно легко создать файл представления не в той папке. Если в итоге вы не по­
лучили файл по имени
MyV iew. c sh t ml
в папке
View s / Home,
тогда удалите созданный
файл и попробуйте создать заново.
Листинг
2.4.
Замена содержимого файла МyView. cshtml из папки Views/Home
@{
La y ou t
null;
< !DOCTYPE h t ml >
<h t ml >
<head >
<me ta name="vi ewpor t" content="width=dev i ce - wi d th" / >
<title>Index< / title>
</ head>
<b ody>
<d i v >
42
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Hello World (from the view)
</div>
</body>
</html>
Теперь
файл
представления
содержит
главным
образом
НТМL-разметку.
Исключением является часть, которая имеет следующий вид:
@{
Layout
null;
Такое выражение будет интерпретироваться механизмом визуализации
Razor,
ко­
торый обрабатывает содержимое представлений и генерирует НТМL-разметку. от­
правляемую браузеру. Показанное выше простое выражение
му
Razor
Razor сообщает механиз­
о том, что компоновка не применяется; компоновка похожа на шаблон для
НТМL-разметки, который посылается браузеру (и будет описан в главе
проигнорируем механизм
Razor
5).
Мы пока
и возвратимся к нему позже. Чтобы увидеть создан­
ное представление, выберем в меню
Debug
пункт
Start Debugging
2.11.
для запуска прило­
жения. Должен получиться результат, приведенный на рис.
С @)!Oёathost:57628 _ _ _ _ _ _ _ _ _ _.
Hello World (from t11e view)
Рис.
2.11.
Тестирование представления
При первом редактировании метод действия
Index ()
возвращал строковое значе­
ние, так что инфраструктура МVС всего лишь передавала браузеру строковое значение
в том виде. как есть. Теперь, когда метод
инфраструктура
MVC
Index ()
возвращает объект
ViewResult,
визуализирует представление и возвращает сгенерированную
НТМL-разметку. Мы сообщили инфраструктуре
MVC
о том, какое представление
должно использоваться, поэтому с помощью соглашения об именовании она автома­
тически выполнила его поиск. Соглашение предполагает. что имя файла представле­
ния совпадает с именем метода действия, а файл представления хранится в папке,
названной по имени контроллера:
/Views/Home/MyView.cshtml.
ViewResul t методы действий могут возвращать другие ре­
зультаты. Например, если мы возвращаем объект RedirectResul t, то браузер будет
перенаправлен на другой URL. Если мы возвращаем объект HttpUnauthorizedResul t,
Кроме строк и объектов
то предлагаем пользователю войти в систему. Все вместе такие объекты называются
результатами действий. Система результатов действий позволяет инкапсулировать
и повторно использовать часто встречающиеся ответы в действиях. В главе
17
рассмотрим их подробнее и продемонстрируем разные способы их применения.
мы
Глава 2. Ваше первое приложение MVC
43
Добавление динамического вывода
Весь смысл платформы для разработки веб-приложений состоит в конструирова­
нии и отображении динамического вывода. В рамках МVС работа контроллера заклю­
чается в подготовке данных и передаче их представлению, которое отвечает за их
визуализацию в виде НТМL-разметки.
Один из способов передачи данных из контроллера в представление предусмат­
ViewBag, который является членом базового
ViewBag - это динамический объект. в котором
ривает использование объекта
Controller.
По существу
класса
можно
устанавливать произвольные свойства. делая их значения доступными в любом визу­
ализируемом далее представлении. В листинге
2.5
способом простых динамических данных в файле
Листинг
2.5.
Установка данных представления в
из папки
демонстрируется передача таким
HomeController. cs.
HomeController.cs
Controllers
using System;
using Microsoft.AspNetCore.Mvc;
narnespace Partylnvites.Controllers
puЫic
class HorneController : Controller
ViewResult Index() (
int hour = DateTime.Now.Hour;
ViewBag. Greeting = hour < 12 ? "Good
return View("MyView");
puЬlic
Мorning"
"Good Afternoon";
Данные для представления указываются во время присваивания значения свойс·
тву
ViewBag. Greeting.
Свойство
Greeting
не существует вплоть до момента. ког­
да ему присваивается значение. что позволяет передавать данные из контроллера в
представление в свободной и гибкой манере. без необходимости в предварительном
определении классов. Чтобы получить значение данных. необходимо еще раз сослать­
ся на свойство
ге
2.6,
ViewBag. Greeting,
но уже в представлении. как показано в листин­
содержащем изменение. которое бьmо внесено в файл
Листинг
2.6.
MyView. cshtml.
Извлечение значения данных ViewВag в файле МyView.
из папки
Views/Home
@(
Layout = null;
<!DOCTYPE htrnl>
<htrnl>
<head>
<rneta narne="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
cshtml
44
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
@ViewBag.Greeting World (from the view)
</div>
</body>
</html>
Добавленный в листинге фрагмент
-
это выражение
Razor,
которое оценивается.
View () в
Index () контроллера приводит к тому. что MVC находит файл представления
MyV iew. csh tml и запрашивает у механизма визуализации Razor синтаксический
анализ содержимого данного файла. Механизм Razor ищет выражения, подобные до­
бавленному в листинге 2.6, и обрабатывает их. В рассматриваемом примере обработ­
когда
MVC
применяет представление для генерации ответа. Вызов метода
методе
ка выражения означает вставку в представление значения. которое было присвоено
свойству
ViewBag. Greeting
внутри метода действия .
Выбор для свойства имени
Greeti ng
не диктуется какими-то особыми соображе­
ниями. Его можно было бы заменить любым другим именем. и все работало бы точно
так же при условии, что имя, используемое в контроллере. совпадает с именем.
ко­
торое применяется в представлении. Присваивая значения более чем одному свойс­
тву, можно передавать из контроллера в представление множество значений данных.
После запуска проекта будет виден результат внесенных изменений (рис.
2.12).
С /~alhost:57628
Good Afteгnoon Woгld
Рис.
2.12.
(fгoin
tl1e vie'-"')
Динамический ответ, сгенерированный
MVC
Создание простого приложения для ввода данных
В оставшихся разделах главы будут исследованы другие базовые функциональные
средства
MVC
за счет построения простого приложения для ввода данных. В этом раз ­
деле мы собираемся несколько увеличить темп изложения. Целью является демонс­
трация инфраструктуры МVС в действии, а потому некоторые объяснения того. что
происходит "за кулисами", будут пропущены. Однако не беспокойтесь
-
мы вернемся
к подробному обсуждению данных тем в последующих главах.
Предварительная настройка
Представьте себе, что ваша подруга решила организовать вечеринку в канун ново ­
го года и попросила создать веб - приложение, которое позволяет приглашенным отве ­
тить на приглашение по электронной почте. Она высказала пожелание относительно
четырех основных средств, которые перечислены ниже:
•
•
домашняя страница, отображающая информацию о вечеринке;
форма, которая может использоваться для ответа на приглашение (гepondez
vous plait - RSVP):
s'il
Глава 2. Ваше первое прило жение MVC
•
проверка достоверности для формы
RSVP.
45
которая будет отображать страницу с
выражением благодарности за внимание;
•
итоговая страница, которая показывает, кто собирается прийти на вечеринку.
В последующих разделах мы достроим проект
MVC ,
созданный в начале главы, и
добавим в него перечисленные выше средства. Первый пункт можно убрать из спис­
ка. применив то, что было показано ранее
-
добавить НТМL-разметку с подробной
информацией о вечеринке в существующее представление . В листинге
содержимое файла
Листинг
Vi ews / Home / MyView . cshtml
2.7 приведено
с внесенными дополнениями.
2. 7. Отображение подробностей о вечеринке в файле МyView. cshtml
из папки Views/Home
@(
La yout = nu l l;
<IDOCTYPE html >
<html>
<head>
<meta name=" v iewport" c ontent="width=de vice - width" / >
<title>Inde x</title >
</head >
<b ody>
<div>
@ViewBag.Greeting Worl d ( f rom the vie w)
<p>We' re going to have an exci ting party. <br />
(То do: sell i t better. Add pictures or something.)
</р>
</ div >
</ b ody>
</ html >
Мы двигаемся в верном направлении. Если запустить приложение, выбрав в меню
Debug
пункт
Start Debugging,
то отобразятся подробности о вечеринке
полнитель для подробностей. но сама идея должна быть понятной (рис.
Good After11oon World
(froш
точнее, за­
-
2.13).
the view)
We'Ie goi11g to have an exciting party.
(То do: sell it bettel". Add pich1Ies or sornetl1iпg. )
Рис.
2.1 З.
Добавление информации о вечеринке в НТМL- разметку представления
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
46
Проектирование модели данных
Буква "М" в аббревиатуре МVС обозначает тodel (модель), которая является самой
важной частью приложения. Модель
-
это представление реальных объектов, про­
цессов и правил, которые определяют сферу приложения, известную как предметная
область. Модель, которую часто называют моделью предметной области, содержит
объекты С# (или объекты предметной области), образующие "вселенную" приложе­
ния, и методы. позволяющие манипулировать ими. Представления и контроллеры
открывают доступ клиентам к предметной области в согласованной манере, и любое
корректно разработанное приложение
MVC
начинается с хорошо спроектированной
модели, которая затем служит центральным узлом при добавлении контроллеров и
представлений.
Для проекта
Partyinvi tes
сложная модель не требуется, поскольку приложение
совсем простое. и нужно создать только один класс предметной области, который по­
лучит имя
GuestResponse.
Такой объект будет отвечать за хранение. проверку досто­
верности и подтверждение ответа на приглашение
По соглашению
имени
Web
(RSVP).
классы, которые образуют модель, помещаются в папку по
которую среда
Models,
шаблона
MVC
Visual Studio
создает автоматически в случае выбора
Applicatioп (Model-View-Coпtroller).
Чтобы создать файл класса, щелкнем правой кнопкой мыши на папке
Solutioп
Explorer
и выберем в контекстном меню пункт
Назначим новому классу имя
GuestResponse. cs
Add9Class
Models
в окне
(Добавить9Класс).
и щелкнем на кнопке
Add (Добавить).
2.8.
Приведем содержимое нового файла класса к виду, показанному в листинге
Листинг
2.8.
Содержимое файла GuestResponse.cs из папки Models
namespace Partyinvites.Models
puЫic
class GuestResponse {
puЬlic
puЫic
puЫic
puЫic
string Name { get; set;
string Email { get; set; )
string Phone { get; set; )
bool? WillAttend { get; set;
Совет. Вы могли заметить, что свойство
т.е. оно может принимать значение
WillAttend имеет тип bool, допускающий null,
true, false или null. Обоснование такого решения
будет приведено в разделе "Добавление проверки достоверности" далее в главе.
Создание второго действия и строго
типизированного представления
Одной из целей разрабатываемого приложения является включение формы
RSVP,
что означает необходимость определения метода действия, который сможет получать
запросы к форме. В единственном классе контроллера можно определять множество
методов действий, а по соглашению связанные действия группируются вместе в од­
ном контроллере. В листинге
к контроллеру Ноте.
2.9
иллюстрируется добавление нового метода действия
Глава 2. Ваше первое приложение MVC
Листинг
2.9.
Добавление метода действия в файле
из папки
47
HomeController. cshtml
Controllers
using System;
using Microsoft.AspNetCore.Mvc;
namespace Partyinvites.Controllers
puЫic
class HomeController : Controller
ViewResult Index() {
int hour = DateTime.Now.Hour;
ViewBag.Greeting = hour < 12 ? "Good Morning"
return View ( "MyView") ;
puЫic
"Good Afternoon";
ViewResul t RsvpForm ()
return View () ;
puЬlic
Метод действия
RsvpForm ()
вызывает метод
View ()
без аргументов, что сообща­
ет инфраструктуре МVС о необходимости визуализации стандартного представления.
связанного с этим методом действия, которым будет представление с таким же име­
нем, как у метода действия
(RsvpForm. cshtml в данном случае).
Views/Home и выберем
Щелкнем правой кнопкой мыши на папке
в контекстном
MVC View
MVC). укажем RsvpForm. cshtml в качестве имени
нового файла и щелкнем на кнопке Add (Добавить), чтобы создать файл. Приведем
содержимое нового файла в соответствие с листингом 2.10.
меню пункт Add~ New
Page
ltem
(Добавить~ Новый элемент). Выберем шаблон
(Страница представления
Листинг
Содержимое файла
2.10.
RsvpForm. cshtml
из папки
Views/Home
@model Partyinvites.Models.GuestResponse
@{
Layout
=
null;
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>RsvpForm</title>
</head>
<body>
<div>
This is the RsvpForm.cshtml View
</div>
</body>
</html>
Содержимое состоит в основном из НТМL-разметки. но с добавлением Rаzоr­
выражения
@model,
которое используется для создания строго типизированного
представления. Строго типизированное представление предназначено для визуали-
48
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
зации специфического типа модели , и если указан желаемый тип (в данном случае
класс
GuestResp o n se
из пространства имен
Pa r ty l nv i tes . Models),
то
MVC
может
со здать ряд удобных сокраще ний , чтобы сделать его проще. Вскоре мы задействуем
преимущество характеристики строгой типизации .
Чтобы протестировать новый метод действия и его представление , запустим при­
ложение, выбрав в меню
Debug пункт Start Debugging, и с помощью бр аузера перей­
URL вида / Horne/RsvpForrn.
Инфраструктура MVC применит описанное ранее соглашение об именовании для
дем на
направления запроса методу действия
Rs vpFo r m () ,
Н оте . Данный метод дей ствия указывает
MVC
определенному в контроллере
о том . что должно визуализировать­
ся стандартное представление , которое посредством еще одного применения того же
соглашения об именов ании ви зуализирует
Результат показ ан на рис .
С 1 <D
Rsvp Fo r rn . cs hml
из папки
Views/Home .
2. 14.
localhos ·Sl628/Home/RsvpForm
This is the RsvpFoп11.csht111l View
Рис.
2.14.
Визуализация второго представления
Ссылка на методы действий
Нам необходимо со здать в представлении
деть представление
Rsvp Forrn
специфиче с кий метод действия (листинг
Листинг
2.11.
ссылку. чтобы гости могли ви­
URL,
который указывает на
2.11) .
Добавление ссылки на форму
из папки
MyView
без обязательного знания
RSVP
в файле МyView. cshtml
Views/Home
@(
Layout = nul l ;
< ! DOCTY PE html>
<h tml >
<h e ad>
<me ta name= "v i ewport " cont e nt= "wi dth=device- width " />
<ti t l e> Index</tit le >
</ head>
<b ody>
<d i v>
@ViewBag . Gre eting World (from th e v i ew)
<p >We ' re going to have a n exci t ing party . <br />
(То do: se ll it be t t er. Add pic tures or some thin g.)
</р>
Глава 2. Ваше первое приложение MVC
49
<а asp-action="RsvpForm">RSVP Now</a>
</div>
</body>
</html>
В листинге
добавлен элемент а, который имеет атрибут
2.11
asp-action . Данный
атрибут является примером атрибута вспомогательной функции дескриптора, т.е.
инструкцией Razoг, которая будет выполнена, когда представление визуализируется.
Атрибут
это инструкция по добавлению к элементу а атрибута
asp-action -
содержащего
URL
href,
для метода действия. Работа вспомогательных функций дескрип­
торов объясняется в главах
24-26,
а пока достаточно знать, что
asp-action -
про­
стейший вид атрибута вспомогательной функции дескриптора для элементов а. Он
указывает
Razor на
необходимость вставки
URL для
метода действия, определенного в
том же контроллере, для которого визуализируется текущее представление. Запустив
проект, можно увидеть ссылку. которую создала вспомогательная функция дескрип­
тора (рис.
2.15).
С
Good Mon1ing \\iorld (from the \·ie\\·)
\Ve're going to ha\"e а.11 exciting party.
(То do: sell it bener. Add pictures or something.)
Рис.
2.15.
!' ~ ~ocalh?s_ 576~8~ome/~svp:crn~
"tr
This is the Rs\·pForm.cshtml \ 'ie\\"
Ссылка на метод действия
Если после запуска приложения навести курсор мыши на ссылку
RSVP Now
(Ответить на приглашение) в окне браузера, то можно заметить, что ссылка указы­
вает на следующий
URL
(возможно, вашему проекту
Visual Studio
назначит другой
номер порта):
http://localhost:57628/Home/RsvpForm
Здесь требуется соблюдать один важный принцип: вы должны использовать средс­
тва. предлагаемые
MVC
для генерации
URL,
а не жестко кодировать их в своих пред­
ставлениях. Когда вспомогательная функция дескриптора создает атрибут
hre f
для
элемента а. она инспектирует конфигурацию приложения. чтобы выяснить, каким
должен быть
URL.
В итоге появляется возможность изменять конфигурацию прило­
жения с целью поддержки разных форматов
URL без
необходимости в обновлении ка­
ких-либо представлений. Особенности работы такого механизма рассматриваются в
главе
15.
Построение формы
Теперь, когда строго типизированное представление создано и достижимо из представ­
ления
Index,
займемся подгонкой содержимого файла
RsvpForm. cshtml , чтобы превра­
GuestResponse (листинг 2.12).
тить его в НТМL-форму для редактирования объектов
50
Часть 1. Введение в инфраструктуру ASP.NEТ Core MVC 2
Листинг
2.12.
Создание представления в виде формы в файле
из папки
RsvpForm. csh tml
Views/Home
@model Partylnvites.Models.GuestResponse
@{
Layout = null;
<IDOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>RsvpForm</title>
</head>
<body>
<form asp-action="RsvpForm" method="post">
<р>
<laЬel
asp-for="Name">Your
<input asp-for="Name" />
name:</laЬel>
</р>
<р>
<laЬel asp-for="Email">Your
<input asp-for="Email" />
email:</laЬel>
</р>
<р>
<laЬel asp-for="Phone">Your
<input asp-for="Phone" />
phone:</laЬel>
</р>
<р>
<laЬel>Will you attend?</laЬel>
<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>
</р>
<Ьutton type="suЬmit">SuЬmit
RSVP</button>
</form>
</body>
</html>
Для каждого свойства класса модели
и
input
(или элемент
select
GuestResponse определены элементы label
WillAttend). Каждый элемент ассо­
в случае свойства
циирован со свойством модели с применением еще одного атрибута вспомогательной
функции дескриптора
- asp-for.
Атрибуты вспомогательных функций дескрипторов
конфигурируют элементы, чтобы привязать их к объекту модели. Вот пример НТМL­
разметки, которую генерируют вспомогательные функции дескрипторов для отправки
браузеру:
<р>
<label for="Name">Your name:</label>
<input type="text" id="Name" name="Name" value="">
</р>
51
Глава 2. Ваше первое приложение MVC
Атрибут
asp-for
asp-for
в элементе
в элементе
label
устанавливает значение атрибута
устанавливает атрибуты
i nput
id
и
name.
for . Атрибут
В данный момент это
не выглядит особенно полезным, но по мере определения прикладной функциональ­
ности вы увидите, что ассоциирование элементов со свойством модели предлагает
дополнительные преимущества.
Более непосредственный результат дает атрибут
который использует конфигурацию маршрутизации
атрибута
ac tion
в
URL,
asp -action
URL
в элементе
form,
приложения для установки
нацеленный на специфический метод действия . например:
<form method="p o st"
actio n ~" / Home/RsvpForm">
Как и в случае атрибута вспомогательной функции дескриптора, примененного к
элементу а, преимущество такого подхода заключается в том, что вы можете изме­
нять систему
URL,
используемую приложением, и содержимое, которое генерируется
вспомогательными функциями дескрипторов, автоматически отразит изменения.
Форму можно увидеть, запустив приложение и щелкнув на ссылке
(рис.
RSVP Now
2. 16).
f-
<D localhost:S7628/Home/RsvpForn1
С
----=-==iJ
Your name: [
Your email:
___
Your phone: [
\\7i.ll
_
-:=J
you attend? Гchoose В_!) option т
{ Submit RSVP
Рис.
j
2.16.
Добавление НТМL-формы к приложению
Получение данных формы
Мы пока еще не указали инфраструктуре
MVC.
что должно быть сделано, когда
форма отправляется серверу. При нынешнем состоянии приложения щелчок на кноп­
ке
Submit RSVP
(Отправить
RSVP)
лишь очищает любые значения. введенные внутри
формы . Причина в том, что форма осуществляет обратную отправку методу действия
Rs vpForm ()
контроллера
Home,
который только сообщает
MVC
о необходимости пов­
торной визуализации представления.
Чтобы получить и обработать отправленные данные формы, мы собираемся вос­
пользоваться основной возможностью контроллера. Мы добавим второй метод дейс­
твия
RsvpForm ( ) .
чтобы получить в свое распоряжение следующие возможности.
52
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
•
Метод. который отвечает н.а НТ'ГР-запросы
GET. Каждый раз, когда кто-то щел­
GET. Эта версия действия будет
кает на ссылке, браузер обычно выдает запрос
отвечать за отображение изначально пустой формы, когда кто-нибудь впервые
посещает
/Home/RsvpForm.
• Метод, который отвечает н.а НТ'ГР·запросы POST. По умолчанию формы. ви­
зуализированные с помощью Html. BeginForm (), отправляются браузером как
запросы POST. Эта версия действия будет отвечать за получение отправленных
данных и принятие решения о том, что с ними делать.
Обработка запросов
GET
и
POST
в отдельных методах С# способствует обеспече­
нию аккуратности кода контроллера, т.к. описанные выше два метода имеют разные
обязанности. Оба метода действий вызываются через тот же самый
URL, но в зави­
- GET или POST - инфраструктура MVC вызывает подходя­
2.13 показаны изменения, которые необходимо внести в класс
симости от вида запроса
щий метод. В листинге
HomeController.
Листинг
2.1 З.
Добавление метода в файле HomeController. cs
из папки
Controllers
using System;
using Microsoft.AspNetCore.Mvc;
using Partyinvites.Models;
namespace Partyinvites.Controllers
puЫic
class HomeController : Controller
ViewResult Index() {
int hour = DateTime.Now.Hour;
ViewBag. Greeting = hour < 12 ? "Good Morning"
return View("MyView");
puЬlic
"Good Afternoon";
[HttpGet]
ViewResult RsvpForm() {
return View ();
puЫic
[HttpPost]
ViewResult RsvpForm(GuestResponse guestResponse)
puЫic
11
Что сделать:
сохранить ответ от гостя
return View();
Существующий метод действия
который указывает
запросов
GET.
RsvpForm ()
был снабжен атрибутом
HttpGet,
на то, что данный метод должен применяться толыю для
Затем была добавлена перегруженная версия метода
нимающая объект
сообщает
MVC
MVC
GuestResponse.
RsvpForm (),при­
HttpPost, который
только с запросами POST.
К ней был применен атрибут
о том, что новый метод будет иметь дело
Произведенные добавления объясняются в последующих разделах. Кроме того, было
импортировано пространство имен
чтобы на тип модели
Partyinvi tes. Models. Это сделано для того.
GuestResponse можно было ссылаться без необходимости в
указании полностью определенного имени класса.
Глава 2. Ваше nервое nриложение MVC
53
Использование привязки модели
Первая перегруженная версия метода действия
самое представление, что и ранее (файл
показанной на рис.
2.16.
RsvpForm () визуализирует то же
RsvpForm. cshtml), для генерации формы,
Вторая перегруженная версия более интересна из-за нали­
чия параметра. Но с учетом того, что данный метод действия будет вызываться в от­
вет на НТГР-запрос
POST,
а тип
GuestResponse
является классом С#, каким образом
они соединяются между собой?
Секрет кроется в привязке модели -удобной функциональной возможности МVС, пос­
редством которой производится разбор входящих данных и применение пар "ключ/зна­
чение" в НТГР-запросе для заполнения свойств в типах моделей предметной области.
Привязка модели
-
мощное и настраиваемое средство, которое избавляет от кропот­
ливого и тяжелого труда по взаимодействию с НТГР-запросами напрямую и позволяет
работать с объектами С#, а не иметь дело с индивидуальными значениями данных, от­
правляемыми браузером. Объект
RsvpForm ( )
GuestResponse,
который передается методу действия
в качестве параметра, автоматически заполняется данными из полей фор­
мы. Привязка модели, включая ее настройку. подробно рассматривается в главе
26.
Одной из целей приложения является предоставление итоговой страницы с дета­
лями о том, кто придет на вечеринку, что означает необходимость сохранения полу­
чаемых ответов. Мы собираемся делать это за счет создания в памяти коллекции объ­
ектов. В реальном приложении такой подход не подойдет, т.к. данные ответов будут
утрачиваться в результате останова или перезапуска приложения, но он позволяет
сосредоточить внимание на
MVC
и создать приложение, которое может быть легко
сброшено в свое начальное состояние.
Совет. В главе
8 будет
продемонстрировано использование
MVC
для постоянного хранения
и доступа к данным как часть более реалистичного примера приложения под названием
SportsStore.
Мы добавили в проект новый файл, щелкнув правой кнопкой мыши на папке
Models
ет имя
и выбрав в контекстном меню пункт Add~Class (Добавить~Класс). Файл име­
Reposi tory. cs
Листинг
2. 14.
и содержимое, показанное в листинге
Содержимое файла
Reposi tory. cs
из папки
2.14.
Models
using System.Collections.Generic;
namespace Partyinvites.Models (
puЫic
static class Repository
private static List<GuestResponse> responses =
new List<GuestResponse>();
puЫic static IEnumeraЫe<GuestResponse> Responses
get (
return responses;
puЬlic
static void AddResponse(GuestResponse response) (
responses.Add(response);
54
Часть 1. Введение в инфраструктуру ASP.NET Соге MVC 2
Класс
Reposi tory
и его члены объявлены статическими, чтобы облегчить со­
хранение и извлечение данных в разных местах приложения. Инфраструктура
MVC
предлагает более сложный подход к определению общей функциональности, называ­
емый внедрением зависимостей, который будет описан в главе
18,
но для простого
приложения вроде рассматриваемого вполне достаточно и статического класса.
Сохранение ответов
Теперь, когда есть куда сохранять данные, можно обновить метод действия, кото­
рый получает НТТР-запросы
Листинг
POST
(листинг
2.15).
Обновление метода действия в файле
2.15.
из папки
HomeController. cs
Controllers
using System;
using Microsoft.AspNetCore.Mvc;
using Partylnvites.Models;
namespace Partylnvites.Controllers
puЫic
class HomeController : Controller
ViewResult Index() {
int hour = DateTime.Now.Hour;
ViewBag.Greeting = hour < 12 ? "Good Morning"
return View ( "MyView") ;
puЬlic
"Good Afternoon";
[HttpGet]
ViewResult RsvpForm() {
return View () ;
puЬlic
[HttpPost]
ViewResult RsvpForm(GuestResponse guestResponse) {
Repository.AddResponse(questResponse);
return View("Thanks", questResponse);
puЫic
Все,
се
-
ект
что необходимо сделать с данными формы,
передать методу
GuestResponse,
Reposi tory. AddResponse ()
отправленными в
запро­
в качестве аргумента объ­
который был передан методу действия, чтобы ответ мог быть
сохранен.
Почему привязка модели не похожа на инфраструктуру
Web Forms?
В главе 1 упоминалось, что одним из недостатков традиционной инфраструктуры ASP.NET
Web Forms было сокрытие деталей НТТР и HTML от разработчиков. Вас может интересовать,
делает ли то же самое привязка модели MVC, которая применялась для создания объекта
GuestResponse из НТТР-запроса POST в листинге 2.15.
Нет, не делает. Привязка модели освобождает нас от решения утомительной и подвержен­
ной ошибкам задачи по инспектированию НТТР-запроса и извлечению всех требующихся
значений данных, но (что самое важное) при желании мы могли бы обрабатывать запрос
Глава 2. Ваше nервое nриложение MVC
вручную, поскольку
55
MVC обеспечивает легкий доступ ко всем данным запроса. Ничто не
скрыто от разработчика, но есть несколько удобных средств, которые упрощают работу с
НТТР и HTML; тем не менее, использовать их вовсе не обязательно.
Это может показаться едва заметной разницей, но по мере углубления знаний инфраструк­
туры
MVC вы увидите, что практика разработки в ней полностью отличается от традици­
Web Forms. Вы всегда будете осведомлены относительно того, как
онной инфраструктуры
обрабатываются получаемые приложением запросы.
Вызов метода
View ()
внутри метода действия
RsvpForm () сообщает MVC о том,
Thanks и передать ему объ­
что нужно визуализировать представление по имени
ект
GuestResponse. Для создания упомянутого представления щелкнем правой
кнопкой мыши на папке Views/Home в окне Solution Explorer и выберем в контекс­
тном меню пункт Add~New ltem (Добавить~Новый элемент). Укажем шаблон MVC
View Page (Страница представления MVC) из категории ASP.NET, назначим ему имя
Thanks. csh tml и щелкнем на кнопке Add (Добавить). Среда Visual Studio создаст
файл Views/Home/Thanks. cshtml и откроет его для редактирования. Поместим в
файл содержимое, приведенное в листинге
Листинг
2.16.
Содержимое файла Thanks. cshtml из папки Views/Home
2.16.
@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!</hl>
@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
</body>
</html>
asp-action="ListResponses ">here</ а> to see who is coming. </р>
<а
Представление
Thanks. cshtml
применяет механизм визуализации
Razor
для
GuestResponse, которое пе­
RsvpForm (). Выражение @model синтаксиса Razor
отображения содержимого на основе значения свойства
редается методу
View ()
внутри
указьmает тип модели предметной области, с помощью которого представление стро­
го типизировано.
Для доступа к значению свойства в объекте предметной области используется
конструкция
Model. ИмяСвойства. Например, чтобы получить значение свойства
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
56
Name, применяется Model. Name. Не беспокойтесь , если синтаксис Razor пока не по­
нятен
-
он более подробно объясняется в главе
5.
Thanks. мы получили базовый пример об­
MVC. Запустим приложение в Visual Studio, выбрав в
Теперь, когда создано представление
работки формы посредством
меню
Debug
пункт
Start Debugging,
занный на рис.
2.17
RSVP Now, введем на форме
Submit RSVP. Появится результат, пока­
щелкнем на ссылке
какие-нибудь данные и щелкнем на кнопке
(он может отличаться, если введено другое имя либо указано о
невозможности посетить вечеринку) .
Thank you, Joe!
Your email: ~oe@examp.!!:co'!:._
lt's great that you're coming. The drinks are a!ready in the fi-idge!
..:___:: ...
Yourphone:@SS-1234
\\"ill you attend? YeS.Thьethere
Click ~ to see \\'ho is coming.
•1
[sub~itRSVP]
Рис.
2. 17.
Представление
Than ks
Отображение ответов
В конце представления
Thanks . c shtml мы добавили элемент а для создания
ссылки, которая позволяет отобразить список людей , собирающихся посетить вече ­
ринку . С применением атрибута вспомогательной функции дескриптора
создается
URL,
<p>Click
который нацелен на метод действия по имени
<а
asp - ac t i o n
Li st Respons es ():
asp-action="ListResponses">here< /a > to see who is coming.
</р >
Наведя курсор мыши на ссылку, которую отображает браузер. легко заметить, что
URL вида /Home/ ListResponses . Это не соответствует ни одному
Home, и если щелкнуть на ссьтке. то появится стра­
ница ошибки "404 - Not Found" (не найдено) .
Мы устраним проблему путем создания в контроллере Home метода действия. на
который нацелен URL (листинг 2. 17).
она указывает на
м етоду действия в контролл е ре
Листинг
2.17. Добавление метода действия в файле HomeController . cs
using
using
using
using
Sys tem ;
Microsoft.AspNetCore.Mvc ;
Partylnvites.Models;
Syst0111.Linq;
namespace Partyln vites .Con t rollers
puЬlic
c lass Ho me Co n tro l le r : Contro ll er
Глава 2. Ваше nервое nриложение MVC
ViewResult Index() {
int hour = DateTime.Now.Hour;
ViewBag.Greeting = hour < 12 ? "Good Morning"
return View ( "MyView") ;
57
puЫic
"Good Afternoon";
[HttpGet]
ViewResult RsvpForm() {
return View () ;
puЫic
[HttpPost]
ViewResult RsvpForrn(GuestResponse guestResponse) {
Repository.AddResponse(guestResponse);
return View("Thanks", guestResponse);
puЫic
ViewResult ListResponses() {
return View(Repository.Responses.Where(r => r.WillAttend
puЬlic
==
true));
ListResponses ();он вызывает метод View (),
Reposi tory. Responses в качестве аргумента. Именно так метод
Новый метод действия называется
используя свойство
действия предоставляет данные строго типизированному представлению. Коллекция
объектов
GuestResponse
фильтруется с применением
LINQ,
так что используются
только ответы с положительным решением об участии в вечеринке.
ListResponses ()
Метод действия
не указывает имя представления, которое
должно применяться для отображения коллекции объектов
GuestResponse,
поэто­
му будет задействовано соглашение об именовании и МVС инициирует поиск пред­
ставления по имени
ListResponses. cshtrnl
в папках
Views/Horne
и
Views/Shared.
Views/
Чтобы создать представление, щелкнем правой кнопкой мыши на папке
Horne
в окне
Solution Explorer
и выберем в контекстном меню пункт Add~New
(Добавить~Новый элемент). Выберем шаблон
ния
MVC)
из категории
нем на кнопке
Add
ASP.NET,
2.18.
ListResponses. cshtrnl
и щелк­
2.18.
Отображение принятых приглашений в файле
из папки
@model
назначим ему имя
ltem
(Страница представле­
(Добавить). Приведем содержимое нового файла представления в
соответствие с листингом
Листинг
MVC View Page
ListResponses. cshtml
Views/Home
IEnumeraЬle<Partyinvites.Models.GuestResponse>
@{
Layout = null;
<!DOCTYPE html>
<htrnl>
<head>
<rneta narne="viewport" content="width=device-width" />
<title>Responses</title>
</head>
58
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
<body>
<h2>Here is t he list of people attending t he pa rty </ h2>
<tаЫе>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<t h>Phone</th >
</tr>
</thead>
<tbody>
@foreach ( Partyinvites.Mo dels.GuestResp on se r in Mode l) {
<tr>
<td>@r.Name</td>
<td >@ r.Email </ td >
<td>@r . Phone</td >
</tr>
</tbody>
</tаЫе>
</body>
</html>
Файлы представлений
Razor имеют расширение . csh tml. потому что содержат
HTML. Это можно заметить в листинге 2.18, где исполь­
смесь кода С# и элементов
зуется цикл
foreach
для обработки всех объектов
Gu estResponse , которые метод
View (). В отличие от нор­
forea ch из Razor содержит элементы
действия передает представлению с применением метода
мального цикла
HTML.
языка С# тело цикла
foreach
добавляемые к ответу. который будет отправлен обратно браузеру . В данном
представлении для каждого объекта
рый содержит элементы
td,
GuestRespo nse
генерируется элемент
Чтобы увидеть список в работе, запустим приложение, выбрав в меню
Start Debugging,
tr.
кото­
Debug
пункт
заполненные значениями свойств объекта .
отправим какие-то данные формы и затем щелкнем на ссьшке для про­
смотра списка ответов. Отобразится сводка по данным , введенным вами с момента за­
пуска приложения (рис.
2.18).
Представление не оформляет данные привлекательным
образом, но пока этого вполне достаточно, а стилизацией мы займемся позже в главе.
Responses
Here is the list of people attending the party
~ame
Email
Phone
Joe joe@ex.ample.com 555-1234
Alice alice@example.com 555-5678
Рис.
2.18.
Отображение сnиска участников вечеринки
Глава 2. Ваше первое приложение MVC
59
Добавление проверки достоверности
Теперь мы готовы добавить в приложение проверку достоверности вводимых дан­
ных. В отсутствие проверки достоверности пользователи смогут вводить бессмыслен­
ные данные или даже отправлять пустую форму. В приложении
MVC
проверка до­
стоверности обычно применяется к модели предметной области, а не производится
в пользовательском интерфейсе. Это значит. что проверка достоверности определя­
ется в одном месте, но оказывает воздействие в приложении везде, где используется
класс модели. Инфраструктура
MVC
поддерживает декларативные правил.а проверки
достоверности, определяемые с помощью атрибутов из пространства имен
CornponentModel. DataAnnotations,
ражаются посредством стандартных атрибутов С#. В листинге
применить такие атрибуты к классу модели
Листинг
из папки
using
2.19
показано, как
GuestResponse.
Применение проверки достоверности в файле
2.19.
Systern.
т.е. ограничения проверки достоверности вы­
GuestResponse. cs
Models
System.Componentмodel.DataAnnotations;
namespace Partylnvites.Models {
class GuestResponse {
[Required(ErrorMessage = "Please enter your name")]
11 Пожалуйста, введите свое имя
puЫic string Name { get; set; }
[Required (ErrorMessage = "Please enter your email address")]
[RegularExpression(".+\\@ .+\\ .. +",
ErrorMessage = "Please enter а valid email address")]
11 Пожалуйста, введите свой адрес электронной почты
puЫic string Email { get; set; }
[Required (ErrorMessage = "Please enter your phone numЬer")]
puЫic
//Пожалуйста,
введите свой номер телефона
string Phone { get; set; }
[Required (ErrorМessage = "Please specify whether you' 11 attend")]
puЫic
//Пожалуйста,
puЫic
укажите,
примете ли участие
bool? WillAttend { get; set; }
Инфраструктура МVС автоматически обнаруживает атрибуты проверки достовер­
ности и использует их для проверки данных во время процесса привязки модели. Мы
импортировали пространство имен, которое содержит атрибуты проверки достовер­
ности, так что к ним можно обращаться, не указывая полные имена.
Совет. Как отмечалось ранее, для свойства WillAttend был выбран булевский тип, до­
пускающий null. Мы поступили так для того, чтобы появилась возможность применять
атрибут проверки достоверности Required. Если бы использовался обычный булевский
тип, то значением, получаемым посредством привязки модели, могло быть только true
или false, и отсутствовала бы возможность определить, выбрал ли пользователь значе­
null, имеет три разрешенных значения: true, false
null. Браузер отправляет null, если пользователь не выбрал значение, и тогда атри­
бут Required сообщит об ошибке проверки достоверности. Это хороший пример того,
насколько элегантно инфраструктура MVC сочетает средства С# с HTML и НТТР.
ние. Булевский тип, допускающий
и
60
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
Проверку на наличие проблемы с достоверностью данных можно выполнить с
применением свойства
ModelState. IsValid
в классе контроллера. В листинге
показано, как она реализована в методе действия
просы
POST,
Листинг
внутри класса контроллера
2.20
().поддерживающем за­
Home.
Проверка на наличие ошибок проверки достоверности в файле
2.20.
HomeController. cs
using
using
using
using
RsvpForm
из папки
Controllers
System;
Microsoft.AspNetCore.Mvc;
Partyinvites.Models;
System.Linq;
namespace Partyinvites.Controllers
class HomeController : Controller
ViewResult Index() {
int hour = DateTime.Now.Hour;
ViewBag.Greeting = hour < 12 ? "Good Morning"
return View("MyView");
puЫic
puЬlic
"Good Afternoon";
[HttpGet]
ViewResul t RsvpForm {) {
return View ();
puЬlic
[HttpPost]
ViewResult RsvpForm{GuestResponse guestResponse)
puЫic
if (ModelState.IsValid) {
Repository.AddResponse(guestResponse);
return View("Thanks", guestResponse);
else {
11
Обнаружена ошибка провер1СИ достоверности.
return View () ;
ViewResult ListResponses() {
return View(Repository.Responses.Where(r => r.WillAttend == true) );
puЫic
Базовый класс
Controller
предоставляет свойство по имени
ModelState.
ко­
торое сообщает информацию о преобразовании данных НТГР-запроса в объекты С#.
Если свойство
ModelState. IsValid
возвращает
true.
то известно. что инфраструк­
тура МVС сумела удовлетворить ограничения проверки достоверности, указанные че­
рез атрибуты для класса
GuestResponse. Когда такое происходит, визуализируется
Thanks, как делалось до того.
Если свойство ModelState. IsValid возвращает false, то известно, что есть
ошибки проверки достоверности. Возвращаемый свойством ModelState объект пре­
представление
доставляет подробные сведения о каждой возникшей проблеме, но нам нет нужды
погружаться на такой уровень деталей. поскольку мы можем полагаться на удобное
средство, которое автоматизирует процесс указания пользователю на необходимость
решения любых проблем, вызывая метод
View ()
без параметров.
Глава 2. Ваше nервое nриложение MVC
Когда МVС визуализирует представление, механизм
Razor
61
имеет доступ к деталям
любых ошибок проверки достоверности, связанных с запросом, и вспомогательные
функции дескрипторов могут обращаться к этим деталям, чтобы отображать сообще­
ния об ошибках пользователю. В листинге
ставления
RsvpForm. csh tml
2.21
приведено содержимое файла пред­
с добавленными атрибутами вспомогательных функций
дескрипторов для проверки достоверности.
Листинг
2.21. Добавление сводки по проверке достоверности в файле
RsvpForm.cshtml из папки Views/Home
@model Partyinvites.Models.GuestResponse
@{
Layout = null;
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>RsvpForm</title>
</head>
<body>
<form asp-action="RsvpForm" method="post">
<div asp-validation-suпunary="All"X/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>
</р>
<button type="submit">Submit RSVP</button>
</form>
</body>
</html>
Атрибут
asp-validation-summary
применяется к элементу
div
и отобра­
жает список ошибок проверки достоверности при визуализации представления.
Значение для атрибута
asp-validation-summary
берется из перечисления по име-
62
ни
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Valida t i onSummary,
которое указывает типы ошибок проверки достоверности ,
помещаемые в сводку. Мы указали
All, что является хорошей отправной
27 будут описаны другие значения.
точкой для
большинства приложений, а в главе
Чтобы взглянуть, как работает сводка по проверке достоверности. запустим при­
ложение, заполним поле
сводка с ошибками (рис.
Name и
2.19).
отправим форму, не вводя другие данные. Появится
• Please enter your email address
• Please enter your phone number
• Please specify \\·hether you'll attend
-·--·--
Your name: Q
Joe
-
]
Your eniail: [ - -~--~
-J
- --- ----
Your phone:
f -
- -
-
-----J
j Submlt RSVP j
Рис.
Метод действия
2.19.
Отображение ошибок проверки достоверности
RsvpForm ()
не визуализирует представление
Th a nks
до тех пор,
пока не будут удовлетворены все ограничения проверки достоверности, примененные
к классу
Gue st Resp onse .
Обратите внимание . что введенные в поле
были сохранены и отображены снова при визуализации механизмом
Name
данные
Razor представ­
ления со сводкой проверки достоверности. Это еще одно преимущество привязки мо­
дели, и оно упрощает работу с данными формы.
На заметку! Если вы работали с
ASP.NET Web Forms , то
знаете, что в Web Forms поддержива­
ется концепция серверных элементов управления, которые сохраняют состояние, сериа­
лизируя значения в скрытое поле формы по имени_ VIEW STATE. Привязка модели
MVC
не имеет никакого отношения к концепциям серверных элементов управления, обратным
отправкам или состоянию представления , принятым в
Web Forms.
Инфраструктура
MVC
не внедряет скрытое поле_ VIEWSTATE в визуализированные НТМL-страницы. Взамен
она включает данные, устанавливая атрибуты
value
элементов управления
inpu t.
подсветка полей с недопустимыми значениями
Атрибуты вспомогательных функций дескрипторов, которые ассоциируют свойс­
тва модели с элементами, обладают удобным средством , которое можно использовать
Глава 2. Ваше первое приложен и е MVC
63
в сочетании с привязкой модели. Когда свой ство класса модели не проходит проверку
достоверности, атрибуты вспомогательных функций дескрипторов будут генерировать
несколько отличающуюся НТМL-разметку. Вот элемент
для поля
Phone
i n put,
ноторый генерируется
при отсутствии ошибок проверки достоверности:
<input type=" te xt" data- v a l =" tr ue"
data- v a l -r e quired= " Pl e a se e nt e r y o u r phone numb e r "
i d = " Pho ne " name = " Pho ne" va lu e = " " >
Для сравнения ниже показан тот же НТМL-элемент после то го, как пользователь
отправил форму , не введя данные в текстовое пол е (что является ошибной проверни
достоверности, поскольку мы применили к свойству
атрибут проверки
Ph o ne
класса
Gu e s tRe sp o n se
Required):
<i npu t t.yp e = " t e x t " class=" input-validation-error" da ta- va l= "true "
d at a - v al -r e q uir e d = "Ple a s e en ter y o ur p ho ne number" i.d= "Pho ne "
n a me= " Phon e " v a lue= "" >
Отличие выделено полужирным: атрибут вспомогательной функции дескриптора
a sp -for
добавил к элементу
input
класс по имени
i n pu t-validati on -e r ror.
Мы
можем воспользоваться такой возможностью, создав таблицу стилей , которая содер ­
жит стили
CSS для
этого класса и другие стили , применяемые различными атрибута­
ми вспомогательных функций дескрипторов.
По принято му в проектах
MVC
мое кли ентам. помещается в папку
согла шени ю статическое с одержи м ое, доставляе­
wwwroot ,
содержимого. так что таблицы стилей
JavaScript Для
ке
в папке
создания
wwwr o ot /c s s
wwwroot / j s
таблицы
в онне
CSS
подпапки которой организованы по типу
находятся в папке
www r oot / cs s ,
файлы
и т. д.
стилей
щелкнем
Solution Explorer
правой
кнопкой
мыши
на
пап ­
и выберем в нонтекстном меню пункт
Addc::>New ltem (Добавить q Н овый элемент). В открывш е мся диалогово м окне Add New
ltem (Добавле ние нового эл е м е нта) перейдем в раздел ASP.NET CoreqWebq Content
(ASP.NET Соrеq Веб q Содержимо е) и вы бер ем ш аблон Style Sheet (Таблица стилей) из
списка шаблонов (рис. 2.20).
Add Now ltem • Partylnvites
•
.
ASP.NEТ Соге
Code
General
• Web
ASP.NEТ
General
Script<
Cont•nt
~
?
•
Sortby:~----~
· I ~~: ::.=:
,; lnstalied
Г-..
c'.)J
HTML Page
ASP.NET Core
~
Style Sh.,.t
ASP.NET Core
~
LESS Style Sheet
ASP.NET Core
И:Ш~
SCSS Style Sheet (SASS)
ASP.NEТ Core
Search (Ctrl• E)
А
cascading style sheet (CSS) used for rich
style definitions
НТМL
styles.css
Рис.
2.20.
Создание таблицы стиле й
Р·
Туре: ASP.NEТ Core
Online
Nam~
Х
CSS
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
64
Совет. Когда для проекта используется шаблон
среда
Visual Studio
создает файл
Web
site.css
Applicatioп (Model-View-Coпtroller),
в папке
wwwroot/css.
В текущей главе
данный файл не задействован.
Назначим файлу имя
styles. css,
щелкнем на кнопке
Add
(Добавить), чтобы со­
здать файл таблицы стилей, и приведем его содержимое к виду, показанному в лис­
тинге
2.22.
Листинг
2.22.
Содержимое файла
s tyles. css
.field-validation-error
.field-validation-valid
.input-validation-error
.validation-suпunary-errors
.validation-suпunary-valid
из папки
wwwroot/ css
color: #fOO; )
display: none;
border: lpx solid #fOO;
background-color: #fee;
{ font-weight: bold; color: #fOO; )
{ display: none; )
Чтобы применить эту таблицу стилей. мы добавили элемент
представления
Листинг
2.23.
RsvpForm
(листинг
link
в раздел
head
2.23).
Применение таблицы стилей в файле
RsvpForm. cshtml
<head>
<meta name="viewport" content="width=device-wid th" />
<title>RsvpForm</title>
<link rel="stylesheet" href="/css/styles.css" />
</head>
Элемент
link
использует атрибут
href для указания местоположения таблицы
wwwroot в URL опущена. Стандартная кон­
стилей. Обратите внимание, что папка
фигурация
ASP.NET
включает поддержку обслуживания статического содержимого,
такого как изображения, таблицы стилей
и файлы
чески отображает запросы на папку
Процесс конфигурации
рассматривается в главе
CSS
wwwroot.
JavaScript,
которая автомати­
ASP.NET и MVC
14.
Совет. Для работы с таблицами стилей предусмотрена специальная вспомогательная функ­
ция дескриптора, которая может оказаться полезной при наличии многочисленных фай­
лов, требующих управления. Подробности ищите в главе
25.
Благодаря применению таблицы стилей в случае отправки данных. которые вызы­
вают ошибки проверки достоверности, отображается визуально более ясные сообще­
ния об ошибках (рис.
2.21).
Глава 2. Ваше первое приложе ние MVC
65
• Please enter your email address
• Please enter your pbone nuшber
• Please specif)· \Yhether you 'll attend
l
Your name: ~ое .
\\.ill you attend? 1Choose an option • ]
J
ГsUЬmit RSVP
Рис.
2.21.
Автоматическая подсветка полей с ошибками проверки достоверности
Стилизация содержимого
Все цели приложения, касающиеся функциональности, достигнуты , но его общий
вид оставляет желать лучшего. Когда вы создаете проект с использованием шаблона
Web Applicatioп (Model-View-Coпtroller). как в рассматриваемом примере. среда Visual
Studio устанавливает несколько распространенных пакетов для разработки на сто ·
роне клиента. Хотя я не являюсь сторонником применения шаблонов проектов. мне
нравятся выбранные
вается
Bootstrap
Microsoft
библиотеки клиентской стороны. Одна из них назы­
и представляет собой удобную инфраструктуру
CSS,
первоначально
разработанную в ТWitter, которая постепенно превратилась в крупный проект с от­
крытым кодом и стала главной опорой разработки веб-приложений.
Bootstrap 3, но версия
Microsoft могут принять решение обно­
Web Applicatioп, в последующих выпус­
На заметку! На момент написания книги текущей версией была
Bootstrap 4
находилась на стадии разработки. В
вить версию
шаблоном
ках
Bootstrap, используемую
Visual Studio, что может привести
к отображению содержимого по-другому. В других
главах книги это не должно стать проблемой, т.к. там будет показано, каким образом явно
указывать версию пакета, чтобы получить ожидаемые результаты.
Стилизация начального представления
Базовые средства
Bootst.rap
работают за счет применения классов к элементам ,
которые соответствуют селекторам
CSS,
определенным внутри добавленных в папку
wwwroot/liЬ/bootstrap файлов. Подробную информацию о классах. определенных
в библиотеке
в листинге
ставлении
Bootstrap, можно получить на веб-сайте http: //ge tboots t rap.c om, а
2.24 демонстрируется использование нескольких базовых стилей в пред­
MyVi e w. cshtml .
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
66
Листинг
Добавление классов
2.24.
из папки
Bootstrap
Views/Home
в файле МyView. cshtml
@{
Layout
nul l ;
=
< 1 DOCTYPE html>
<h tml >
<head>
<meta name= " view port " content="w id th=device -w idt h" />
<title >Index</t itle >
<link rel="stylesheet" href="/liЬ/bootstrap/dist/css/bootstrap.css"
/>
</ head>
<body >
<div class="text-center">
<hЗ>We're going to have an exciting party!</hЗ>
<h4>And you are invi ted</h4>
<а class="Ьtn Ьtn-primary" asp-action="RsvpForm">RSVP Now</a>
</d1v>
</body>
</html>
В разметку был добавлен элемент
bootstrap. css
ты
из папки
wwwroo t /
l ink.
атрибут
href
которого загружает файл
l iЬ/ bootstrap / d ist/css. По соглашению паке ­
CSS и JavaScгipt от независимых поставщиков устанавливаются
lib.
и в главе
6
в папку
www root /
будет описан инструмент, предназначенный для управления такими
пакетами.
После импортирования таблиц стилей
Bootstrap
осталось стилизовать элементы.
Рассматриваемый пример прост, поэтому требует использования лишь небольшого
числа классов
Класс
CSS
из
Bootstrap: text- cen ter,
text-center
Ьtn и Ьtn -prima ry .
центрирует содержимое элемента и его дочерних элементов.
Класс Ьtn стилизует элемент
butt on, input
или а в виде симпатичной кнопки. а
класс Ьtn-primary указывает диапазон цветов для кнопки. Запустив приложение.
можно увидеть результат. показанный на рис .
~
С
2.22.
<D localhost:S7628
We're going to have an exciting party!
And you are invited
1 ;Н11!!.\!Щ
Рис.
2.22.
Стилизация представления
Глава 2. Ваше первое приложение MVC
Совершенно очевидно, что я
-
67
не веб-дизайнер. На самом деле, будучи еще ребен­
ком, я был освобожден от уроков рисования по причине полного отсутствия таланта.
Это произвело благоприятный эффект в виде того, что я стал уделять больше време­
ни урокам математики, но вместе с тем мои художественные навыки не развивались
примерно с десятилетнего возраста. Для реальных проектов я обратился бы к помощи
профессионального дизайнера содержимого, но в настоящем примере я собираюсь де­
лать все самостоятельно и применять
Bootstrap
с максимально возможной сдержан­
ностью и согласованностью, на какую только способен.
Стилизация представления
В библиотеке
Bootstrap
RsvpForm
определены классы, которые могут использоваться для
стилизации форм. Я не планирую вдаваться в особые детали, но в листинге
2.25
по­
казано, как были применены эти классы.
Листинг
2.25.
Добавление классов
из папки
Bootstrap
Views/Home
в файле
RsvpForm. cshtml
@model Partyinvites.Models.GuestResponse
@{
Layout = null;
< 1 DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>RsvpForm</title>
<link rel="stylesheet" href=" /css/styles. css" />
<link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css"
/>
</head>
<body>
<div class="panel panel-success">
<div class="panel-heading text-center"><h4>RSVP</h4></div>
<div class="panel-body">
<form class="p-a-1" asp-action="RsvpForm" method="post">
<div asp-validation-summary="All"></div>
<div class="form-group">
<laЬel asp-for="Name">Your name:</laЬel>
<input class="form-control" asp-for="Name" />
</div>
<div class="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" aзp-for="WillAttend">
68
Часть 1. Введение в инфраструктуру ASP. NET Core MVC 2
<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">
<button class="Ьtn Ьtn-primary" type="suЬmit">
SuЬmit RSVP
</button>
</div>
</form>
</div>
</div>
</body>
</html>
Классы
Bootstrap
в данном примере создают заголовок, просто чтобы придать
компоновке структурированность. Для стилизации формы используется класс
group, который стилизует элемент, содержащий label и связанный
или select. Результаты стилизации можно видеть на рис. 2.23.
С 1 Ф loca~ho~:57628/Home/Rsvp_:o~m
RSVP
Yourname:
Youremail:
Yourphone:
Will you attend?
Choose an option
SuЬmitRSVP
Рис.
2.23.
Стилизация представления
RsvpForm
элемент
f orminpu t
Глава 2. Ваш е первое приложе н ие MVC
Стилизация представления
Thanks
Следующим стилизуемым представлением является
ге
2.26 показано,
69
как это делается с применением классов
Thanks. c shtml : в
CSS, подобных тем ,
листин ­
которые
ис пользовались для других представлений. Чтобы облегчить управление приложени­
ем, имеет смысл везде, где только возм ожно, избегать дублирования кода и разметки .
Инфраструктура
MVC
предлагает несколько средств, помогающих сократить дубли­
рование, которые рассматрива ются в последующих гл авах. К таким средствам отно­
сятся компоновки
Razor (глава 5).
22).
частичные представления (глава
21)
и компоненты
представлений (глава
Листинг
2.26.
Применение классов
из папки
Bootstrap
Views/Home
в файле
Thanks. cshtml
@model Par t y i nv it es .Mode l s . Gues t Re sp onse
@{
Layou t = nul l ;
< ! DOCTYPE h tml>
<html>
<head>
<meta name=" vi ewport" con te nt ="w idth=device - width" />
<ti t l e>T han ks< / t itl e>
<link rel="stylesheet" href="/lib/bootstrap/dist/css/Ьootstrap . css" />
</head>
<body class="text-center">
< р>
<hl>Tha n k you , @Mode l. Na me ! </ hl>
@if (Mode l . WillAttend == t rue ) {
@:It's g r e a t t hat you ' re coming. The dri nks are a lready in the f r i dge!
e l se {
@: Sorry t o he a r that you can 't ma ke i t , but thanks for lett i ng us know.
< / р>
Click <а class="nav-link" asp-action="ListResponses">here</a>
to see who is coming.
</ bod y>
</htпil >
Н а ри с .
2.24
пока зан результат стил изации.
Thanks
С
l
х
!ф
localhost.57628/Ho~/RsvpFom1
Thank you, Joe!
lt's great that you're coming. The drinks are alreacly in the fridge!
Click here to see who is coming.
Рис.
2.24.
Стилизация представления
Tha n ks
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
70
Стилизация представления
Lis tResponses
Последним мы стилизуем представление
ListResponses.
которое отображает
список участников вечеринки. Стилизация содержимого следует тому же базовому
подходу, который применялся в отношении всех стилей Bootstгap (листинг
2.27).
Листинг 2.27. Добавление классов Bootstrap в файле ListResponses. cshtml
из папки
@model
Views/Home
IEnumeraЫe<Partyinvites.Models.GuestResponse>
@{
Layout = null;
< 1 DOCTYPE
htшl>
<htшl>
<head>
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" />
<title>Responses</title>
</head>
<body>
<div class="panel-body">
<h2>Here is the list of people attending the party</h2>
<tаЫе class="taЫe taЫe-sm taЫe-striped taЫe-bordered">
<thead>
<tr>
<th>Name</th>
<th>Eшail</th>
<th>Phone</th>
</tr>
</thead>
<tbody>
@foreach (Partyinvites.Models.GuestResponse r in Model) {
<tr>
<td>@r.Naшe</td>
<td>@r.Eшail</td>
<td>@r.Phone</td>
</tr>
</tbody>
</tаЫе>
</div>
</body>
</htшl>
На рис.
2.25
показано, как теперь выглядит таблица участников вечеринки.
Добавление стилей к данному представлению завершает пример приложения, кото­
рое теперь достигло всех целей разработки и имеет намного более совершенный вне­
шний вид.
Глава 2. Ваше первое приложение MVC
(-
С
71
,-ф localhost57628/Home/l1stResponses
Here is the list of people attending the party
Name
Email
Phone
Joe
joe@example.com
555-1234
Alice
alice@example.com
555-5678
ВоЬ
ЬoЬ@example.com
255-2345
Рис.
2.25.
Стилизация представления
L i stResponses
Резюме
MVC, который использовался для построения
MVC, что позволило получить первое представле­
ние об архитектуре ASP.NET Core MVC и применяемом подходе. Некоторые основные
средства (включая синтаксис Razor. маршрутизацию и тестирование) не рассматри­
В главе был создан новый проект
простого приложения ввода данных
вались , но мы вернемся к данным темам в последующих главах . В следующей главе
MVC , которые
ASP.NET Core MVC.
будет описан ы паттерны проектирования
тивной р азработки с помощью
формируют основу эффек­
ГЛАВА
3
Паттерн, проекты
и соглашения МVС
режде чем приступать к углубленному изучению деталей инфраструктуры
п ASP.NET
Core MVC,
необходимо освоить паттерн проектирования МVС. лежа­
щие в его основе концепции и способ, которым они транслируются в проекты ASP.NEТ
Core MVC.
Возможно, вы уже знакомы с некоторыми идеями и соглашениями, обсуж­
даемыми в настоящей главе, особенно если вам приходилось заниматься разработкой
сложных приложений
ASP.NET
или С#. Если это не так, тогда внимательно читайте
главу. Хорошее понимание того, что положено в основу
MVC,
может помочь увязать
функциональные возможности инфраструктуры с контекстом материала, излагаемо­
го в оставшихся главах книги.
История создания
MVC
Термин модель-представление-контроллер
(model-view-controller - MVC) был в
Smalltalk в Xerox PARC.
употреблении с конца 1970-х годов и происходит из проекта
где он был задуман как способ организации ряда ранних приложений с графичес­
ким пользовательским интерфейсом. Некоторые нюансы первоначального паттерна
MVC
были связаны с концепциями, специфичными для
Smalltalk,
такими как экраны
и инструменты, но более широкие понятия по-прежнему применимы к приложени­
ям
-
и особенно хорошо они подходят для веб-приложений.
Особенности паперна
MVC
Если оперировать высокоуровневыми понятиями. то паттерн МVС означает, что
приложение
•
MVC
Модели,
будет разделено. по крайней мере, на три части.
содержащие или представляющие данные, с которыми работают
пользователи.
•
Представления. используемые для визуализации некоторой части модели в виде
пользовательского интерфейса.
•
Контроллеры, которые обрабатывают входящие запросы. выполняют операции
с моделью и выбирают представления для визуализации пользователю.
Глава 3. Паттерн, проекты и соглашения MVC
73
Каждая порция архитектуры МVС четко определена и самодостаточна: такое по­
ложение вещей называют разделением обязанностей. Логика, которая манипулиру­
ет данными в модели, содержится только в модели. Логика, отображающая данные,
присутствует только в представлении. Код, который обрабатывает пользовательские
запросы и ввод, находится только в контроллере. Благодаря ясному разделению меж­
ду частями приложение будет легче сопровождать и расширять на протяжении его
времени существования вне зависимости от того, насколько большим оно станет.
Понятие моделей
Модели
(М в
MVC)
содержат данные,
с
которыми работают пользователи.
Существуют два обширных типа моделей: модели представлений, которые выража­
ют сами данные, передаваемые из контроллера в представление, и модели предмет­
ной области, которые содержат данные в предметной области наряду с операциями,
трансформациями и правилами для создания, хранения и манипулирования данны­
ми, вместе называемыми логикой моделей.
Модели
-
это определение "вселенной", в которой функционирует приложение.
Например, в банковском приложении модель представляет все аспекты банковской
деятельности,
поддерживаемые приложением, такие как расчетные счета, главная
бухгалтерская книга и кредитные лимиты для клиентов, равно как и операции, ко­
торые могут применяться для манипулирования данными в модели, подобные вне­
сению денежных средств и списанию их со счетов. Модель отвечает также за сохра­
нение общего состояния и целостности данных
-
например, удостоверяясь, что все
транзакции внесены в главную книгу, а клиент не снимает со счета больше денежных
средств, чем имеет на то право, или больше, чем находится в распоряжении самого
банка.
Для каждого компонента в паттерне
MVC
будет описано, что он должен включать,
а что не должен.
Модель в приложении, построенном с использованием паттерна МVС, должна:
•
содержать данные предметной области:
•
содержать логику для создания, управления и модификации данных предмет­
ной области:
•
предоставлять чистый АРI-интерфейс, который открывает доступ к данным мо­
дели и операциям с ними.
Модель не должна:
•
показывать детали того,
как осуществляется получение или управление дан­
ными модели (другими словами, подробности механизма хранения данных не
должны быть видны контроллерам и представлениям);
•
содержать логику, которая трансформирует модель на основе взаимодействия с
пользователем (поскольку это работа контроллера):
•
содержать логику для отображения данных пользователю (т.к. это работа
представления).
Преимущества обеспечения изоляции модели от контроллера и представлений за­
ключаются в том, что вы можете гораздо легче тестировать логику (модульное тести­
рование описано в главе
7)
и проще расширять и сопровождать приложение в целом.
74
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Совет. Многих разработчиков, только приступивших к ознакомлению с паттерном
MVC, при­
водит в замешательство идея помещения логики в модель данных из-за их уверенности
в том, что целью паттерна
цель паперна
MVC -
MVC
является отделение данных от логики. Это заблуждение:
разделение приложения на три функциональных области, каждая
из которых может содержать и логику, и данные. Цель не в том, чтобы устранить логику
из модели. Наоборот, цель в том, чтобы гарантировать наличие в модели только логики,
предназначенной для создания и управления данными модели.
Понятие контроллеров
Контроллеры являются "соединительной тканью" паттерна
MVC,
исполняя роль
каналов между моделью данных и представлениями. Контроллеры определяют дейс­
твия, предоставляющие бизнес-логику, которая оперирует на модели данных и обес­
печивает представления данными, подлежащими отображению для пользователя.
Контроллер, построенный с применением паттерна МVС, дол:ж:ен:
•
содержать действия, требующиеся для обновления модели на основе взаимо­
действия с пользователем.
Контроллер не должен:
•
содержать логику, которая управляет внешним видом данных (это работа
представления);
•
содержать логику, которая управляет постоянством данных (это работа модели).
Понятие представлений
Представления содержат логику. которая требуется для отображения данных поль­
зователю или для сбора данных от пользователя, так что они могут быть обработаны
каким-то действием контроллера. Представления дол:ж:ны:
•
содержать логику и разметку. необходимые для показа данных пользователю.
Представления не должны:
•
•
содержать сложную логику (ее лучше поместить в контроллер);
содержать логику, которая создает, сохраняет или манипулирует моделью пред­
метной области.
Представления могут содержать логику, но она должна быть простой и использо­
ваться умеренно. Помещение в представление чего угодно кроме вызовов простейших
методов или несложных выражений затрудняет тестирование и сопровождение при­
ложения в целом.
Реализация паттерна
MVC
в
ASP.NET Core
ASP.NET Core MVC адаптиру­
ASP.NET и С#. В инфраструктуре
Как подразумевает само название, реализация
ет абстрактный паттерн
ASP.NET Core MVC
MVC
к миру разработки
контроллеры
-
это классы С#, обычно производные от класса
Microsoft. AspNetCore. Mvc. Con trol ler. Каждый открытый метод в производном
от Controller классе является методом действия, который ассоциирован с каким­
то
URL.
Когда запрос отправляется по
URL,
связанному с методом действия, опера­
торы в данном методе действия выполняются, чтобы провести некоторую операцию
над моделью предметной области и затем выбрать представление для отображения
клиенту. Взаимодействия между контроллером, моделью и представлением проил­
люстрированы на рис.
3.1.
Глава 3. Паттерн , проекты и соглашения MVC
Запрос
НТТР
_ ._ Постоянство
- - - - -- - -- - ---1..i
Контроллер
....__ _ _ ___.
Модель
представления
Рис.
В инфраструктуре
вестный как
3.1.
(обычно с помощью
Модель
Представление ~-----1
Ответ
75
реляционной
- -
базы данных)
.___ _ __,
Взаимодействия в приложении
ASP.NET Core MVC
MVC
применяется механизм визуал изации, из ­
который является компонентом, ответственным за обработку
Razor,
представления для генерации ответа браузеру. Представления
Razor -
это НТМL­
шаблоны , содержащие логику С# , которая используется для обработки данных мо ­
дели с целью генерации динамического содержимого , реагирующего на изменения в
Razor рассматрив ается в главе 5.
ASP.NET MVC не налагает никаких огра ничений
модели. Работа механизма
Инфра структура
на реализацию
модели предметной области. Вы можете создать модель с применением обычных
объектов С# и реализовать постоянство с использованием любых баз данных, инф­
раструктур объектно-реляционного отображения или других инструментов работы с
данными. поддерживаемых в
.NET.
Понятие одностраничных приложений
Исторически сложилось так, что при разработке веб-приложений браузеры обычно тракто­
вались как простое устройство отображения для визуализации НТМL-разметки и реагирова ­
ния на щелчки кнопками мыши. Такой стиль веб-приложений называется полным циклом об­
мена. Каждый раз , когда пользователь щелкает на ссылке , приложению
ASP.NET Core MVC
посылается НТТР-запрос. Внутри приложения контроллер выбирает представление, которое
визуализируется механизмом Razor и отправляется обратно браузеру, так что пользователю
может быть отображена новая НТМL-страница . Вся логика , данные и состояние находятся
Core MVC, что упрощает разработку и означает отсутствие необходи­
на сервере ASP.NEТ
мости уделять много внимания браузеру, кроме как проверять, поддерживает ли он возмож­
ности
HTML,
реализованные в представлениях Razoг.
По контрасту с ними одностраничные приложения включают браузер в платформу прило­
жений. Сервер несет ответственность за управление данными приложения , в то время как
код
JavaScript, выполняющийся в браузере, запрашивает эти данные, отображает их поль­
зователю и реагирует на пользовательское взаимодействие . В одностраничном приложе­
нии обязанности, касающиеся моделей, представлений и контроллеров, разделяются меж­
ду браузером и сервером. Вместо отправки полных НТМL-страниц браузеру часть ASP.NET
Core MVC приложения обеспечивает доступ к данным приложения, которые запрашиваются
и отображаются инфраструктурой
JavaScript, такой как Angular или React.
Одностраничные приложения могут быть более отзывчивыми, чем приложения с полным
циклом обмена, но их сложнее создавать, а эффективная разработка таких приложений
JavaScript, сложность которых не должна недо­
20 будет показано , как можно использовать инфраструктуру ASP.NET
требует навыков работы с языками С# и
оцениваться . В главе
Core MVC
для предоставления данных в одностраничном приложении, но процесс разра­
ботки приложений подобного рода не демонстрируется, поскольку он сам по себе является
JavaScript я отдаю предпочтение Angular, которая
Pro Angular. При желании задействовать Angular с ASP.NET Core MVC
Essentia/ Angular for ASP.NET Саге MVC.
отдельной темой. Среди инфраструктур
описана в моей книге
почитайте мою книгу
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
76
Сравнение
MVC
Разумеется,
с другими паттернами
MVC -
далеко не единственный архитектурный паттерн программно­
го обеспечения. Есть много других паттернов. и некоторые из них чрезвычайно по­
пулярны или, по крайней мере , были таковыми. О паттерне
MVC
можно узнать много
интересного, сравнивая его с альтернативами. В последующих разделах кратко опи­
саны разные подходы к структурированию приложения и приведено их сравнение с
MVC.
Одни паттерны будут близкими вариациями на тему
MVC,
тогда как другие со­
вершенно отличаться.
Я не утверждаю. что
MVC -
идеальный паттерн во всех ситуациях . Я сторонник
выбора такого подхода, который лучше всего решает имеющуюся задачу . Как вы вско­
ре увидите , есть ситуации, когда некоторые конкурирующие паттерны в равной сте­
пени полезны или лучше
MVC.
Я призываю делать осознанный и взвешенный выбор
паттерна. Сам факт чтения вами этой книги наводит на мысль, что в определенной
мере вы уже склонны применять паттерн
MVC.
но всегда полезно сохранять макси­
мально широкую точку зрения .
Паттерн "Интеллектуальный пользовательский интерфейс"
Один из наиболее распространенных паттернов проектирования известен как
"Интеллектуальный пользовательский интерфейс"
(Smart UI).
Большинству про­
граммистов приходилось создавать приложение с интеллектуальным пользователь­
ским интерфейсом на том или ином этапе своей профессиональной деятельности
меня это определенно касается. Если вы использовали
Web Forms,
Windows Forms
или
ASP.NET
то сказанное относится и к вам.
При построении приложения с интеллектуальным пользовательским интерфей­
сом разработчики конструируют интерфейс, часто перетаскивая набор компонентов
либо элементов управления на поверхность проектирования или холст. Элементы
управления сообщают о взаимодействии с пользователем, инициируя события для
щелчков кнопками мыши, нажатий клавиш на клавиатуре, перемещений курсора
и т.д. Разработчик добавляет код реакции на такие события в набор обработчиков
событий, которые являются небольшими блоками кода. вызываемыми при выдаче
специфического события в определенном компоненте. В конечном итоге получается
монолитное приложение, показанное на рис .
Запрос
__..,
Интеллектуальный
3.2.
__ _.
пользовательский
Отв ет
Рис.
3.2.
интерфейс
Постоянство
(обычно с помощью
реляционной
- -
базы данных)
Паттерн "Интеллектуальный пользовательский интерфейс"
Код, который поддерживает пользовательский интерфейс и реализует бизнес-ло­
гику, перемешан между собой без какого-либо разделения обязанностей. Код. кото­
рый определяет приемлемые значения для вводимых данных и запрашивает данные
или модифицирует, например, расчетный счет пользователя, оказывается размещен­
ным в небольших фрагментах. связанных друг с другом в предполагаемом порядке
поступления событий.
Глава 3. Паттерн, проекты и соглашения MVC
77
Интеллектуальные пользовательские интерфейсы идеальны для простых проектов,
т.к. позволяют добиться неплохих результатов достаточно быстро (по сравнению с раз­
работкой МVС, которая. как показано в главе
8, требует определенных начальных затрат,
прежде чем будут доставлены результаты). Интеллектуальные пользовательские интер­
фейсы также подходят для построения прототипов пользовательских интерфейсов. Их
инструменты визуального конструирования могут оказаться по-кастоящему удобными.
и если вы обсуждаете с заказчиком требования к внешнему виду и потоку пользователь­
ского интерфейса, то инструмент интеллектуального пользовательского интерфейса мо­
жет быть быстрым и удобным способом для генерации и проверки различных идей.
Самый крупный недостаток интеллектуальных пользовательских интерфейсов
связан с тем, что их трудно сопровождать и расширять. Смешивание кода модели
предметной области и бизнес-логики с кодом пользовательского интерфейса приво­
дит к дублированию кода, когда один и тот же фрагмент бизнес-логики копируется
и вставляется для поддержки вновь добавленного компонента. Нахождение всех дуб­
лированных фрагментов и применение к ним исправления может быть непростой за­
дачей. Добавление новой функции может оказаться практически невозможным без
нарушения работы каких-то существующих функций. Тестирование приложения с
интеллектуальным пользовательским интерфейсом также может быть затруднено.
Единственный способ тестирования
-
эмуляция взаимодействия с пользователем,
что является далеким от идеала и трудно реализуемым фундаментом для обеспечения
полного покрытия тестами.
В мире
MVC
интеллектуальный пользовательский интерфейс часто называют
актuпаттерком, т.е. чем-то таким. чего следует избегать любой ценой. Такая ан­
типатия возникает (по крайней мере, частично) оттого, что к инфраструктуре
MVC
обращаются в поисках альтернативы после множества не слишком успешных попы­
ток разработки и сопровождения приложений с интеллектуальным пользовательским
интерфейсом, которые попросту вышли из-под контроля.
Тем не менее, полный отказ от паттерна "Интеллектуальный пользовательский
интерфейс" будет заблуждением. Далеко не все в нем настолько плохо, и с данным
подходом связаны также положительные аспекты. Приложения с интеллектуальным
пользовательским интерфейсом быстро и легко разрабатывать. Производители ком­
понентов и инструментов проектирования приложили немало усилий, чтобы сделать
процесс разработки приятным занятием, поэтому даже совершенно неопытный про­
граммист за считанные часы может получить профессионально выглядящий и доста­
точно функциональный результат.
Наибольшая слабость приложений с интеллектуальным пользовательским интер­
фейсом
-
низкое удобство сопровождения
-
не проявляется при мелких объемах
разработки. Если вы создаете несложный инструмент для небольшой аудитории, то
приложение с интеллектуальным пользовательским интерфейсом может оказаться
идеальным решением. Просто отсутствует дополнительная сложность, присущая раз­
работке приложения
MVC.
Архитектура "модель -представление"
В приложении с интеллектуальным пользовательским интерфейсом проблемы сопро­
вождения обычно возникают в области бизнес-логики, которая настолько рассеяна по
приложению, что внесение изменений или добавление новых функций становится му­
чительным процессом. Улучшить ситуацию помогает архитектура "модель-представ­
лекuе", которая выносит бизнес-логику в отдельную модель предметной области. Все
данные, процессы и правила концентрируются в одной части приложения (рис.
3.3).
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
78
Запрос
_ ._
Пользовательский -----~
Модель
интерфейс
(представление)
Ответ
Рис.
3.3.
--
Постоянство
(обычно с помощью
реляционной
базы данных)
Архитектура "модель-представление"
Архитектура "модель-представление" может быть значительным усовершенствова­
нием по сравнению с монолитным интеллектуальным пользовательским интерфейсом
(скажем. ее гораздо легче сопровождать). но возникают две проблемы . Первая: пос­
кольку пользовательский интерфейс и модель предметной области тесно переплете­
ны, модульное тестирование любой из упомянутых частей может оказаться затрудни­
тельным. Вторая проблема проистекает из практики. а не из определения паттерна .
Обычно модель содержит большой объем кода доступа к данным (не обязательно. но в
большинстве случаев). а это означает, что модель данных содержит не только бизнес­
данные. операции и правила.
Классическая трехуровневая архитектура
Чтобы решить проблемы . присущие архитектуре "м одель-представле ние", трех­
звенная или
трехуровневая архитектура отделяет код реализации постоянства от
модели предметной области и помещает его в новый компонент. который называется
уровнем доступа к данным
на рис.
(data access
Iауег
- DAL).
Сказанное проиллюстрировано
3.4.
Постоянство
Запрос
Пользовательский 1-----~
Модель
интерфейс
Ответ
(представление)
Рис.
3.4.
-
Уровень
-•(обычно
доступа
с помощью
к данным
реляционной
- - базы данных)
Трехуровневая архитектура
Трехуровневая архитектура является наиболее широко используемым паттерном в
бизнес-приложениях. Она не устанавливает никаких ограничений на с пособ реализа ­
ции пользовательского интерфейса и обеспечивает хорошее разделение обязанностей .
не будучи излишне сложной. Кроме того. ценой некоторых усилий уровень
DAL
мо­
жет быть создан так. чтобы модульное тестирование проводилось относительно легко .
Между классическим трехуровневым приложением и паттерном
MVC
можно заметить
очевидные сходства. Разница в том. что когда уровень пользовательского интерфей­
са напрямую связан с инфраструктурой графического пользовательского интерфейса
типа "щелчок-событие" (такой как
Windows
Foгms или
ASP.NET Web
Foгms) , то выпол­
нение автоматизированных модульных тестов становится практически невозможным .
А поскольку часть пользовательского интерфейса трехуровневого приложения может
быть сложной, останется много кода . не подвергавшегося серьезному тестированию.
В худшем сценарии отсутствие принудительных ограничений , накладываемых
трехуровневой архитектурой на уровень пользовательского интерфейса. означает.
что многие такие приложения в итоге оказываются тонко замаскиров анными при -
Глава 3. Паперн, проекты и соглашения MVC
79
ложениями с интеллектуальным пользовательским интерфейсом без какого-либо ре­
ального разделения обязанностей. Это ведет к наихудшему конечному результату: не
поддерживающему тестирование и трудному в сопровождении приложению. которое
к тому же чрезмерно сложно.
Разновидности
MVC
Основные принципы проектирования приложений
MVC
были уже описаны, осо­
бенно те из них. которые применимы к реализации ASP.NEТ
лизации интерпретируют аспекты паттерна
MVC
Core MVC.
Другие реа­
иначе. дополняя, подстраивая или
как-то еще адаптируя его для соответствия области охвата и целям своих проектов.
В последующих разделах мы кратко рассмотрим две наиболее распространенные ва­
риации на тему
MVC. Понимание этих разновидностей не
ASP.NET Core МVС; данная информация
в случае работы с
имеет особого значения
включена ради полноты
картины, потому что такие термины будут употребляться в большинстве обсуждений
паттернов проектирования ПО.
Паттерн "модель-представление-презентатор"
Паттерн "модель-представление-презентатор"
ляется разновидностью
MVC
(model-view-presenter - MVP)
яв­
и разработан для того. чтобы облегчить согласование с
поддерживающими состояние платформами графического пользовательского интер­
фейса. такими как
Windows Forms
или
ASP.NET Web Forms.
Это достойная попытка из­
влечь лучшее из паттерна интеллектуального пользовательского интерфейса, избежав
проблем, которые он обычно привносит.
В паттерне МVР презентатор имеет те же обязанности, что и контроллер МVС. но
он также более непосредственно связан с представлением, сохраняющим информа­
цию о состоянии, напрямую управляя значениями, которые отображаются в компо­
нентах пользовательского интерфейса в соответствии с вводом и действиями пользо­
вателя. Существуют две реализации паттерна
•
MVP.
Реализация пассивного представления. в которой представление не содер­
жит никакой логики. Такое представление служит контейнером для элементов
управления пользовательского интерфейса, которыми напрямую управляет
презентатор.
•
Реализация координирующего контроллера, в которой представление может от­
вечать за определенные элементы логики презентации. такие как привязка дан­
ных. и получать ссылку на источник данных от моделей предметной области.
Отличие между указанными двумя реализациями касается уровня интеллекту­
альности представления. В любом случае презентатор отделен от инфраструктуры
графического пользовательского интерфейса, что делает логику презентатора более
простой и подходящей для модульного тестирования.
Паттерн "модель-представление-модель представления"
Паттерн "модель-представление-модель представления"
(model-view-view model Microsoft разновидность МVС, которая используется
в инфраструктуре Windows Presentation Foundation (WPF). В рамках паттерна MWM
модели и представления играют те же самые роли, что и в MVC. Разница связана
с присутствующей в MWM концепцией модели представления. которая является
МVVM)
-
недавно выпущенная
абстрактным обозначением пользовательского интерфейса. Как правило. модель
80
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
представления
-
это класс С#, который открывает доступ к свойствам для данных.
подлежащих отображению в пользовательском интерфейсе. и операциям с данны­
ми. инициируемым из пользовательского интерфейса. В отличие от контроллера
MVC
модель представления
MWM
не имеет ни малейшего понятия о существова­
нии представления (или любой конкретной технологии пользовательских интерфей­
сов). Представление
MWM
применяет средство привязки
WPF.
чтобы установить
двунаправленное соединение между свойствами, доступ к которым открывают эле­
менты управления в представлении (вроде пунктов раскрывающегося меню или
эффекта от щелчка на кнопке), и свойствами, доступ к которым открывает модель
представления.
На заметку! В паттерне
MVC
также используется термин модель представления, но он отно­
сится к простому классу модели, предназначенному только для передачи данных из конт­
роллера в представление, как противоположность моделям предметной области, которые
являются сложными представлениями данных, операций и правил.
Проекты
ASP.NET Core MVC
При создании нового проекта
ASP.NET Core MVC
среда
Visual Studio
предлагает
на выбор несколько вариантов начального содержимого, которое желательно иметь в
проекте. Идея заключается в том, чтобы облегчить разработчикам-новичкам процесс
обучения и применить сберегающий время передовой опыт при описании распростра­
ненных средств и задач. Я не поклонник такого подхода в отношении заготовленных
проектов или кода. Намерения обычно хороши, но исполнение никогда не приводит
в восторг. Одной из характеристик, которая мне больше всего нравится в
и
MVC,
ASP.NET
является высочайшая гибкость в подгонке платформы под мой стиль разра­
ботки. Процессы, классы и представления, которые создает и наполняет среда
Studlo,
Visual
вызывают у меня чувство. что я вынужден работать в стиле кого-то другого.
К тому же я нахожу содержимое и конфигурацию слишком обобщенными и прими­
тивными. чтобы приносить хоть какую-нибудь пользу. Разработчики в
Microsoft
не
могут знать, какой вид приложения необходим, поэтому они охватывают все основы.
но настолько обобщенным путем. что в итоге я просто избавляюсь от всего стандар­
тного содержимого.
Моя рекомендация (всем, кто по недоразумению спросил)
-
начинать с пустого
проекта и добавлять нужные папки, файлы и пакеты. Вы не только узнаете больше о
способе работы МVС, но и будете обладать полным контролем над тем. что содержит
ваше приложение.
Но мои предпочтения не должны задавать тон вашей практике разработки. Вы мо­
жете счесть шаблоны более удобными, нежели я, особенно если являетесь новичком в
разработке с помощью
ASP.NET и
еще не выработали стиль, который устраивает лич­
но вас. Вы можете также признавать шаблоны проектов полезным ресурсом и источ­
ником идей, хотя должны проявлять осмотрительность и не добавлять функциональ­
ность к приложению до того, как полностью поймете. каким образом она работает.
Создание проекта
При создании нового проекта
ASP.NET Core
предлагается выбрать вариант из це­
лого набора отправных точек, как показано на рис.
3.5.
Глава 3. Паттерн , проекты и соглашения MVC
.NEТCore
у
~
~
Empty
w.ьдРI
ASP.NEТ
1
•
Core 2.0
w.ь
iJ
Application
81
ill.rn.!!!ш
А
projort template for creating an ASP.NEТ Core
application with example ASP.NEТ Core MVC Vitws and
Controllus. This t•mpllte con also Ье used for RESTTul
о
НTTPsuvi<os.
Angular
•• . •
"
6ЭJ
R•oct.js
Re•ctjs 11nd
R.dux
1
1Chonge Authenticotion j
Authentication No AuthentiatIOn
О ЕnаЫе Docker Support
05'
Nndaws
R.equirn Qo<tff forWindows
Docker support оп also Ье enaЬl•d latu ~
~1
Рис.
Шаблон проекта
Empty
3.5.
Шаблоны проектов
Cancel
ASP.NET Core
(Пустой) содержит связующие механизмы для инфраструк­
туры
ASP.NET Core, но не включает библиотеки или конфигурацию. требующиеся
приложению MVC. В состав шаблона проекта Web API входят инфраструктуры ASP.
NET Соге и MVC, а также пример приложения, который демонстрирует получение и
обработку запросов данных от клиентов с использованием контроллера АР!. который
будет описан в главе
Шаблон проекта
20.
Web Application (Model-View-Controller)
представление-контроллер)) содержит инфраструктуры
(Веб-приложение (модель­
ASP.NET
Соге и
MVC
с приме­
ром приложения, иллюстрирующим генерацию НТМL-содержимого. Шаблоны
и
Web Application (Model-View-Controller)
Web API
могут конфигурироваться с различными схе­
мами для аутентификации пользователей и авторизации их доступа к приложению .
Остальные шаблоны предоставляют начальное содержимое, подходящее для ра ­
боты с инфраструктурами одностраничных приложений (Aпgular и
структурой
Razor Pages
React)
и с инфра­
(которая разрешает смешивать в одном файле код и размет­
ку. объединяя роли контроллеров и представлений, и ради простоты отказывается от
ряда преимуществ модели
MVC).
Шаблоны проектов могут производить впечатление. что для создания определен­
ного вида приложения ASP.NEТ вы обязаны следовать специфическому пути. но дело
обстоит вовсе не так. Шаблоны
-
это просто разные отправные точки для получе­
ния той же самой функциональности, и вы можете добавлять в проекты. созданные с
помощью любого шаблона. любую желаемую функциональность. Скажем. в главе
объясняется. каким образом работать с НТТР-запросами данных, а в главах
20
28-30 -
как поступать с аутентификацией и авторизацией. причем все примеры будут начи­
наться с шаблона проекта
Empty.
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
82
Реальная разница между шаблонами проектов связана с начальным набором библи­
отек. конфигурационных файлов, кода и содержимого. который добавляет среда
Studio
Visual
{Empty) и
при создании проекта. Существует много отличий между простейшим
{Web Application (Model-View-Controller)) проектами, как можно видеть
Solution Explorer после создания проектов с примене­
нием каждого шаблона . Для случая с шаблоном Web Application (Model-View-Controller)
пришлось предоставить снимки окна Solution Explorer с разными открытыми папками,
самым сложным
на рис.
3.6,
где приведено окно
потому что полный список файлов не уместился бы на печатной странице.
SolWon~tr
Solutio11&pl0ttr
~ы- ' Э·
,"
5NrthSc1utionExplo•t1(Ct1l•_;J
~ &.· 1'ф · ~~ "'il> I '""'ю.""'~"
~ ·119 ·'<"
·
Sol11tionЬpiortf
' ~
5"r<h Soluuon &;p/oftr (Ctli•;}
1"""1
·1
'э·'<"
Stal(hSoiutionE>.plortr{Ctti•;)
-
f'
~ Solution'WtЬдppkatiol'ISecurIO'(~~~!!!l·••8'~m;!!!!•-.•=·······
·
~Conм~S.М.:es
Account
W...ьA\)plt~tIOIOecur~
(t>Cnnnкttd~f!'I
fl
.~/ Dtpendtiкits
t
J-P1optltiei
1>
.-;:
~ptnO.n<iп
"
;
Propcrtia
1>
8-root
fl
С'1
Prog.ram..::s
fl
С•
Shrtup.U
eJ
1>
1>
1::,1
8
0
0
8 , 1,
rnages
fO
&Ь
. controRм
1>
Со-
AccountConttolltf.CS
fl
cw
НomtCQnt1on1r.o
1>
"
J>
О>
с-
"'
Stivica
1Em1ilStndtr.c1
с-
t• ISm1Stndtr.t1
J>
• с• Мfl119tStМcн.cs
8 Vitllf1;
J>
Ю' ~pstttln9JJ5on
•и-;...
а ЬundlкonftgJюn
Pn:i91tm.n
t• JndoVlewModd.n
Со ~n1~in1V._мod
С" Rtm-toginV-..мodt
AЬoutдhtml
M1n1ge
.iCfl.ltfY"Ylitdltion· unoЬtruЖt
li) flVkon.ko
J>
с• Sf'IP11swordVitwМ~.._---.---------'
J>
с• VeiifyPhontNumberVitwМodel.cs
•
С" дpplk•tionUstr,CJ
J>
С" [tf'OiYitwМodd..ct
е AddPhoneNumЬtr.cstrt·-1---~------~
~ CtwngeP1ssword.ciht.ml
l]!ndc.цifltml
@! Mtl\lgtlo9int.tshtml
@!SttPщwotcl.cU!1ml
~~~.t$hlrnl
Shtr1d
Q ).qout.ahtml
0
0
8
C- St•lt\lp.Ct
J>
S9nfd0ut.tU>tml
н.~
@]Conttct.csntml
@] lndtushtml
•
J>
b1Юht1'1p
llll jquciy
Jqu1ty-v.itd1t1on
@llJuilyCod•.c.ntmt
0
Applk11ionDbC011tcct
ApplicttionDbConltld:.t$
......
С-
0
M1gг.ttions
с• OOOOOCIOOOOOOOO_Crtlt
RnttP,щword.uhtml
Е'1 RкttPufWOrdConfirm1 ·
.".
1>
J>
с-
0
!!) StndCodt.ethtml
с- МiмgeControlltr.c1
..._
&ttlМl.ogWaikимshl:
F01gotPaиwo1d.ahtm1
FotgotPщwordConfi
@)Lockout.cUltrnl
Е:') l891"-clhtml
1!) Rtjiittr.cshtml
D flVkon.ko
"
AttessOenitd.cshtml
~ ~~~~=INtl
::1"'"'
о WWWfoot
J.ogit1Pwt181.nhtml
_V.u.t.onkripьPartlll.cihtml
bor.uhtml
~-~pottS.cU!tml
@l.V~ohtml
Рис.
3.6.
Стандартное содержимое, добавляемое в проект шаблонами
Empty
и
Web
Application (Model-View-Controller)
Дополнительные файлы , которые шаблон
Web Application (Model-View-Controller)
- всего лишь заполни­
добавляет в проект, выглядят устрашающе, но многие из них
тели или реализации, иллюстрирующие общие функциональные возможности. Одни
дополнительные файлы настраивают
MVC
или конфигурируют
ASP.NET Core:
другие
являются библиотеками клиентской стороны, которые будут включаться в состав
НТМL-разметки, генерируемой приложением. Список файлов в настоящий момент
может выглядеть огромным. но к концу книги вы будете понимать, что делает каж­
дый ИЗ НИХ.
Независимо от того. какой шаблон используется для создания проекта, существу­
ют общие папки и файлы, имеющиеся во всех шаблонах. Одни элементы в проекте
играют специальные роли, которые жестко закодированы в инфраструктуре
Core
или МVС либо каком-то инструменте из числа поддерживаемых
ASP.NET
Visual Studio.
Другие связаны с соглашениями об именовании, которые используются в большинс ­
тве проектов
ASP.NET Core
или
MVC. В табл. 3.1 описаны важные файлы и папки, с
ASP.NET Core MVC; часть из них по умолчанию не
которыми вы столкнетесь в проекте
присутствует в проекте, но будет представлена в последующих главах .
83
Глава 3. Паттерн, проекты и соглашения MVC
Таблица
3.1.
Сводка по элементам проекта
Папка или файл
Описание
/Areas
Области
-
MVC
это способ разбиения крупных приложений на
мелкие порции. Области рассматриваются в главе
/Dependencies
16
Зависимости предоставляют подробные сведения обо
всех пакетах, от которых зависит проект. Диспетчеры па­
кетов, применяемые
/Components
Visual Studio,
описаны в главе
6
Здесь определены классы компонентов представлений,
которые используются для отображения самодостаточ­
ных средств, таких как корзины для покупок. Компоненты
представлений обсуждаются в главе
/Controllers
22
Сюда помещаются классы контроллеров. Это соглаше­
ние. Классы контроллеров могут размещаться где угодно,
потому что все они компилируются в одну и ту же сборку.
Контроллеры подробно рассматриваются в главе
/Data
17
Здесь определены классы контекста баз данных, хотя я
предпочитаю игнорировать это соглашение и определять их
в папке
/Data/Migrations
Models,
как будет продемонстрировано в главе
Здесь хранятся миграции Eпtity
Framework
8
Саге, чтобы
базы данных можно было подготовить для хранения дан­
ных приложения. Миграции будут применяться в главах
8-11
/Models
как часть проекта
SportsStore
Сюда помещаются классы моделей представлений и
моделей предметной области. Это соглашение. Классы
моделей могут определяться в любом месте текущего
проекта или даже в отдельном проекте
/Views
Здесь хранятся представления и частичные представле­
ния, обычно сгруппированные в папки с именами конт­
роллеров, к которым они относятся. Представления будут
подробно описаны в главе
/Views/Shared
21
Здесь хранятся компоновки и представления, которые
не являются специфичными для каких-то контроллеров.
Представления будут подробно описаны в главе
/Views/ _ Viewimports. cshtml
21
Этот файл используется для указания пространств имен,
которые будут включены в файлы представлений
объясняется в главе
5.
Razor,
ки вспомогательных функций дескрипторов (глава
/Views/ ViewStart. cshtml
23)
Этот файл позволяет указать стандартную компоновку
для механизма визуализации
/appsettings. j son
как
Он также применяется для настрой­
Razor,
как описано в главе
5
Этот файл содержит настройки конфигурации, которые
можно подгонять под разные среды, такие как среда раз­
работки, среда тестирования и производственная среда.
Чаще всего он используется для определения строк под­
ключения к серверу баз данных, а также настроек ведения
журналов и отладки (глава
14)
84
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Окончание табл.
3. 1
Папка или файл
Описание
/bower.json
Этот файл содержит список пакетов, управляемых дис­
петчером пакетов
/<проект>.jsоn
(глава
Bower
6)
Этот файл содержит конфигурацию проекта, включая
пакеты
NuGet,
которые требуются для приложения, как
описано в главах
6
и
Данный файл скрыт и может
14.
быть отредактирован только путем щелчка правой кноп­
кой мыши на имени проекта в окне
выбора в контекстном меню пункта
(Редактировать <проект>.
/Program.cs
Solution Exploreг и
Edit <npoeкт>.csproj
csproj)
Этот класс конфигурирует платформу, на которой разме­
щено приложение (глава
14)
/Startup.cs
Этот класс конфигурирует само приложение (глава
/wwwroot
Сюда помещается статическое содержимое, такое как
файлы
CSS
и изображений. Кроме того, именно сюда
диспетчер пакетов
и
Соглашения в проекте
CSS
14)
(глава
Bower устанавливает
пакеты
JavaScript
6)
MVC
Есть два вида соглашений в проекте
MVC.
Первый вид
-
просто предположения
о том. как проект может быть структурирован. Например, пакеты
JavaScript
и
CSS
от независимых поставщиков, на которые вы полагаетесь, общепринято помещать в
папку
wwwroot/ lib.
Именно там ожидают их обнаружить другие разработчики
MVC,
и сюда их будет устанавливать диспетчер пакетов. Но вы вольны переименовать пап­
ку
lib
или вообще удалить ее и хранить пакеты в другом месте. Это не помешает ин­
фраструктуре
link
MVC
выполнить ваше приложение при условии, что элементы
scr ipt
и
в представлениях ссылаются на местоположение, куда вы поместили пакеты.
Второй вид соглашений вытекает из принципа соглашения по конфигурации (или
соглашения. над конфигурацией, если делать акцент на преимуществе соглашения пе­
ред конфигурацией), который был одним из главных аспектов, обеспечивших попу­
лярность платформе
Ruby
оп
Rails.
Соглашение по конфигурации означает, что вы
не должны явно конфигурировать, скажем, ассоциации между контроллерами и их
представлениями. Вы просто следуете определенному соглашению об именовании для
своих файлов
-
и все работает. Но когда приходится иметь дело с соглашением такого
вида, снижается гибкость в отношении изменения структуры проекта. В последующих
разделах объясняются соглашения, которые используются вместо конфигурации.
Совет. Все соглашения могут быть изменены путем замены стандартных компонентов
MVC
собственными реализациями. В книге будут описаны различные способы изменения со­
глашений, что поможет объяснить, как работают приложения
MVC,
но с рассматриваемы­
ми здесь соглашениями придется иметь дело в большинстве проектов.
Глава 3. Паттерн, nроекты и соглашения MVC
85
Следование соглашениям для классов контроллеров
Controller,
HomeController. При ссылке
Классы контроллеров имеют имена, которые заканчиваются на слово
например,
ProductController, AdminController
и
на контроллер в другом месте проекта, скажем, в случае применения вспомогательно­
го метода
HTML,
указывается первая часть имени (вроде
МVС автоматически добавляет к такому имени слово
Product),
Controller
а инфраструктура
и начинает поиск
класса контроллера.
Совет. Описанное поведение можно изменить, создав соглашение для моделей, как будет
объясняться в главе
31.
Следование соглашениям для представлений
Представления помещаются в папку по имени
Например, представление, ассоциированное с классом
в папку
/Viеws/ИмяКонтроллера.
ProductController,
попадет
/Views/Product.
Controller имени класса в имени папки внут­
Views не указывается, т.е. используется имя /Views/Product, а не /Views/
ProductController. Поначалу такой подход может показаться нелогичным, но он
Совет. Обратите внимание, что часть
ри
быстро войдет в привычку.
Инфраструктура
MVC
ожидает, что стандартное представление для метода дейс­
твия должно иметь имя этого метода. Например, представление, ассоциированное с
List. cshtml. Таким обра­
List () класса ProductController стан­
дартным представлением будет /Views/Product/List. cshtml. Стандартное пред­
ставление применяется при возвращении результата вызова метода View () в методе
методом действия по имени
List
(),должно называться
зом, ожидается, что для метода действия
действия, примерно так:
return View();
Можно указать имя другого представления:
return View("MyOtherView"};
Обратите внимание. что мы не включаем в представление расширение имени
MVC просматривает
/Views/Shared. Это значит, что
файла или путь. При поиске представления инфраструктура
папку, имеющую имя контроллера, а затем папку
представления, которые будут использоваться более чем одним контроллером, можно
поместить в папку
/Views/Shared
и
MVC
успешно найдет их.
Следование соглашениям для компоновок
Соглашение об именовании для компоновок предусматривает снабжение имени
файла префиксом в виде символа подчеркивания
ки внутри папки
/Views/Shared.
(_)
и размещение файлов компонов­
Стандартная компоновка применяется по умолча­
нию ко всем представлениям через файл
/Views/ _ ViewStart. cshtml.
Если вы не
хотите, чтобы стандартная компоновка применялась к представлениям, тогда можете
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
86
изменить настройки в файле_ViewStart.
cshtml
(либо вообще удалить его), указав
другую компоновку в представлении, например:
@{
Layout
=
"-/_MyLayout.cshtml";
Или же можете отключить компоновку дпя заданного представления:
@{
Layout
=
null;
Резюме
В главе был представлен архитектурный паттерн
MVC
и его сравнение с несколь­
кими другими паттернами, с которыми вы могли сталкиваться или слышать о них ра­
нее. Вы узнали о важности модели предметной области и ознакомились с концепцией
разделения обязанностей, которая позволяет отвязывать компоненты друг от друга
дпя обеспечения строгого отделения частей приложения. В следующей главе объясня­
ется структура проектов
MVC в Visual Studio
и рассматриваются важнейшие средства
языка С#, которые используются при разработке веб-приложений
MVC.
ГЛАВА
4
Важные
функциональные
возможности языка С#
в этой главе будут описаны функциональные возможности С#, используемые при
разработке веб-приложений, которые не очень хорошо понимают или часто пу­
тают. Однако настоящая книга не посвящена языку С#, так что для каждой возмож­
ности будет приводиться только краткий пример, поэтому вы можете проработать
примеры из остальных глав книги и задействовать нужные возможности в своих про­
ектах. В табл.
Таблица
4. 1.
4.1
приведена сводка для данной главы.
Сводка по главе
Листинг
Решение
Задача
Избегание обращения к свойствам
Используйте
по ссылкам
операцию
null
Упрощение свойств С#
4.5-4.8
nul 1-условную
Используйте автоматически реа­
4.9-4.11
лизуемые свойства
Используйте интерполяцию строк
4.12
Создание объекта и установка его свойств
Используйте инициализатор объ-
4.13-4.16
за один шаг
екта или коллекции
Упрощение формирования строк
Проверка типа или характеристик объекта
4.17-4.18
Используйте сопоставление
с образцом
Добавление функциональности в класс,
который не может быть модифицирован
Используйте расширяющий метод
4.19-4.26
Упрощение использования делегатов
Используйте лямбда-выражение
4.27-4.34
Применение неявной типизации
Используйте ключевое слово
4.35
Создание объектов без определения типа
Используйте анонимный тип
4.36, 4.37
Упрощение использования асинхронных
Используйте ключевые слова
4.38-4.41
методов
async
Получение имени метода или свойства
Используйте выражение
и однострочных методов
класса без определения статической строки
и
var
await
nameof
4.42, 4.43
88
Часть 1. Введение в инфраструктуру ASP.NET Соге MVC 2
Подготовка проекта для примера
для
Создадим
Visual Studio
в
главы
текущей
новый
по
проект
имени
LanguageFeatures, используя шаблон ASP.NET Саге Web Application (Веб-приложение
ASP.NEТ
Core)
из группы
.NET Core
(рис.
4.1 ).
Когда отобразятся различные конфигурации проекта, выберем шаблон
(Пустой). как показано на рис.
4.2.
Выберем варианты
списках в верхней части окна и удостоверимся в том, что
No Authentication
(Аутентификация) установлен в
флажок ЕпаЫе
Docker Support
Empty
ASP.NET Core 2.0 в
переключатель Authentication
.NET Core
и
(Аутентификация отсутствует). а
(Включить поддержку
Docker)
не отмечен. Щелкнем на
кнопке ОК. чтобы создать проект.
№.YOPIVJ~t
1
1
'
•
.NET Frмi~k".6.1
Р·
~ ConscНApp (.NHCOftJ
"' V1su•I<•
о.;~
:i;il!
\l/indows с~.ик Otsktop
Vleb
fj
\iJ
.Nff Ccкe;
.NПSt•ndмd
Cloud
Tts!
t
Х
'
_3 SortЬy. !Dtft\i
Туре:
Visual' С•
Ptoject tO"l'lplltnfor CJUt.ing ASP.NET
Co1t 'Pphntющ for Windows, LinUllt •nd
m•(OS Vllfl.9 .NЕТ (ore or .Ж Т
Ою LtЬr•ry (.NЕТ CortJ
~r•mewod'.
Uм ltst P•ojt<< f,NH Со")
.u..т"'"'°"''(.NЕТСо")
Visu•l~sic
j
SQLS.М<
Not f1ndin9wh.t )'OU vt. loolang fof?
Opcn V~i \WdIO · миuс1
'~"
- -~-~~~_,...,
!9_u;;""'""~
Ui\guagitfиitvfu
~-~-~~-~~ ...__~ о.:,м;.-=-Рис.
.
.NfТCort
1
Rttct.js
Е'О]
Y.i~API
ASP.N<f C0<t 2.0
iП
Wtb
Applkation
4.1.
•
0
в.-._
Crtat~dlrtdoryfor,~
О AddtoSour,e.(ontroJ
J
.~~~~ ~~OK~~~C..ncd
Выбор типа проекта
1.u!!!-"-'Ш
аю
о
\'/tb
Angul•r
An ltmf)t)'prt;ed ~emp&.t• f0tc1e•tJng tn ASP.NEТ
Cott •pplkat..on. Thts templtte dca not hwt .ny
conttntinit.
Applicltюn
{Мodtl·V1t..,...
Controtlв)
&Ь
Ruct.Jsand
........
Authentkмion
No AuthentЬtion
0 En•Ьl•Dod...~
СУ..:
>\t"rndoW',
Rtqu11ts [)qd;cr for ifmdgW$
Oocktr suppcrt ctn Юо Ье eivbled i.te' ~
COCJ~
- - - -· --·
--·-----
L------·- Рис.
4.2.
Выбор шаблона проекта
Глава 4. Важные функциональные возможности языка С#
Включение
89
ASP.NET Core MVC
Empty создает проект, который содержит минимальную конфи­
ASP.NET Core без какой-либо поддержки MVC. Это значит, что содержи­
мое-заполнитель, добавляемое шаблоном Web Application (Model-View-Controller) (Веб­
Шаблон проекта
гурацию
приложение (модель-представление-контроллер)), отсутствует, но также означает
необходимость в выполнении ряда дополнительных шагов для включения МVС, чтобы
заработали такие средства, как контроллеры и представления. В данном разделе мы
внесем изменения, требуемые для включения
MVC
в проекте, но пока не будем вда­
ваться в детали каждого шага.
Чтобы включить инфраструктуру
веденные в листинге
Листинг
using
using
using
using
using
using
using
using
4.1.
MVC,
внесем в класс
Startup
изменения, при­
4.1.
Включение
MVC
в файле
Startup. cs
из папки
LanguageFea tures
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencylnjection;
namespace LanguageFeatures
puЫic
class Startup {
puЫic
void ConfigureServices(IServiceCollection services) {
services.AddМvc();
puЫic
void Configure(IApplicationBuilder
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
арр,
IHostingEnvironment env) {
)
11 app.Run(async (context) => {
11 awai t context. Response. Wri teAsync ( "Hello World ! ") ;
11 } ) ;
app.UseМvcWithDefaultRoute();
Конфигурирование приложений ASP.NEТ
Core
МVС будет объясняться в главе
пока достаточно знать, что два оператора, добавленные в листинге
базовую настройку
MVC
4.1,
14,
а
обеспечивают
с применением стандартной конфигурации и соглашений.
Создание компонентов приложения
MVC
Имея настроенную инфраструктуру МVС, можно заняться добавлением компонен­
тов приложения
MVC,
ковых средств С#.
которые будут использоваться для демонстрации важных язы­
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
90
Создание модели
Начнем с создания простого класса модели, чтобы иметь какие-то данные, с кото­
рыми можно работать. Добавим папку по имени
Models и создадим в ней файл класса
4.2.
Product. cs
с определением, показанным в листинге
Листинг
Содержимое файла
4.2.
Product. cs
из папки
Models
namespace LanguageFeatures.Models {
puЬlic class Product {
puЫic string Name ( get; set; }
puЫic decimal? Price ( get; set;
puЫic static Product[] GetProducts()
Product kayak = new Product (
Name = "Kayak", Price = 275М
};
Product lifejacket = new Product
Name = "Lifejacket", Price = 48.95М
) ;
return new Product[] ( kayak, lifejacket, null );
Products определены свойства Name и Price, а также статический ме­
GetProducts (),который возвращает массив элементов Product. Один
из элементов, содержащихся в возвращаемом из метода GetProducts () массиве,
установлен в null; он будет применяться для демонстрации ряда языковых средств
В классе
тод по имени
позже в главе.
Создание контроллера и представления
В примерах настоящей главы для демонстрации различных языковых средств мы
используем простой контроллер. Создадим папку
класса по имени
ге
4.3.
HomeController. cs,
Controllers
и добавим в нее файл
содержимое которого приведено в листин­
В случае применения стандартной конфигурации инфраструктура
по умолчанию отправлять НТГР-запросы контроллеру
Листинг
4.3.
Содержимое файла
HomeController. cs
MVC
будет
Home.
из папки
Controllers
using Microsoft.AspNetCore.Mvc;
namespace LanguageFeatures.Controllers
puЫic class HomeController : Controller
puЬlic ViewResult Index()
return View(new string[] ( "С#", "Language", "Features" )) ;
Метод действия
Index ()
сообщает инфраструктуре
MVC
о необходимости визу­
ализировать стандартное представление и передает ей массив строк, который дол-
Глава 4. Важные функциональные возможности языка С#
91
жен быть включен в НТМL- разметку . отправляемую клиенту. Чтобы создать соответс­
твующее представление, добавим папку
а затем внутри нее папку
Index. cshtml
Home)
Views/Home
(сначала создав папку
Views,
и поместим в нее файл представления по имени
с содержимым, показанным в листинге
4.4.
Листинг
4.4. Содержимое файла Index. cshtml из папки Views/Home
@model
IEn umeraЫe<string>
@{ Layout = null;
< ! DOCTYPE html>
<html>
<head>
<meta name="viewport" co r1t e n t= " wi dth = device-w i dth" />
<title> Language Features</tit le >
</head>
<body>
<ul>
@foreach (string s in Model ) {
<li>@s</li>
</ul>
</bod y >
</html>
В результате запуска примера приложения за счет выбора пункта
(Запустить отладку) в меню
Debug
1--
Start Debugging
(Отладка) по.явится вьmод, представленный на рис.
--
4.3.
-
С LФ~cal~ost:б2~5
• С:;:
• Language
• Features
Рис.
4.3 .
Запуск пример приложения
Поскольку выводом во всех примерах данной главы является текст. отображаемые
браузером сообщения в дальнейшем будут показаны так:
С#
Language
features
Использование null-условной операции
С помощью null-условной операции можно более элегантно обнаруживать зна­
чения
nul l.
Во время разработки приложений МVС встречается много проверок на
92
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
предмет
null,
т.к. нужно выяснять, содержит ли запрос специфический заголовок
или значение либо содержит ли модель определенный элемент данных. Традиционно
работа со значениями
null
требовала явных проверок, которые могли становиться
утомительными и подверженными ошибкам, когда требовалось инспектировать и
объект, и его свойства. За счет применения null-условной операции такие проверки
будут намного легче и компактнее (листинг
Листинг
4.5.
Обнаружение значений
из папки
null
4.5).
в файле
HomeController. сз
Controllers
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using LanguageFeatures.Models;
namespace LanguageFeatures.Controllers
puЬlic class HomeController : Controller
puЫic ViewResult Index() (
List<string> results
= new
List<string>();
foreach (Product р in Product.GetProducts())
string name = р? .Name;
decimal? price = р? . Price;
resul ts .Add (string. Format ( "Name: {О) , Price: {1)", name, price)) ;
return View(results);
Статический метод
GetProducts (),
определенный в классе
Product, возвра­
Index () конт­
щает массив объектов, который инспектируется в методе действия
роллера с целью получения списка значений
Narne
и
Price. Проблема в том, что и
null, т.е. нельзя просто ссы­
foreach, не получив исключение
объект в массиве, и значения его свойств могут быть
Narne или р. Price внутри цикла
NullReferenceException. Во избежание такой
латься нар.
ситуации используется null-услов­
ная операция:
string name = p?.Name;
decimal? price = p?.Price;
null-условная операция обозначается знаком вопроса(?). Если значение р равно
null, то переменная narne также
но null, тогда переменной narne
будет установлена в
null.
Если значение р не рав­
будет присвоено значение свойства
Аналогичной проверке подвергается и свойство
Price.
Person. Narne.
Обратите внимание, что пе­
ременная, которой выполняется присваивание с применением null-условной опера­
ции, должна быть в состоянии иметь дело со значениями
price
объявлена с десятичным типом, допускающим
null, поэтому
null (decirnal ?).
переменная
Связывание в цепочки null-условных операций
Для навигации по иерархии объектов null-условные операции могут связываться
в цепочки и превращаться в по-настоящему эффективный инструмент для упрощения
Глава 4. Важные функциональные возможности языка С#
кода и обеспечения безопасной навигации. В листинге
4.6 к классу Product
93
добавлено
свойство с вложенными ссылками, что создает более сложную иерархию объектов.
Листинг
4.6.
Добавление свойства в файле
Product.cs из папки Мodels
namespace LanguageFeatures.Models {
puЫic class Product {
puЬlic
string Name { get; set; 1
decimal? Price { get; set; )
puЬlic
Product Related { qet; set; }
puЬlic
static Product[] GetProducts()
Product kayak = new Product 1
Name = "Kayak", Price = 275М
puЫic
);
Product lifejacket = new Product
Name = "Lifejacket", Price = 48.95М
);
kayak.Related = lifejacket;
return new Product[J 1 kayak, lifejacket, null 1;
Product имеет свойство Related, которое может ссылаться
Product. В методе GetProducts {) мы устанавливаем свойство
объекта Product, представляющего каяк. В листинге 4.7 показано, как
Каждый объект
на другой объект
Related
для
можно соединять вместе null-условные операции для навигации по свойствам объ­
ектов, не вызывая исключение.
Листинг
4.7.
Обнаружение вложенных значений
из папки
null в файле HomeController.cs
Controllers
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using LanguageFeatures.Models;
namespace LanguageFeatures.Controllers
puЬlic class HomeController : Controller
ViewResult Index() {
List<string> results = new List<string>();
foreach (Product р in Product.GetProducts())
string name = p?.Name;
puЫic
decimal? price
=
p?.Price;
strinq related.Name = p?.Related?.Name;
results.Add(strinq.Format("Name: (0}, Price: (1}, Related: {2}",
name, price, related.Name));
return View(results);
94
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
null-условную операцию можно применять к каждой части цепочки свойств,
например:
string relatedName
=
p?.Related?.Name;
relatedName получит значение null. когда значение
Related. Иначе relatedName будет присвоено значение свойс­
р. Rela ted. Name. После запуска примера приложения в окне браузера появится
В таком случае переменная
null
тва
имеет р или р.
следующий вывод:
Name: Kayak, Price: 275, Related: Lifejacket
Name: Lifejacket, Price: 48.95, Related:
Name: , Price: , Related:
Комбинирование null-условной операции
и операции объединения с
null
Для установки альтернативного значения. представляющего
null.
удобно комби­
нировать null-условную операцию (один знак вопроса) и операцию объединения с
null
(два знака вопроса), как демонстрируется в листинге
Листинг
4.8.
Сочетание операций работы с
из папки
4.8.
null в файле HomeCon troller. cs
Controllers
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using Languagefeatures.Models;
namespace Languagefeatures.Controllers
puЫic class HomeController : Controller
ViewResult Index() {
List<string> results = new List<string>();
foreach (Product р in Product.GetProducts())
string name = р?. Name ?? "<No Name>";
decimal? price = р?. Price ?? О;
string relatedName = р? .Related? .Name ?? "<None>";
results.Add(string.Format("Name: {0}, Price: {1}, Related: {2}",
name, price, relatedName));
puЫic
return View(results);
null-условная операция гарантирует, что при навигации по свойствам объектов
не возникнет исключение
null
NullReferenceException, а операция объединения с
null в результатах, отображаемых в брау­
обеспечивает отсутствие значений
зере. В результате запуска примера приложения в окне браузера будет получен такой
вывод:
Name: Kayak, Price: 275, Related: Lifejacket
Name: Lifejacket, Price: 48.95, Related: <None>
Name: <No Name>, Price: О, Related: <None>
Глава 4. Важные функциональные возможности языка С#
95
Использование автоматически
реализуемых свойств
В языке С# поддерживаются автоматически реализуемые свойства, которые мы
применяли при определении свойств класса
Person
в предыдущем разделе:
namespace LanguageFeatures.Models {
puЫic class Product {
puЬlic
{ get; set; }
Price { get; set;
Product Related { get; set; }
puЫic
static Product[] GetProducts()
puЫic
string
Nаш.е
puЬlic deciшal?
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 };
Данное средство дает возможность определять свойства, не реализуя блоки кода
get
и
set.
Средство автоматически реализуемых свойств позволяет трактовать сле­
дующее определение свойства:
puЫic
string Name { get; set; }
как эквивалентное приведенному ниже коду:
string Name {
get { return name;
set { name = value; }
puЫic
Средства подобного типа известны как "синтаксический сахар", который делает
работу с С# более приятной (в этом случае за счет устранения избыточного кода. дуб­
лируемого в итоге для каждого свойства), однако существенно не изменяет поведение
языка. Термин "сахар" может показаться умаляющим ценность средства, но любые
улучшения, которые облегчают написание и сопровождение кода, приносят пользу
-
особенно в крупных и сложных проектах.
Использование инициализаторов
автоматически реализуемых свойств
Автоматически реализуемые свойства поддерживаются, начиная с версии С#
3.0.
В последней версии языка С# доступны инициализаторы для автоматически реали-
96
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
зуемых свойств. которые позволяют устанавливать начальные значения, не требуя
применения конструкторов (листинг
Листинг
4.9.
4.9).
Использование инициализатора автоматически реализуемого свойства
в файле
Product. cs
из папки Мodels
namespace LanguageFeatures.Models {
puЬlic class Product {
puЫic
puЬlic
puЫic
puЫic
string Name { get; set; }
string Category { get; set; } = "Watersports";
decimal? Price { get; set; }
Product Related { get; set; }
puЫic
static Product[] GetProducts()
Product kayak = new Product {
11
Name =
кауаk",
Category = "Water Craft",
Price = 275М
};
Product lifejacket = new Product {
Name = "Lifejacket", Price = 48.95М
};
kayak.Related = lifejacket;
return new Product[] { kayak, lifejacket, null };
Присваивание значения автоматически реализуемому свойству не препятствует
применению установщика для изменения свойства в более позднее время. а всего
лишь приводит в порядок код для простых типов с конструкторами, которые содер­
жат список присваиваний свойств, обеспечивающих наличие стандартных значений.
В приведенном примере инициализатор присваивает свойству
Category значение
"Wa tersports". Начальное значение может быть изменено, что и делается при со­
здании объекта kayak, когда ему указывается значение "Wa ter Craft".
Создание автоматически реализуемых свойств только для чтения
Для создания свойства только для чтения в определении автоматически реализу­
емого свойства. которое имеет инициализатор, нужно опустить ключевое слово
(листинг
Листинг
4.1 О).
4.1 О.
Создание свойства только для чтения в файле
Product. cs
из папки Мodels
namespace LanguageFeatures.Models
puЫic class Product {
puЫic
puЫic
puЫic
puЫic
puЬlic
string Name { get; set; )
string Category { get; set;
decimal? Price { get; set; )
Product Related { get; set; )
bool InStock { get; } = true;
"Watersports";
set
Глава 4. Важные функциональные возможности языка С#
puЬlic
97
static Product[) GetProducts()
Product kayak = new Product
Narne = "Kayak",
Category = "Water Craft",
Price = 275М
);
Product lifejacket = new Product {
Narne = "Lifejacket", Price = 48.95М
};
kayak.Related = lifejacket;
return new Product[J { kayak, lifejacket, null };
Свойство
InStock
инициализируется значением
true
и не может быть изменено;
тем не менее, ему можно присвоить значение внутри конструктора типа, как показа­
но в листинге
Листинг
4. 11.
4.11.
Присваивание значения свойству только для чтения
в файле
Product. cs из папки Models
narnespace LanguageFeatures.Models {
puЫic class Product {
Product (bool stock
InStock = stock;
puЬlic
puЫic
puЫic
puЫic
puЫic
puЬlic
= true)
string Narne { get; set; }
string Category { get; set;
decirnal? Price { get; set; }
Product Related { get; set; }
bool InStock { get; }
"Watersports";
puЫic
static Product[] GetProducts()
Product kayak = new Product {
Narne = "Kayak",
Category = "Water Craft",
Price = 275М
};
Product lifejacket = new Product (false)
Name = "Lifejacket",
Price = 48. 95М
} ;
kayak.Related = lifejacket;
return new Product[J { kayak, lifejacket, null };
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
98
Конструктор позволяет указывать в качестве аргумента значение для свойства,
допускающего только чтение, и если значение не предоставлено, тогда он устанавли­
вает свойство в стандартное значение
true.
После установки конструктором значе­
ния свойства оно не может быть изменено.
Использование интерполяции строк
Традиционным инструментом С# для образования строк, содержащих значения
данных, является метод
контроллере
string. Format
().Вот пример применения этого приема в
Home:
results.Add(string.Format( "Name: {О}, Price: {1}, Related: {2}",
name, price, relatedName));
Язык С# также поддерживает другой подход, называемый интерполяцией строк,
который позволяет избежать необходимости гарантировать, что ссылки вида
и
( 1)
( О ),
в шаблоне строки соответствуют переменным, указанным в качестве ар­
{2)
гументов. Взамен интерполяция строк использует имена переменных напрямую (лис­
тинг
4.12).
Листинг
4.12.
Применение интерполяции строк в файле
из папки
HomeController. cs
Controllers
using Microsoft.AspNetCore.Mvc ;
using System.Collections.Gener ic;
using LanguageFeatures.Models;
namespace LanguageFeatures.Control lers
puЫic class HomeController : Controller
puЬlic
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($ 11 Name: {name}, Price: {price}, Related: {relatedName}");
return View(results);
Интерполированная строка снабжается префиксом в виде символа $ и содержит
"дыры", которые представляют собой ссылки на значения, содержащиеся внутри фи­
гурных скобок ( ( и ) ). Когда строка оценивается, "дыры" заполняются текущими зна­
чениями указанных переменных и констант.
Среда
Visual Studio
обеспечивает поддержку средства
IntelliSense
для создания
интерполированных строк и предлагает список доступных членов, когда набирается
символ открывающей фигурной скобки, что помогает минимизировать количество
опечаток, а результатом оказывается формат строки, который легче понять.
Глава 4. Важные функциональные возможности языка С#
99
Совет. Интерполяция строк поддерживает все спецификаторы формата, которые доступ­
ны для метода
string. Forma t (). Спецификаторы формата включаются в виде части
Price: {price: С2}" сформатирует значение price как денежное
"дыры", поэтому$"
значение с двумя десятичными цифрами.
Использование инициализаторов
объектов и коллекций
При создании объекта в статическом методе
GetProducts ()
класса
Product
при­
менялся инициализатор объекта, который позволяет создавать объект и указывать
значения его свойств за один шаг, например:
Product kayak
= new Product
"I<ayak" ,
Name
Cateqory = "Water Craft",
Price = 275М
=
) ;
Это еще одна форма "синтаксического сахара", делающая язык С# легче в исполь­
зовании. Без такого средства пришлось бы вызывать конструктор
Product
и затем
применять вновь созданный объект для установки всех его свойств:
Product kayak = new Product();
kayak.Name = "Kayak";
kayak.Category = "Water Craft";
kayak.Price = 275М;
Связанное с ним средство
-
инициализатор коллекции
-
позволяет создавать
коллекцию и указывать ее содержимое за один шаг. Без такого инициализатора со­
здание, скажем, массива потребовало бы указания размера и элементов массива по
отдельности (листинг
Листинг
4.1 З.
4.13).
Инициализация массива в файле
из папки
HomeController. cs
Controllers
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using LanguageFeatures.Models;
namespace LanguageFeatures.Controllers
puЫic class HomeController : Controller
puЬlic
ViewResult Index() {
strinq[] names = new strinq[З];
"ВоЬ";
names [О]
"Joe";
names[l]
names [2] = "Alice";
return View("Index", names);
=
100
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Инициализатор коллекции дает возможность указать содержимое массива как
часть его конструирования, что неявно предоставляет компилятору размер массива
4.14).
(листинг
Листинг
4.14.
Использование инициализатора коллекции в файле
из папки
HomeController. cs
Controllers
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using LanguageFeatures.Models;
namespace LanguageFeatures.Controllers
puЫic class HomeController : Controller
ViewResul t Index () {
return View("Index", new string[] (
puЫic
"ВоЬ",
Элементы массива задаются между символами
"Joe", "Alice" }) ;
{ и f • что
позволяет получить более
краткое определение коллекции и делает возможным определять коллекцию внутри
вызова метода. Код в листинге
4.14
дает тот же результат, что и код в листинге
4.13;
если запустить пример приложения, то в окне браузера получится следующий
вывод:
ВоЬ
Joe
Alice
Использование инициализатора индексированной коллекции
В последних версиях С# улучшен способ инициализации коллекций с индексами,
таких как словари. В листинге
4.15
приведен код метода действия
Index (),
перепи­
санный для определения коллекции с помощью традиционного подхода С# к иници­
ализации словаря.
Листинг
4.15.
Инициализация словаря в файле
из папки
HomeController. cs
Controllers
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using LanguageFeatures.Models;
namespace LanguageFeatures.Controllers
puЫic class HomeController : Controller
ViewResult Index() {
Dictionary<string, Product> products = new Dictionary<string, Product> (
( "Кауаk", new Product ( Nаше = "Кауаk", Price = 275М } } ,
( "Lif'ejacket", new Product( Name = "Lif'ejacket", Price = 48. 95М } }
puЫic
};
return View("Index", products .Keys);
Глава 4. Важные функциональные возможности языка С#
101
Синтаксис для инициализации коллекции такого типа слишком сильно полагает­
ся на символы
{ и }, особенно
когда значения коллекции создаются с применением
инициализаторов объектов. В последних версиях С# поддерживается более естест­
венный подход к инициализации индексированной коллекции, который согласован
со способом извлечения или модификации значений после того, как коллекция была
инициализирована (листинг
Листинг
4. 16.
4.16).
Использование синтаксиса инициализатора коллекции
в файле
HomeController.cs
из папки
Controllers
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using LanguageFeatures.Models;
namespace LanguageFeatures.Controllers
puЫic class HomeController : Controller
ViewResult Index() {
Dictionary<string, Product> products = new Dictionary<string, Product> {
puЬlic
["Кауаk"] = new Product { Name = "Кауаk", Price = 275М },
[ "Lif'ejacket"] = new Product { Name = "Lifejacket", Price
= 48. 95М
);
return View("Index", products.Keys);
Результат остается прежним. т.е. создание словаря, ключами которого являются
Kayak
и
Lifejacket,
а значениями
-
объекты
Product,
но элементы создаются с
применением системы обозначений для индексов, используемой в других операци­
ях с коллекциями. Вот вывод. получаемый в окне браузера после запуска примера
приложения:
Kayak
Lifejacket
Сопоставление с образцом
Одним из наиболее полезных недавних добавлений в язык С# стала поддержка со­
поставления с образцом, которую можно использовать для проверки, что объект отно­
сится к определенному типу или имеет специфические характеристики. Сопоставление
с образцом
-
еще одна форма "синтаксического сахара", и она способна значительно
упростить сложные блоки условных операторов. Ключевое слово
выполнения проверки типа. как демонстрируется в листинге
Листинг
4. 17.
Выполнение проверки типа в файле
из папки
Controllers
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using LanguageFeatures.Models;
namespace LanguageFeatures.Controllers
puЫic class HomeController : Controller
i s
применяется для
4.17.
HomeCon trol ler. cs
102
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
puЬlic
ViewResult Index() {
object [] data = new object [] { 275М, 29.
"apple", "orange", 100, 10 } ;
decimal total = О ;
for (int i = О; i < data.Length; i++) {
if (data[i] is decimal d) {
total += d;
95М,
return View("Index", new string[] { $"Total: {total:C2}" }) ;
Ключевое слово
is
выполняет проверку типа, и если значение принадлежит ука­
занному типу, тогда оно присваивается новой переменной:
if (data [i] is decimal d) {
Выражение будет оценено как
тип
decimal.
Значение
data (i]
true,
если значение, хранящееся в
будет присваиваться переменной
data [ i], имеет
d, что позволит
его использовать в последующих операторах без необходимости в каких-либо пре­
образованиях типов. Ключевое слово
is
даст совпадение лишь с указанным типом.
т.е. будут обработаны только два значения в массиве
массиве относятся к типам
string
и
int).
data []
(остальные элементы в
В результате запуска приложения в окне
браузера появится следующий вывод:
Total: $304.95
Сопоставление с образцом в операторах
swi tch
Сопоставление с образцом может также применяться в операторах
рые поддерживают ключевое слово
сопоставляется в операторе
Листинг
case
when
(листинг
4.18).
4.18. Сопоставление с образцом в файле HomeController.cs
из папки
swi tch,
кото­
для ограничения случаев, когда значение
Controllers
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using LanguageFeatures.Models;
namespace LanguageFeatures.Controllers
puЫic class HomeController : Controller
ViewResult Index() {
object[] data = new object[] { 275М,
"apple", "orange", 100, 10 1;
decimal total = О;
puЬlic
29.95М,
for (int i =О; i < data.Length; i++} {
switch (data[i]) {
case deciшal deciшalValue:
total += deciшalValue;
break;
103
Глава 4. Важные функциональные возможности языка С#
case int intValue when intValue > 50:
total += intValue;
break;
return View("Index", new string[] { $"Total: {total:C2}" }) ;
Для сопоставления любого значения специфического типа в операторе
case
ис­
пользуются тип и имя переменной, например:
case decimal decimalValue:
Такой оператор
case
сопоставляется с любым значением
его новой переменной по имени
ности, можно добавить
decimalValue.
ключевое слово when:
decirnal
и присваивает
Чтобы достичь большей избиратель­
case int intValue when intValue > 50:
case сопоставляет значения типа int и присваивает их перемен­
intValue, но лишь когда значение больше 50. Запуск приложения при­
Этот оператор
ной по имени
водит к следующему выводУ в окне браузера:
Total: $404.95
Использование расширяющих методов
Расширяющие методы
-
удобный способ добавления методов в классы, владель­
цем которых вы не являетесь и не можете модифицировать напрямую. В листин­
ге
4.19 приведено определение класса ShoppingCart, добавленного в папку Models в
ShoppingCart. cs, который представляет коллекцию объектов Product.
виде файла
Листинг
4.19.
Содержимое файла
ShoppingCart. cs
из папки
Models
using System.Collections.Generic;
namespace LanguageFeatures.Models
puЫic class ShoppingCart {
puЬlic IEnumeraЫe<Product> Products { get; set; }
Это простой класс, который действует как оболочка для последовательности объек­
тов
Product
(в данном примере необходим лишь элементарный класс). Предположим,
что нас интересует возможность определения общей стоимости объектов
классе
ShoppingCart,
Product
в
но мы не можем изменить сам класс из-за того, что он пос­
тупил от третьей стороны и его исходный код отсутствует. Для добавления нужной
функциональности можно применить расширяющий метод. В листинге
зан класс
MyExtensionMethods,
MyExtensionMethods.cs.
также добавленный в папку
Models
4.20
пока­
в виде файла
104
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
Листинг
4.20. Содержимое файла МyExtensionМethods. cs из папки Мodels
namespace LanguageFeatures.Models {
puЫic
static class MyExtensionMethods
static decimal TotalPrices(this ShoppingCart cartParam) {
decimal total = О;
foreach (Product prod in cartParam.Products)
total += prod?.Price ?? О;
puЫic
return total;
Ключевое слово
TotalPrices ()
this,
расположенное перед первым параметром,
помечает
как расширяющий метод. Первый параметр указывает .NЕТ, к ка­
кому классу может применяться расширяющий метод
случае. На экземпляр класса
ShoppingCart,
-
к
ShoppingCart
в данном
к которому применен расширяющий ме­
cartParam. Расширяющий метод
Product в ShoppingCart и возвращает сумму значений их
свойств Product. Price. В листинге 4.21 демонстрируется применение расширяю­
щего метода в методе действия контроллера Home.
тод, можно ссьmаться с использованием параметра
проходит по объектам
На заметку! Расширяющие методы не позволяют нарушать правила доступа, которые клас­
сы определяют для своих методов, полей и свойств. С помощью расширяющего метода
функциональность класса можно расширить, но с использованием только тех членов, к
которым в любом случае имеется доступ.
Листинг
4.21.
Применение расширяющего метода в файле
из папки
HomeController. cs
Controllers
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using LanguageFeatures.Models;
namespace LanguageFeatures.Controllers
puЫic class HomeController : Controller
ViewResult Index{) {
ShoppingCart cart
= new ShoppingCart { Products = Product. GetProducts () } ;
decimal cartтotal = cart.TotalPrices();
return View ( 11 Index 11 , new string [] { $ "Total: {cartTotal: С2} " } ) ;
puЬlic
Вот ключевой оператор:
decimal cartTotal
=
cart.TotalPrices();
Глава 4. Важные функциональные возможности языка С#
Метод
105
TotalPrices () вызывается на объекте ShoppingCart. как если бы он бьm
ShoppingCart, хотя он представляет собой расширяющий метод. ко­
частью класса
торый определен в совершенно другом классе. Среда .NЕТ будет обнаруживать рас­
ширяющие классы, если они находятся в области действия текущего класса, т.е. яв­
ляются частью того же самого пространства имен или пространства имен, которое
указано в операторе
using.
В результате запуска примера приложения в окне браузе­
ра появится следующий вывод:
Total: $323. 95
Применение расширяющих методов к интерфейсу
Можно также создавать расширяющие методы, которые применяются к интерфей­
су, что позволит вызывать такие расширяющие методы для всех классов, реализую­
щих данный интерфейс. В листинге
4.22
приведен код класса
ShoppingCart,
моди­
фицированный с целью реализации интерфейса IEnumeraЫe<Product>.
Листинг
4.22.
Реализация интерфейса в файле
ShoppingCart. cs
из папки Мodels
using System.Collections;
using System.Collections.Generic;
namespace LanguageFeatures.Models
puЫic
class ShoppingCart :
IEnumeraЬle<Product>
puЫic IEnumeraЬle<Product>
Products { get; set;
IEnumerator<Product> GetEnumerator ()
return Products.GetEnumerator();
puЬlic
IEnumerator IEnumeraЬle. GetEnumerator ()
return GetEnumerator();
Теперь расширяющий метод можно изменить так, чтобы он работал с интерфей­
сом IEnumeraЬle<Product> (листинг
Листинг
4.23.
4.23).
Модификация расширяющего метода в файле МyExtensionМethods.
из папки
Models
using System.Collections.Generic;
namespace LanguageFeatures.Models
puЫic
static class MyExtensionMethods
puЬlic
static decimal TotalPrices (this
decimal total = О;
foreach (Product prod in products)
total += prod?. Price ??
return total;
О;
IEnumeraЬle<Product>
products)
cs
106
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
тип первого параметра бьш изменен на IEnumeraЫe<Product>. а это значит. что
цим
foreach
в теле метода работает непосредственно с объектами
Product.
Переход
на использование упомянутого интерфейса означает, что мы можем подсчитать об­
щую стоимость объектов
Product,
перечисляемых посредством любого интерфейса
IEnumeraЫe<Product>, что вмючает не только экземпляры
же массивы объектов
Листинг
4.24.
Product
(листинг
ShoppingCart,
но так­
4.24).
Применение расширяющего метода к массиву в файле
HomeController. cs
из папки
Controllers
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using LanguageFeatures.Models;
namespace LanguageFeatures.Controllers
puЫic class HomeController : Controller
puЬlic
ViewResult Index() {
ShoppingCart cart
= new ShoppingCart { Products = Product.GetProducts() );
Product [] productArray = {
new Product {Name = 11 Kayak 11 , Price = 275М},
new Product {Name = 11 Lifejacket 11 , Price = 48.
95М}
};
decimal cartTotal = cart.TotalPrices();
decimal arrayTotal = productArray.TotalPrices();
return View( 11 Index 11 , new string[]
$ 11 Cart Total : { cartTotal : С2} 11 ,
$ 11 Array Total: { arrayTotal: С2} 11
} )
;
После запуска проекта будут получены показанные ниже результаты. которые де­
монстрируют, что расширяющий метод возвращает один и тот же результат незави­
симо от способа перебора объектов
Product:
Cart Total: $323.95
Array Total: $323. 95
Создание фильтрующих расширяющих методов
Последний аспект расширяющих методов, о котором необходимо упомянуть
-
возможность их использования для фильтрации коллекций объектов. Расширяющий
метод, который оперирует на интерфейсе IEnumeraЬle<T> и также возвращает
IEnumeraЬle<T>, может задействовать мючевое слово
yield,
чтобы применить
критерий отбора к элементам в источнике данных с целью генерации сокращенного
набора результатов. В листинге
в класс
MyExtensionMethods.
4.25
представлен такой метод. который добавляется
Глава 4. Важные функциональные возможности языка С#
Листинг
4.25.
107
Добавление фильтрующего расширяющего метода в файле
МyExtensionМethods.
cs
из папки
Controllers
using System.Collections.Gener ic;
namespace LanguageFeatures.Models
puЫic
static class MyExtensionMethods
static decimal TotalPrices(this
decimal total = О;
foreach (Product prod in products)
total += prod?.Price ?? О;
puЫic
IEnumeraЫe<Product>
products) {
return total;
static IEnumeraЫe<Product> FilterByPrice(
this IEnwneraЫe<Product> productEnwn, decimal minimumPrice)
foreach (Product prod in productEnum) {
if ( (prod?. Price ?? 0) >= minimumPrice)
yield return prod;
puЫic
Расширяющий метод по имени
Fi 1 t е r В у Р r i
се
()
принимает дополнительный
параметр, который позволяет фильтровать товары. так что в результате возвращают­
ся объекты
Product,
у которых значение свойства
Price совпадает или превышает
FilterByPrice () демонс­
значение. указанное в параметре. Использование метода
трируется в листинге
Листинг
4.26.
4.26.
Применение фильтрующего расширяющего метода в файле
HomeController. cs
из папки
Controllers
using Microsoft.AspNetCore.Mvc ;
using System.Collections.Gener ic;
using LanguageFeatures.Models;
namespace LanguageFeatures.Control lers
puЬlic class HomeController : Controller
ic ViewResul t Index () {
Product[J productArray = {
new Product {Name = "Kayak", Price = 275М},
new Product {Name = "Lifejacket", Price = 48.95М},
new Product {Name = "Soccer ball", Price = 19.SOM},
new Product {Name = "Corner flag", Price = 34. 95М}
рuЫ
};
decimal arrayTotal = productArray.FilterByPric e(20) .TotalPrices();
return View("Index", new string[] { $"Array Total:
{arrayТotal:C2}"
}) ;
108
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
При вызове метода
TotalPrices ()
FilterByPrice ()
на массиве объектов
Product
метод
получает и использует для подсчета суммы только те из них, кото­
рые стоят больше
$20.
В результате запуска примера приложения в окне браузера
отобразится следующий вывод:
Total: $358.90
Использование лямбда-выражений
Лямбда-выражения являются функциональной возможностью, ставшей причиной
многочисленных заблуждений
-
не в последнюю очередь из-за того, что упрощаемое
с их помощью средство само вызывает путаницу. Чтобы понять решаемую задачу,
рассмотрим расширяющий метод
FilterByPrice
(),который был определен в пре­
дыдущем разделе. Метод реализован так, что он может фильтровать объекты
Product
по цене, а это значит, что если понадобится фильтровать объекты по названию, тогда
придется создать второй метод вроде приведенного в листинге
Листинг
4.27.
4.27.
Добавление фильтрующего метода в файле МyExtensionмethods. cs
из папки
Models
using System.Collections.Generic;
namespace LanguageFeatures.Models
puЫic
static class MyExtensionMethods
puЫic
static decimal TotalPrices(this
decimal total = О;
foreach (Product prod in products)
total += prod?.Price ?? О;
IEnumeraЬle<Product>
products)
return total;
puЫic
static IEnumeraЫe<Product> FilterByPrice(
this IEnumeraЫe<Product> productEnum, decimal minimumPrice) {
foreach ( Product prod in productEnum) {
if ( (prod?.Price ?? 0) >= minimumPrice)
yield return prod;
static IEnwneraЬle<Product> FilterByName (
this IEnwneraЬle<Product> productEnum, char firstLetter)
foreach (Product prod in productEnum) {
if (prod?.Name?[O] == firstLetter) {
yield return prod;
puЬlic
В листинге
4.28
демонстрируется применение обоих фильтрующих методов внут­
ри контроллера для создания двух разных итоговых сумм.
Глава 4. Важные функциональные возможности языка С#
Листинr
109
4.28. Использование двух фильтрующих методов в файле HomeController. cs
из папки Controllers
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using LanguageFeatures.Models;
namespace LanguageFeatures.Controllers
puЫic class HomeController : Controller
puЫic
ViewResult Index() {
Product [] productArray = {
"Kayak", Price = 275М},
new Product {Name
"Lifej acket", Price = 4 8. 95М},
new Product { Name
19.SOM},
"Soccer ball", Price
new Product {Name
"Corner flag", Price = 34.95М}
new Product {Name
};
decimal priceFilterTotal =productArray.FilterВyPrice(20) .TotalPrices();
decimal nameFil terTotal = productArray. Fil terВyName ( ' S' ) . TotalPrices () ;
return View (" Index" , new string [] {
$ "Price Total: {priceFil terTotal: С2)" ,
$ "Name Total: {nameFil terTotal: С2}" } ) ;
Первый фильтр отбирает все товары с ценой
ры с названиями, начинающимися на букву
S.
$20
и выше, а второй фильтр
-
това­
После запуска примера приложения в
окне браузера появится такой вывод:
Price Total: $358.90
Name Total: $19.50
Определение функций
Описанный выше процесс можно повторять до бесконечности и создавать филь­
трующие методы для каждого интересующего свойства и сочетаний свойств. Более
элегантный подход предусматривает отделение кода. обрабатывающего перечисле­
ние, от критерия отбора. Язык С# облегчает задачу, позволяя передавать функции
как объекты. В листинге
4.29
показан единственный расширяющий метод, который
фильтрует перечисление объектов
Product, но делегирует отдельной функции реше­
ние о том, какие из них включать в результат.
Листинr
4.29. Соэдание универсальноrо фильтрующеrо метода в файле
МyExtensionМethods. cs из папки Models
using System.Collections.Generic;
using System;
namespace LanguageFeatures.Models
puЫic
static class MyExtensionMethods
static decimal TotalPrices(this
decimal total = О;
puЫic
IEnumeraЬle<Product>
products) {
11 О
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
foreach (Product prod in products)
total += prod?.Price ?? О;
return total;
static IEnwneraЫe<Product> Filter (
this IEnwneraЫe<Product> productEnwn,
Func<Product, bool> selector) {
foreach (Product prod in productEnwn)
if (selector(prod)) {
yield return prod;
puЫic
Вторым аргументом метода
ект
Product
кцию для каждого объекта
вращает
true.
Product
4.30.
bool.
Метод
Filter ()
вызывает эту фун­
и включает его в результат, если функция воз­
Для применения метода
автономную функцию (листинг
Листинг
является функция, которая принимает объ­
Fil ter ()
и возвращает значение типа
Fil ter ()
можно указать метод или создать
4.30).
Использование функции для фильтрации объектов
в файле
HomeController. cs
из папки
Product
Controllers
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using LanguageFeatures.Models;
using Systeш;
namespace LanguageFeatures.Controllers
puЫic class HomeController : Controller
bool FilterByPrice(Product р) {
return (p?.Price ?? 0) >= 20;
puЫic
ViewResult Index() {
Product [] productArray = {
"Kayak", Price = 275М},
new Product {Name
"Lifejacket", Price = 48.95М},
new Product {Name
19.SOM},
"Soccer ball", Price
new Product {Name
"Corner flag", Price = 34.95М}
new Product {Name
1;
Func<Product, bool> nameFilter
return prod?.Naшe?[O] == 'S';
= delegate
} ;
priceFilterTotal = productArray
.Filter(FilterByPrice)
.TotalPrices();
deciшal naшeFil terTotal = productArray
.Filter(nameFilter)
.TotalPrices();
deciшal
(Product prod) {
Глава 4. Важные функциональные возможности языка С#
111
return View("Index", new string[] (
$"Price Total: (priceFilterTotal:C2)",
$"Name Total: {nameFilterTotal:C2)" ));
FilterByPrice (),за­
Func<Product, bool> устраняет такую
Оба подхода не идеальны. Определение методов, подобных
соряет определение класса. Создание объекта
проблему, но сопряжено с неудобным синтаксисом, который труден для восприятия и
сопровождения. Именно эту задачу решают лямбда-выражения, позволяя определять
функции более элегантным и выразительным способом, как видно в листинге
Листинг
4.31.
Использование лямбда-выражений в файле
из папки
using
using
using
using
4.31.
HomeController. cs
Controllers
Microsoft.AspNetCore.Mvc;
System.Collections.Generic;
LanguageFeatures.Models;
System;
namespace LanguageFeatures.Controllers
puЫic class HomeController : Controller
puЫic
ViewResult Index() {
Product [] productArray = (
"Kayak", Price = 275М),
new Product (Name
"Lifejacket", Price = 48.95М),
new Product (Name
19.SOM),
"Soccer ball", Price
new Product (Name
"Corner flag", Price = 34.95М)
new Product (Name
) ;
decimal priceFilterTotal = productArray
.Filter(p => (p?.Price ?? 0) >= 20)
.TotalPrices();
productArray
decimal nameFilterTotal
.Filter(p => p?.Name?[O] = 'S')
.TotalPrices();
return View("Index", new string[]
$"Price Total: (priceFilterTotal:C2)",
$"Name Total: (nameFilterTotal:C2)" )) ;
Лямбда-выражения вьщелены полужирным. Параметры выражаются без указания
типа, который будет выведен автоматически. Символы
=>
можно читать как "направ­
ляется в" и они связывают параметр с результатом лямбда-выражения. В рассматри­
Product по имени р направляется в результат bool, кото­
true, если значение свойства Price эквивалентно или превышает
выражении или если значение свойства Name начинается с буквы S во
ваемом примере параметр
рый будет равен
2О
в первом
втором выражении. Такой код работает аналогично отдельному методу и делегату в
виде функции, но он короче и для большинства людей легче в восприятии.
112
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Другие формы лямбда-выражений
Представлять логику делегата посредством лямбда-выражения вовсе не обязательно. С тем
же успехом можно вызвать метод:
prod => EvaluateProduct(prod)
Если требуется лямбда-выражение для делегата, который имеет несколько параметров, то
параметры должны быть заключены в круглые скобки:
(prod, count) => prod.Price > 20 && count >
О
Наконец, если в лямбда-выражении необходима логика, которая требует более одного опе­
ратора, то ее можно реализовать, используя фигурные скобки
тором
({ }) и
завершая блок опера­
return:
(prod, count) => {
/ / ... несколько операторов
return resul t;
кода
.. .
Применять лямбда-выражения в коде не обязательно, но они служат изящным способом
выражения сложных функций в читабельной и ясной манере. Вы будете часто встречать
лямбда-выражения в примерах, приводимых далее в книге.
Использование методов и свойств в форме лямбда-выражений
Лямбда-выражения можно применять для реализации конструкторов, методов и
свойств. Во время разработки приложений
MVC,
особенно при написании контролле­
ров, часто появляются методы, которые содержат единственный оператор, выбира­
ющий данные для отображения и представление для визуализации. В листинге
приведен код метода действия
Index ( ) ,
4.32
переписанный в соответствии с таким об­
щим шаблоном.
Листинг
4.32.
Создание общего шаблона действия в файле
из папки
using
using
using
using
using
HomeController.cs
Controllers
Microsoft.AspNetCore.Mvc;
Systern.Collections.Generic;
LanguageFeatures.Models;
Systern;
System.Linq;
narnespace LanguageFeatures.Controllers
puЫic class HorneController : Controller
puЫic iliewResul t Index () {
return View(Product.GetProducts() .Select(p
=>
р?.Nаше));
)
Метод действия
Index ()получает от статического метода Product. GetProducts ()
Product и с помощью LINQ строит проекцию значений свойств
коллекцию объектов
Name.
Затем проекция применяется в качестве модели представления для стандарт­
ного представления. В результате запуска примера приложения в окне браузера ока­
жется след,ующий вывод:
Kayak
Lifejacket
Глава 4. Важные функциональные возможности языка С#
11 З
В окне браузера также будет присутствовать пустой элемент списка, потому что
метод
GetProducts ()
включает в свой результат ссылку
null,
но в данном разделе
главы это неважно.
Когда тело конструктора или метода состоит из единственного оператора, его мож­
но переписать в виде лямбда-выражения (листинг
Листинг
4.33.
Метод действия, представленный как лямбда-выражение,
в файле
using
using
using
using
using
4.33).
HomeController. cs
из папки
Controllers
Microsoft.AspNetCore.Mvc;
System.Collections.Generic;
LanguageFeatures.Models;
System;
System.Linq;
namespace LanguageFeatures.Controllers
puЫic class HomeController : Controller
ViewResul t Index () =>
View(Product.GetProducts() .Select(p => p?.Name));
puЫic
Лямбда-выражения для методов позволяют опустить ключевое слово
return
и ис­
пользуют символы=> (направляется в) для ассоциирования сигнатуры метода (вклю­
чая аргументы) с его реализацией. Метод
Index (),
работает точно так же, как метод
из листинга
Index ()
показанный в листинге
4.32,
4.33,
но представлен более
лаконично. Тот же самый базовый подход можно также применять для определения
свойств. В листинге
4.34
демонстрируется добавление в класс
Product
свойства, ко­
торое использует лямбда-выражение.
Листинг
4.34.
Представление свойства как лямбда-выражения в файле Product. cs
из папки
Models
namespace LanguageFeatures.Models
puЫic class Product {
Product(bool stock
InStock
stock;
puЬlic
puЫic
puЫic
puЬlic
puЫic
puЫic
puЫic
puЫic
=
true)
string Name { get; set; )
string Category { get; set;
"Watersports";
decimal? Price { get; set; )
Product Related { get; set; }
bool InStock { get; }
bool NameВeginsWi thS => Name? [О]
'S' ;
static Product[J GetProducts()
Product kayak = new Product {
Name = "Kayak",
Category = "Water Craft",
Price = 275М
};
114
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Product lifejacket = new Product(false) {
Narne = "Lifejacket",
Price = 48.95М
) ;
kayak.Related = lifejacket;
return new Product[J { kayak, lifejacket, null );
Использование автоматического
выведения типа и анонимных типов
Ключевое слово
var
зания ее типа (листинг
позволяет определять локальную переменную без явного ука­
4.35).
Такой прием называется выведекием типа или кеявкой
типизацией.
Листинг
4.35.
Использование выведения типа в файле
из папки
using
using
using
using
using
HomeController. cs
Controllers
Microsoft.AspNetCore.Mvc;
Systern.Collections.Generic;
LanguageFeatures.Models;
Systern;
Systern.Linq;
narnespace LanguageFeatures.Controllers
puЫic class HorneController : Controller
ViewResul t Index () {
var names = new [] { 11 кауаk", "Lifejacket", "Soccer ball" } ;
return View(names);
puЬlic
Речь идет вовсе не о том, что переменная myVariaЬle не имеет типа; мы всего
лишь предложили компилятору самостоятельно вывести тип из кода. Компилятор ис­
следует объявление массива и решает, что он является строковым. Выполнение при­
мера дает следующий вывод:
Kayak
Lifejacket
Soccer ball
Использование анонимных типов
Комбинируя инициализаторы объектов и выведение типов, можно создавать про­
стые объекты модели представления, которые удобны для передачи данных между
контроллером и представлением, без необходимости в определении класса или струк­
туры (листинг
4.36).
Глава 4. Важные функциональные возможности языка С#
Листинг
4.36.
Создание анонимного типа в файле HomeController. cs
из папки
using
using
using
using
using
115
Controllers
Microsoft.AspNetCore.Mvc;
System.Collections.Generic;
LanguageFeatures.Models;
System;
System.Linq;
namespace LanguageFeatures.Controllers
puЫic class HomeController : Controller
ViewResult Index() {
var products = new [] (
new ( Name = "Kayak", Price = 275М } ,
new ( Name = "Lifejacket", Price = 48. 95М } ,
new ( Name = "Soccer ball" , Price = 19. 50М } ,
new ( Name = "Corner flag" , Price = 34 . 95М }
puЫic
} ;
return View(products.Select(p
Каждый объект в массиве
products
=> p.Name));
является анонимно типизированным. Это не
означает, что объекты имеют динамическую приро.цу в том смысле, в каком считают­
ся динамическими переменные
JavaScript.
Это просто значит, что определение типа
будет создано автоматически компилятором. Строгая типизация по-прежнему обеспе­
чивается. Скажем, вы можете получать и устанавливать только те свойства, которые
бьии определены в инициализаторе. В результате запуска примера в окне браузера
отобразится сле.цующий вывод:
Kayak
Lifejacket
Soccer ball
Corner flag
Компилятор С# генерирует класс на основе имени и типов параметров в инициа­
лизаторе. Два анонимно типизированных объекта, которые имеют те же самые имена
и типы свойств, будут относиться к одному и тому же автоматически сгенерированно­
му классу. В результате все объекты в массиве
products
получат один и тот же тип,
поскольку они определяют те же самые свойства.
Совет. Для определения массива анонимно типизированных объектов должно применять­
ся ключевое слово
var,
т.к. тип не будет создан до тех пор, пока код не скомпилирует­
ся, и его имя не известно. Все элементы в массиве анонимно типизированных объектов
обязаны определять те же самые свойства, иначе компилятор не сможет выяснить тип
массива.
В целях демонстрации изменим вывод в листинге
типа вместо значения свойства
Name.
4.37,
чтобы отображать имя
116
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Листинг
4.37.
Отображение имени анонимного типа в файле
из папки
using
using
using
using
using
HomeController.cs
Controllers
Microsoft.AspNetCore.Mvc;
System.Collections.Generic;
LanguageFeatures.Models;
System;
System.Linq;
namespace LanguageFeatures.Controllers
puЫic class HomeController
Controller
ViewResult Index() {
var products = new [] {
new { Name
"Kayak", Price = 275М } ,
"Lifejacket", Price = 48.95М },
new { Name
new { Name
"Soccer ball", Price
19.SOM },
new { Name
"Corner flag", Price = 34.95М }
puЫic
};
return View(products.Select(p => p.GetType() .Name));
Всем объектам в массиве был назначен один и тот же тип, в чем можно убедиться.
запустив пример. Имя типа не выглядит дружественным к пользователю, но оно и не
рассчитано на непосредственное использование. и в вашем случае может оказаться
другим:
<>f __AnonymousType0'2
<>f AnonymousType0'2
<>f~AnonymousType0'2
<>f
AnonymousType0'2
Использование асинхронных методов
Асинхронные методы осуществляют возврат и выполняют работу в фоновом режи­
ме с уведомлением о ее завершении, позволяя коду в это время заниматься другими
действиями. Асинхронные методы
-
важный инструмент при устранении узких мест
в коде; они позволяют приложениям извлекать преимущества от наличия нескольких
процессоров и процессорных ядер. чтобы выполнять работу в параллельном режиме.
В инфраструктуре
MVC
асинхронные методы могут применяться для увеличения
общей производительности приложения, предоставляя серверу большую степень гиб­
кости относительно того, как запросы планируются и выполняются. Для выполне­
ния работы асинхронным образом используются два ключевых слова С#
- async
и
await.
Преследуемые в настоящем разделе цели требуют добавления в пример проекта
новой сборки
.NET.
чтобы можно было делать асинхронные НТТР-запросы. Щелкнем
правой кнопкой мыши на имени проекта
выберем в контекстном меню пункт
LanguageF'eatures. csproj)
LanguageF'eatures
в окне
Solution Explorer,
Edit LanguageFeatures.csproj
(Редактировать
и добавим элемент. показанный в листинге
4.38.
Глава 4. Важные функциональные возможности языка С#
Листинг
117
4.38. Добавление пакета в файле LanguageFeatures. csproj
из папки
LanguageFeatures
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="System.Net.Http" Version="4.3.2" />
< /I temGroup>
</Project>
После сохранения файла
сборку
в
System. Net. Http
главе 6.
LanguageFeatures. csproj
среда
Visual Studio загрузит
и добавит ее в проект. Более подробно процесс будет описан
Работа с задачами напрямую
Язык С# и платформа
.NET предлагают великолепную
поддержку для асинхронных
методов, но код быстро становится многословным, а разработчики, не привыкшие к
параллельному программированию, зачастую не могут справиться с необычным син­
таксисом. В качестве примера в листинге
ни
4.39
приведен асинхронный метод по име­
(),который определен в классе
GetPageLength
Models в виде
папку
Листинг
4.39.
файла
MyAsyncMethods,
добавленном в
MyAsyncMethods. cs.
Содержимое файла МyAsyncМethods. cs из папки Models
using System.Net.Http;
using System.Threading.Tasks;
namespace LanguageFeatures.Models
puЫic
class MyAsyncMethods {
puЫic
static Task<long?> GetPageLength()
HttpClient client
=
new HttpClient();
var httpTask = client.GetAsync("http://apress.com");
return httpTask.ContinueWith((Task<HttpResponseMessage> antecedent) =>
1
return antecedent.Result.Content.Headers.ContentLength;
}) ;
Метод использует объект
System.Net.Http.HttpClient
держимого домашней страницы издательства
Apress и
для запрашивания со­
возвращает длину полученного
содержимого. Работа, которая будет выполняться асинхронно. представлена в
.NET
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
118
как объект
Task.
Объекты
Task
строго типизируются на основе результата, выдавае­
мого фоновой работой. Таким образом, при вызове метода
получается объект
Task<HttpResponseMessage>.
HttpClient. GetAsync ()
Он сообщает о том, что запрос вы­
полнится в фоновом режиме и его результатом будет объект
Ht tpResponseMessage.
Совет. Когда мы употребляем понятия вроде фоновый режим, то опускаем массу деталей,
чтобы подчеркнуть только ключевые аспекты, которые важны для мира
.NET
MVC.
Поддержка
для асинхронных методов и параллельного программирования в целом превосход­
на, и ее рекомендуется внимательно изучить, если вы хотите создавать по-настоящему
высокопроизводительные приложения, которые способны извлекать преимущества от
многопроцессорного или многоядерного оборудования. Вы увидите, как
MVC
облегчает
создание асинхронных веб-приложений далее в книге по мере представления разнооб­
разных функциональных средств.
Самой непонятной для большинства программистов частью является продолжение,
представляющее собой механизм, с помощью которого указывается то, что должно
произойти, когда фоновая задача завершится. В приведенном примере применяется
метод
Con tinueW i th () для обработки объекта Ht tpResponseMessage, получаемого
HttpClient. GetAsync (). Это делается с использованием лямбда-выра­
из метода
жения, которое возвращает значение свойства, содержащего длину полученного от
веб-сервера
Apress
содержимого. Вот код продолжения:
return httpTask.ContinueWith( (Task<HttpResponseMessage> antecedent) =>
{
return antecedent.Result.Content.Headers.ContentLength;
1);
Обратите внимание, что ключевое слово
return
встречается два раза. Именно дан­
return указывает,
Task<HttpResponseMessage>. который при завершении
задачи возвратит (второе ключевое слово return) длину заголовка ContentLength.
Заголовок ContentLength возвращает значение long? (тип long, допускающий
null), т.е. результатом метода GetPageLength () является Task<long?>:
ная часть вызывает путаницу. Первое применение ключевого слова
что возвращается объект
puЫic
static Task<long?> GetPageLength() {
Не переживайте, если все сказанное выглядит для вас бессмысленным
замешательстве вы не одиноки. Именно по этой причине в
Microsoft
-
в своем
и решили доба­
вить в С# ключевые слова. упрощающие работу с асинхронными методами.
Применение ключевых слов
Разработчики из
Microsoft
async
и
awai t
ввели в язык С# два ключевых слова, которые спе­
циально призваны облегчить использование
Ht tpClien t. GetAsync () .
Ими являются
async
как с их помощью упростить наш пример метода.
асинхронных методов,
и
awai t;
в листинге
4.40
подобных
показано,
Глава 4. Важные функциональные возможности языка С#
Листинг
Применение ключевых слов
4.40.
из папки
async
и
awai t
в файле МyAsyncМethods
119
. cs
Models
using Systern.Net.Http;
using Systern.Threading.Tasks;
narnespace LanguageFeatures.Models
puЫic class MyAsyncMethods {
puЫic async static Task<lonq?> GetPageLength()
HttpClient client = new HttpClient();
var httpMessage = await client.GetAsync("http://apress.com");
return httpMessage.Content.Headers.ContentLength;
Ключевое слово
awai t
используется при вызове асинхронного метода. Оно сооб­
щает компилятору С# о том. что необходимо подождать результата
возвращается методом
GetAsync (),
Task,
который
и затем заняться выполнением остальных опе­
раторов в том же методе.
Применение ключевого слова
метода
GetAsync
awai t
означает, что мы можем трактовать результат
().как если бы он был обычным методом, и просто присвоить воз­
вращаемый им объект
HttpResponseMessage
какой-нибудь переменной. Еще лучше
то, что затем можно использовать ключевое слово
выдачи результата из другого метода
-
return традиционным образом для
ContentLength в данном
значения свойства
случае. Это намного более естественный подход. к тому же нам не придется беспо­
коиться по поводу метода
слова
ContinueWi th ()
и многократного применения ключевого
return.
При использовании ключевого слова
туре метода ключевое слово
async,
awai t
ре. Тип результата метода не изменяется
возвращает
ализованы
Task<long?>.
с применением
потребуется также добавить к сигна­
как было сделано в рассмотренном выше приме­
-
метод
GetPageLength () по-прежнему
await и async ре­
Причина в том, что ключевые слова
ряда искусных трюков
компилятора.
которые позволя­
ют использовать более естественный синтаксис, но не изменяют того, что происхо­
дит внутри методов, к которым они применены. Программист, вызывающий метод
GetPageLength
().по-прежнему имеет дело с результатом
существует фоновая операция. которая выдает значение
Task<long?>, т.к. все еще
long, допускающее null;
хотя, конечно же, программист может также предпочесть пользоваться ключевыми
словами
awai t
и
async.
Такой шаблон повсеместно соблюдается в контроллере МVС, что позволяет легко
писать асинхронные методы действий (листинг
Листинг
4.41.
Определение асинхронных методов действий в файле
HomeController. cs
using
using
using
using
using
usinq
4.41).
из папки
Controllers
Microsoft.AspNetCore.Mvc;
Systern.Collections.Generic;
LanguageFeatures.Models;
Systern;
Systern.Linq;
System.Threadinq.Tasks;
narnespace LanguageFeatures.Controllers
120
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
puЫic
class HomeController : Controller {
async Task<ViewResul t> Index () {
long? length = awai t МyAsyncМethods . GetPageLength () ;
return View(new string[] { $"Length: {length}" }) ;
puЫic
1Ип результата метода действия
Index ()
изменен на
Task<ViewResul t>.
Подобным
образом инфраструктуре МVС сообщается о том. что метод действия будет возвращать
объект
Task,
который по завершении выдаст объект
ViewResul t,
а тот предоставит
детали представления. подлежащего визуализации, и требующиеся ему данные. К оп­
ределению метода было добавлено ключевое слово
вать ключевое слово
awai t
при вызове метода
О продолжениях позаботятся инфраструктура
async, что позволило использо­
MyAsyncMethods. GetPathLength ().
MVC
и платформа
.NET.
а результатом
будет код, который легко писать. читать и сопровождать. После запуска приложения
появится вывод. похожий на показанный ниже (хотя наверняка с отличающейся дли­
ной, т.к. содержимое веб-сайта
Apress
часто изменяется):
Length: 54576
Получение имен
Во время разработки веб-приложений есть много задач. в которых необходимо ссы­
латься на имя аргумента, переменной. метода или класса. Распространенными при­
мерами может служить генерация исключения или создание ошибки проверки досто­
верности при обработке пользовательского ввода. Традиционный подход предполагал
применение строкового значения с жестко закодированным именем (листинг
Листинг
4.42.
Использование жестко закодированного имени в файле
HomeController. св
using
using
using
using
using
4.42).
из папки
Controllers
Microsoft.AspNetCore.Mvc;
System.Collections.Generic;
LanguageFeatures.Models;
System;
System.Linq;
namespace LanguageFeatures.Controllers
puЬlic class HomeController : Controller
puЫic ViewResult Index() (
var products = new [] {
new { Name = "Кауаk", 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 (р => $ "Name:
Вызов метода
Select ()
из
LINQ
{р. Name}
, Price:
{р.
Price} ") ) ;
генерирует последовательность строк. каждая из
которых содержит жестко закодированную ссылку на свойства
Name
и
Price.
Глава 4. Важные функциональные возможности языка С#
121
Запуск приложения дает следующий вывод в окне браузера:
Name:
Name:
Name:
Name:
Kayak, Price: 275
Lifejacket, Price: 48.95
Soccer ball, Price: 19.50
Corner flag, Price: 34.95
Проблема такого подхода в том, что он предрасположен к ошибкам, которые
обусловлены либо неправильно набранным именем, либо некорректно обновленным
именем в строке после рефакторинга. Результат может вводить в заблуждение, что
особенно проблематично для сообщений, отображаемых пользователю. Язык С# под­
держивает выражение
nameof,
благодаря которому ответственность за формирова­
ние строки имени возлагается на компилятор (листинг
Листинг
4.43.
Использование выражений
из папки
4.43).
nameof в файле HomeController. cs
Controllers
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using LanguageFeatures.Models;
using System;
using System.Linq;
namespace LanguageFeatures.Controllers
puЫic class HomeController : Controller
puЫic ViewResult Index() {
var products = new [] {
"Kayak", Price = 27 5М } ,
new { Name
"Lifejacket", Price = 48.95М },
new { Name
19. 50М } ,
"Soccer ball", Price
new { Name
"Corner flag", Price = 34.95М}
new { Name
};
return View(products.Select(p =>
$"{nameof(p.Name)}: {p.Name}, {nameof(p.Price)}: {p.Price}"));
Компилятор обрабатывает ссьmку вроде р
. Narne
таким образом, что в строку вклю­
чается только последняя часть, порождая тот же самый вывод. как и в предыдущем при­
мере. Среда
narneof,
Visual Studio
располагает поддержкой средства
IntelliSense для
выражений
поэтому вы будете снабжены подсказками при выборе ссьmок, а выражения
корректно обновятся в случае рефакторинга кода. Поскольку за работу с
narneof
отвеча­
ет компилятор, применение недопустимой ссьmки приводит к ошибке на этапе компиля­
ции, что не позволит некорректным или устаревшим ссьmкам ускользнуть от глаз.
Резюме
В главе бьm дан обзор основных языковых средств С#, которые должен знать резуль­
тативный программист приложений МVС. Язык С# достаточно гибкий для того, чтобы
обычно существовало несколько способов решения любой задачи, но есть средства, с
которыми вы будете чаще всего встречаться во время разработки веб-приложений и
видеть повсюду в примерах. рассматриваемых в данной книге. В следующей главе бу­
дет представлен механизм визуализации Razoг и приведены объяснения, как его ис­
пользовать для генерации динамического содержимого в веб-приложениях МVС.
ГЛАВА
5
Работа с
Razor
в приложении ASP.NET Core MVC для выпуска содержимого, отправляемого кли­
ентам, используется компонент, который называется механизмом визуали­
за41ш. Стандартным механизмом визуализации является
Razor,
и он обрабатывает
аннотированные НТМL-файлы, производя поиск инструкций, которые вставляют ди­
намическое содержимое в вывод. отправляемый браузеру.
В настоящей главе дается краткое введение в синтаксис
опознавать выражения
Razor,
Razor,
так что вы сможете
когда столкнетесь с ними. Мы не собираемся превра­
Razor; считайте ее в большей степени
Razor будут раскрываться в после­
других средств MVC. В табл. 5.1 приведена
щать главу в исчерпывающий справочник по
ускоренным курсом по синтаксису. Особенности
дующих главах книги при рассмотрении
сводка, позволяющая поместить
Таблица
5.1.
Помещение
Razor
Razor
в контекст.
в контекст
Вопрос
Ответ
Что это такое?
Razor -
это механизм визуализации, отвечающий за встраива­
ние данных в документы
Чем он полезен?
HTML
Возможность динамической генерации содержимого является
неотъемлемой частью процесса написания веб-приложения.
Механизм визуализации
Razor
предоставляет средства, которые
упрощают работу с остальной инфраструктурой
MVC
Как он используется?
ASP.NET Core
с применением операторов С#
Выражения
Razor добавляются
к статической НТМL-разметке в
файлах представлений. Такие выражения оцениваются для гене­
рации ответов на клиентские запросы
Существуют ли какие-то
Выражения
скрытые ловушки или
При решении, должна логика принадлежать представлению или
ограничения?
контроллеру, иногда возникают затруднения, и это способно
Razor
могут содержать почти любые операторы С#.
разрушить принцип разделения обязанностей, который является
центральным в паттерне
MVC
Существуют ли
В главе
альтернативы?
зуализации. Доступны механизмы визуализации от независимых
21
объясняется, как написать собственный механизм ви­
поставщиков, но они обычно полезны только в ограниченных си­
туациях и для них не гарантируется долгосрочная поддержка
В табл.
5.2
приведена сводка по главе.
Глава 5. Работа с Razor
Таблица
123
5.2. Сводка по главе
Листинг
Задача
Решение
Доступ к модели представления
Используйте выражение
@model для
оп­
ределения типа модели и выражения
@Model для
5.5, 5.14,
5.17
доступа к объекту модели
Использование имен типов без их
Создайте файл импортирования
уточнения с помощью пространств
представлений
5.6, 5.7
имен
Применяйте компоновку
5.8-5.1 О
Указание стандартной компоновки
Используйте файл запуска представления
5.11-5.1 З
Передача данных из контроллера
Применяйте объект
5.15, 5.16
Определение содержимого, которое
будет использоваться множеством
представлений
ViewBag
представлению за пределами модели представления
Выборочная генерация содержимого
Используйте условные выражения
Генерация содержимого для каждого
Применяйте выражение
элемента в массиве или коллекции
низма
Razor
@foreach меха-
5.18, 5.19
5.20, 5.21
Razor
Подготовка проекта для примера
Razor создадим новый проект
ASP.NET Core Web Application (Неб-приложение ASP.NET Core) из
группы .NET Core, используя шаблон Empty (Пустой). как поступали в предыдущей
главе. Включим инфраструктуру MVC, внеся в класс Startup изменения. которые по­
Для демонстрации работы механизма визуализации
по имени
Razor
типа
казаны в листинrе
Листинг
using
using
using
using
using
using
using
using
5.1.
5.1. Включение MVC в файле Startup. cs из папки Razor
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
namespace Razor {
puЫic class Startup {
puЫic
void ConfigureServices(IServiceCollection services) {
services.AddМvc();
puЫic
void Configure(IApplicationBuilder
{
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
арр,
IHostingEnvironment env)
124
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
// app.Run(async (context) => {
//
awai t context. Response. Wri teAsync ( "Hello World ! ") ;
//
}) ;
app.UseМvcWithDefaultRoute();
Определение модели
Создадим папку
Models
веденным в листинге
Листинг
5.2.
5.2
и добавим в нее файл класса по имени
Product. cs
с при­
определением простого класса модели.
Содержимое файла
Product. cs
из папки
Models
namespace Razor.Models {
puЫic
class Product {
puЬlic
puЫic
puЫic
puЫic
puЫic
int ProductID { get; set;
string Name { get; set; }
string Description { get; set;
decimal Price { get; set;
string Category { set; get; }
Создание контроллера
Стандартная конфигурация, установленная в файле
Startup. cs, следует соглаше­
Home no умолчанию.
класса HomeController. cs, по­
нию МVС относительно отправки запросов контроллеру с именем
Создадим папку
Controllers
и добавим в нее файл
местив в него простое определение контроллера (листинг
Листинг
5.3.
Содержимое файла
HomeController. cs
using Microsoft.AspNetCore.Mvc;
using Razor.Models;
namespace Razor.Controllers {
puЫic class HomeController : Controller
ViewResult Index() {
Product myProduct = new Product {
ProductID = 1,
Name = "Kayak",
Description = "А boat for one person",
Category = "Watersports",
Price = 275М
puЫic
};
return View(myProduct);
5.3).
из папки
Controllers
Глава 5. Работа с Razor
В контроллере определен метод действия по имени
ся объект
View ( ) ,
Product
с заполнением его свойств. Объект
125
I ndex (),
в котором создает­
Product
передается методу
так что он используется как модель во время визуализации представления.
При вызове метода
View ()
имя файла представления не указывается, поэтому будет
применяться стандартное представление для метода действия.
Создание представления
Чтобы определить стандартное представление для метода действия
Index () ,
здадим папку
ставления
Листинг
Views / Home и добавим в нее файл типа MVC View Page (Страница
MVC) по имени Index. cshtml с содержимым из листинга 5.4.
со­
пред­
5.4. Содержимое файла Index. cshtml из папки Views/Home
@model Razor.Models.Product
@{
Layout = null;
< 1 DOCTYPE html >
<html>
<head>
<meta name="viewport" con ten t ="width=de vice-w i dth " />
<title>Inde x</t it l e>
</head>
<body>
Con tent will go here
</ body>
</ html>
В последующих разделах мы рассмотрим различные части представления
Razor
и продемонстрируем разнообразные вещи. которые с ним можно делать. При изуче­
нии
Razor
полезно помнить. что представления существуют для выражения пользо­
вателю одной или более частей модели
-
и это оэначает генерацию НТМL-разметки ,
которая отображает данные, извлеченные из одного или множества объектов. Если
не забывать, что мы всегда пытаемся строить НТМL-страницу. которая может быть
отправлена клиенту. тогда вся активность механизма визуализации
Razor
обретает
смысл. В результате запуска приложения отобразится простой вывод, приведенный
на рис.
5.1.
Content \Yill go 11ere
Рис.
5. 1.
Выполнение примера приложения
126
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Работа с объектом модели
Давайте начнем с самой первой строки в файле представления
I n dex . c shtml :
@mo del Razor .Mo dels. Pr od u c t
Выражения
Razor
начин аются с символа
@.
В да нном случае выражение
@mo d e 1
объявляет тип объекта модели. который будет пер едаваться представлению из ме ­
тода действия . В итоге появляется возможность ссылки на методы , поля и сво йства
объекта модели представления посредством
@Model ,
отображается простое дополнение к представлению
Листинг
5.5.
ка к показано в листинге
5.5,
где
I nde x .
Ссылка на свойство объекта модели представления
в файле Index. csh tml из папки Views /Home
@model Ra z o r . Models.Prod u c t
@{
Layout = nul l ;
< 1 DOCTYPE html>
<htm l >
<h e ad>
<me t a name= " v i ewp or t" co n t e n t= " wi dth =dev i ce-wi dth " />
<t itle>Index< / t i tle >
</ h e ad>
<bo dy >
@Model.Name
</ b o dy >
</ h tml >
На заметку! Обратите внимание, что тип объекта модели представления объявляется с ис­
пользованием
применением
@mo de l
@Model
(со строчной буквой
m),
а доступ к свойству
Name
производится с
(с прописной буквой М). Поначалу такая особенность может немно­
го запутывать , но со временем она станет вполне привычной.
П осле запуска прил ожения отобразится вывод . представленный н а рис .
С \ Q ~ca~hos :60753
Kayak
Рис.
5.2.
=========
Результат чтения значения свой ства внутри пр е дста вл е н ия
5.2.
Глава 5. Работа с Razor
Представление. которое использует выражение
127
@mode l для указания типа, назы­
вается строго типизированным представлением. Среда Visнal
Studio
способна при­
@model для открытия окна со списком предполагаемых
нов в случае ввода @Model с последующей точкой (рис . 5.3).
менять выражение
имен чле­
""°del Rozor .~lodels . Product
3
4
Loyout • null;
5
б
7
8
9
10
11
12
< ! ООСТУРЕ ht ml>
B <html>
G <heod>
<met l!I name•"viewport"' content•"width•device width" />
<ti tle> Index</ti tle>
</head>
13
.Э <Ьоdу>
l
4
@'l'lodel.~
~ </body>
14
15
16
17
</html>
~ Otscription
...
Ф Equ•ls
Ф
GetHA<hCodc
GetType
Ф
~
·
1· -.'to-··-----·~ ProductlO
Price
1100
8
....
string Razor.Models.Product.Nome { get; set; }
~
Ф
Рис.
5.3.
Среда
ToString
Visual Studio
предлагает список предполагаемых имен членов
на основе выражения
@Mode l
Список предполагаемых имен членов, отображаемый
Visual Studio,
помогает из­
бегать ошибок в представлениях Razoг. При желании такие предположения можно
игнорировать, и тогда
Visual Studio
будет подсвечивать проблемные имена членов,
чтобы можно было внести исправления. как поступает с обычными файлами классов
5.4, где мы пытается сослать­
Visual Sttidio обнаруживает, что
С#. Пример подсветки проблемы можно видеть на рис.
ся на свойство
класс
@Model. NotARealP roper t y . Среда
Product , указанный в качестве типа модели, не содержит данное свойство, и
подсвечивает ошибку в редакторе.
l) . r.
Г::J <head>
<meta name="vie1~port" content="width=device-1vidth" />
<title>Index</title>
</head>
t
- <body>
@Мodel . NotARealPro er
f </Ьоdу>
</html>
1
Рис.
5.4.
Среда
Visual Studio сообщает о проблеме с выражением @Mode l
128
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
Использование файла импортирования представлений
При определении объекта модели в начале файла
Index. cshtml
необходимо было
включать пространство имен, которое содержит класс модели. например:
@model Razor.Models.Product
По умолчанию все типы. на которые производится ссылка в строго типизирован­
ном представлении
Razor.
должны уточняться своими пространствами имен. Это не
особо крупная проблема. когда есть только ссылка на объект модели. но понимание
представления может значительно затрудниться при написании более сложных вы­
ражений Razoг. таких как рассматриваемые позже в главе.
Добавив в проект файл импорmuрования представлений. можно указать набор про­
странств имен. в которых должен осуществляться поиск типов. Файл импортирования
представлений размещается в папке
На заметку! Файлы в папке
( ),
Views,
Views
и имеет имя_ Viewlmports.
cshtml.
имена которых начинаются с символа подчеркивания
пользователю не возвращаются, что позволяет с помощью имен файлов проводить
различие между представлениями, подлежащими визуализации, и поддерживающими их
файлами . Файлы импортирования представлений и файлы компоновки (будут описаны
вскоре) имеют имена, начинающиеся с символа подчеркивания.
Чтобы создать файл импортирования представлений, щелкнем правой кнопкой
мыши на папке
Addc~New
ltem
Views
в окне
Solution Explorer.
(Страница импортирования представлений
(рис.
Sortby: jD!f1ult
ASP.NEТ
Core
Г,}."
~
Code
Gentr11I
.., Web
ASP.NEТ
Generol
Scripts
Content
~
MVC)
MVC View lmports Page
ASP.NET CoreqWeb
из категории
5.5).
.., lnstolled
..,
выберем в контекстном меню пункт
(ДобавитьqНовый элемент) и укажем шаблон
ASP . NEТ
MVCViewlmport<P•ge
ASP.NEТ Core
~·
R.iorT19 Helper
ASP . NEТ
Core
~·
Middle>vare Class
ASP.NEТ
Core
~·
Stortup cl•ss
ASP. NEТ
Core
ASP . NEТ
ASP . NEТ
Core
ASP . NEТ
Core
vГJ
rш
о
Configuration File
Razor P1ge
_Viewlmpoot<.cshtml
5.5.
'"'
Туре:
МVCVi
[ij
~
Рис.
Core
ASP.NET Core
~
№me
MVC View Layout P•ge
;;· , _
~· МVС View St•rt P1ge
~
Online
-----~
Создание файла импортирования представлений
129
Глава 5. Работа с Razor
Visual Studio автоматически назначит файлу имя_Viewimports. cshtml, а
Add (Добавить) приведет к созданию файла, который будет пустым.
Поместим в файл выражение, показанное в листинге 5.6.
Среда
щелчок на кнопке
Листинг
5.6.
Содержимое файла_Viewimports.
cshtml
из папки
Views
@using Razor.Models
Пространства имен, где должен проводиться поиск классов, применяемых в пред­
ставлениях
Razor,
указываются с использованием выражения
следует пространство имен. В листинге
Razor. Models,
5.6
@u s i n g,
содержащего класс модели в примере приложения.
Теперь, когда пространство имен
Razor. Models
включено в файл импортирова­
ния представлений, можно удалить пространство имен из файла
тинг
за которым
добавлена запись для пространства имен
Index. cshtml
(лис­
5.7).
Листинг
5. 7.
Пропуск пространства имен с классом модели в файле
из папки
Index. csh tml
Views/Home
@model Product
@{
Layout
=
null;
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
@Model.Name
</body>
</html>
Совет. Выражение
@using
можно также добавлять в отдельные файлы представлений, что
позволит применять внутри них типы без указания пространства имен.
Работа с компоновками
Ниже приведено еще одно важное выражение
Razor
из файла представления
Index. cshtml:
@{
Layout
null;
Это пример блока кода
Razor,
который позволяет включать в представление опе­
раторы С#. Блок кода открывается посредством @{ и закрывается с помощью
содержащиеся в нем операторы оцениваются при визуализации представления.
},
а
130
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Показанный выше блок кода устанавливает значение свойства
Представления
Razor
La you t в n u 11 .
MVC. а в ба­
Layout . В главе 21 объ­
компилируются в классы С# внутри приложения
зовом классе, который они используют. определено свойство
ясняется, как все это работает, а пока достаточно знать, что результатом установки
свойства
Layout
в
null
является сообщение инфраструктуре
MVC
о том , что наше
представление является самодостаточным и будет визуализировать все свое содержи­
мое, которое необходимо возвратить клиенту.
Самодостаточные представления хороши для простых примеров приложений. но
реальный проект может включать десятки представлений. и некоторые из них будут
иметь общее содержимое. Дублированное общее содержимое в представлениях стано­
вится трудным в управлении. особенно если нужно внести изменение и отследить все
представления, подлежащие модификации.
Более эффективный подход предусматривает использование компоновки
Razor,
яв­
ляющейся шаблоном, который хранит общее содержимое и может быть применен к
одному или большему числу представлений. Когда вы вносите изменение в компонов­
ку. оно автоматически воздействует на все представления, которые ее используют.
Создание КОМПОНОВКИ
Компоновки обычно совместно используются представлениями. применяемыми
множеством контроллеров, и хранятся в папке по имени
входит в перечень местоположений, просматриваемых
Чтобы создать компоновку, создадим папку
Views / S h ared ,
которая
Razor
в попытках найти файл.
Vi e ws/Share d.
щелкнем на ней правой
кнопкой мыши и выберем в контекстном меню пункт
Add9New ltem (Добавить9Новый
MVC View Layout Page (Страница компоновки представле­
ASP.NET Core9Web и введем_ BasicLayou t . csh tml в качес­
(рис. 5.6). Щелкнем на кнопке Add (Добавить) для создания файла.
элемент). Укажем шаблон
ний
MVC)
в категории
тве имени файла
(Подобно файлам импортирования представлений имена файлов компоновок начина­
ются с символа подчеркивания.)
_. lnstalled
_. ASP.NET Core
Cod•
General
ASP.NEi Core
~·
W•b АР! Controllor Class
ASP.NEi Cor•
&i" MVC
General
View Р •9•
~ : MVC Vi•w layout Pag•
r;,,s,•
~
MVC View Start Page
Scripts
Content
Online
Name:
MVC Controll•r Class
~
_. Web
ASP.NEi
~
~·
~
5.6.
Coro
ASP.NE.Т Cor•
дSP.NEi
Core
MVC Viow lmports Page
ASP.NEТ
Core
Razor Т•9 H•lper
ASP.NEТ
Core
Middleware Class
ASP.NEТ
Core
_Basicl•yout.cshtml
Рис.
ASP.NEТ
Создание компоновки
Глава 5. Работа с Razor
В листинге
131
5.8 показано начальное содержимое файла_BasicLayout. cshtml, до­
Visual Studio при создании файла.
бавленное средой
Листинг
Начальное содержимое файла
5.8.
из папки
_ BasicLayout. cshtml
Views/Shared
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
</head>
<body>
<div>
@RenderBody ()
</div>
</body>
</html>
-
Компоновки
это специализированная форма представлений; в листинге
присутствуют два выражения@. Вызов метода
@RenderBody ()
5.8
вставляет в разметку
компоновки содержимое представления, указанное с помощью метода действия:
<div>
@RenderBody ()
</div>
Второе выражение
ViewBag. Ti tle
Razor
в компоновке
обращается
для установки содержимого элемента
к свойству по имени
title:
<title>@ViewBag.Title</title>
Объект
ViewBag -
удобное средство, которое позволяет передавать значения
данных внутри приложения (в рассматриваемом случае между представлением и
его компоновкой). Вы увидите, как он работает, когда компоновка будет применена к
представлению.
Элементы НТМL в компоновке будут применены к любому представлению, которое
использует компоновку, предоставляя шаблон для определения общего содержимого.
В листинге
5.9
к компоновке добавлена простая разметка. чтобы ее эффект как шаб­
лона был очевидным.
Листинг
5.9.
Добавление содержимого в файл _ВasicLayout.cshtml
из папки
Views/Shared
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
<style>
132
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
#mainDiv {
padding: 20рх;
border: solid medium
font-size: 20pt
Ыасk;
</style>
</head>
<body>
<hl>Product Information</hl>
<div id="mainDiv">
@RenderBody ()
</div>
</body>
</html>
Здесь был добавлен элемент заголовка, а также стиль
держимого элемента
di v,
ради прояснения того,
в котором находится выражение
CSS
для стилизации со­
@RenderBody (),
какое содержимое поступает из компоновки,
просто
а какое
-
из
представления.
Применение компоновки
Чтобы применить компоновку к представлению, понадобится установить значение
свойства
Layout
и удалить НТМL-разметку, которая теперь будет предоставляться
компоновкой, такую как элементы
Листинг
h tml, head
и
body
(листинг
5.10).
5.1 О. Применение компоновки в файле Index. cshtml из папки Views/Home
@model Product
@(
Layout = 11 _BasicLayout 11 ;
ViewBag. Т i tle = 11 Product Name 11
;
Product Name: @Model, Name
Свойство
Layout
указывает имя файла компоновки, который будет использовать­
ся для представления, без расширения
. cshtml. Механизм Razor будет искать ука­
/Views/Home и Views/Shared.
Кроме того, установлено свойство ViewBag. т i tle, которое будет применять­
компоновкой для определения содержимого элемента ti tle при визуализации
занный файл компоновки в папках
ся
представления.
Трансформация представления значительна даже для такого простого приложе­
ния. Компоновка располагает всей структурой, требуемой для любого НТМL-ответа,
оставляя представлению только заботу о динамическом содержимом, которое отоб­
ражает данные пользователю.
Index. csh tml, она
ответа (рис. 5. 7).
Когда инфраструктура
MVC
обрабатывает файл
применяет компоновку для создания унифицированного НТМL­
Глава 5. Работа с Razor
D Product Name
С
133
)(
<D localhost·60753
P1·oduct Information
P1·oduct
_____
Nаше: Kayak
1
.____
,
_,J
Рис.
5.7.
Результат применения компоновки к представлению
Использование файла запуска представления
Осталась еще одна мелкая шероховато сть. которую необходимо устранить
-
мы должны указывать файл компоновки для применения в каждом представлении .
Следовательно. если файл компоновки переименовывается, тогда придется отыскать
все ссылающиеся на него представления и внести необходимое изменение , что будет
процессом, чреватым ошибками, и противоречит лейтмотиву, который пронизывает
всю разработку приложений
легкости сопровождения.
MVC -
Решить упомянутую проблему можно с использованием файла запуска представ·
ления. При визуализации представления инфр аструктура
ViewStart. cshtml.
MVC ищет ф айл по имени
Содержимое этого файла будет трактоваться так, как если бы
оно присутствовало внутри самого файла представления. и данное средство можно
применять для автоматической установки свойства
Layo ut.
Чтобы создать файл запуска представления. щелкнем правой кнопкой мыши на
папке
Views.
выберем в контекстном меню пункт
элемент) и укажем шаблон
категории
MVC View Start Page
ASP.NET CoreqWeb (рис. 5.8).
"
ASP.Nrт
Core
Code
General
• Wd>
AddQNew ltem
r:f.'
L\i::J
MVC V1ew L<yout P•ge
~
MVC View St•rt Ра~~
General
~·
"
MVC View lmports P•g•
ASP.NET Core
Scripts
Content
~
~-
R11zorTag Helper
ASP.NEТ
~!>;:
Middleware Clas.s
ASP .NЕТ Сore
ASP.NП
t> Online
Рис.
5.8.
ASP.NET Core
, : ,
дSР.NЕТ Core
Core
~
~· St•rtup сlш
!>;:
ASP.NEТCore
vfJ
ASP.NEТ
Core
ASP.NEТ
Core
[;§'
(ДобавитьqНовый
(Файл запуска представления
ASP.NП
Configur•t1on File
Razor Page
Создание файла запуска представления
~
(
MVC) в
134
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Среда
Visual Studio
щелчок на кнопке
Add
автоматически назначит файлу имя
мым, приведенным в листинге
Листинг
5.11.
ViewStart. cshtml;
(Добавить) приведет к созданию файла с начальным содержи­
5. 11.
Начальное содержимое файла
Viewstart. cshtml
из папки
Views
@{
Layout
" Layout";
Дт:Iя применения компоновки ко всем представлениям в приложении понадобится
изменить значение, присваиваемое свойству
Листинг
5.12.
Layout
(листинг
5.12).
Применение стандартного представления
в файле_ViewStart.
cshtml
из папки
Views
@{
Layout
"_BasicLayout";
Поскольку файл запуска представления содержит значение для свойства
можно удалить соответствующее выражение из файла
Листинг
5.1 З.
Index. cshtml
Применение файла запуска представления в файле
из папки
Layout,
5.13).
(листинг
Index. cshtml
Views/Home
@model Product
@{
ViewBag.Title = "Product Name";
Product Name: @Model.Name
Специально указывать, что должен применяться файл запуска представления, не
требуется. Инфраструктура
MVC
автоматически обнаруживает данный файл и ис­
пользует его содержимое. Преимущество отдается значениям, определяемым в фай­
ле представления, что делает переопределение файла запуска представления очень
простым.
Можно также применять несколько файлов запуска представлений, чтобы устано­
вить стандартное поведение для разных частей приложения. Механизм
Razor
ищет
самый близкий файл запуска для обрабатываемого представления, а :это значит, что
стандартные настройки можно переопределять, добавляя файл запуска представле­
ния в папку
Views/Home
или
Views/Shared.
Внимание! Важно понимать разницу между отсутствием свойства
ставления и его установкой в
null.
вы не хотите применять компоновку, тогда установите свойство
просто опустить свойство
Layout,
Layou t
в файле пред­
Если представление является самодостаточным, и
то инфраструктура
MVC
Layout
в
null.
Если же
будет считать, что компоновка
вам необходима, и она должна использовать значение, которое найдет в файле запуска
представления.
Глава 5. Работа с Razor
Использование выражений
135
Razor
Теперь, когда вы ознакомились с основами представлений и компоновок, давай­
те переключимся на другие виды выражений, поддерживаемые
Razor,
и посмотрим,
как их применять для создания содержимого представлений. В хорошем приложении
MVC
имеется четкое разделение между ролями метода действия и представления.
Роли просты и кратко описаны в табл.
Таблица
5.3.
5.3.
Роли, исполняемые методом действия и представлением
Чего не делает
Компонент
Что делает
Метод действия
Передает представлению объект
Не передает представлению
модели представления
сформатированные данные
Представление
Использует объект модели пред­
Не изменяет ни одного аспекта в
ставления для отображения со­
объекте модели представления
держимого пользователю
Далее в книге мы будем неоднократно возвращаться к данной теме. Чтобы измечь
максимум из МVС, необходимо соблюдать и обеспечивать разделение между разными
частями приложения. Как будет показано, механизм
Razor
позволяет делать очень мно­
гое, включая применение операторов С#, но вы не должны использовать
Razor для
вьmол­
нения бизнес-логики или манипулирования объектами моделей предметной области. В
листинге
Листинг
5.14 демонстрируется
5.14.
добамение нового выражения в предстамение
Добавление выражения в файле
Index.
Index. cshtml из папки Views/Home
@model Product
@{
ViewBag.Title
=
"Product Name";
<p>Product Name: @Model.Name</p>
<p>Product Price: @($ 11 {Model. Price: С2} ") </р>
Значение свойства
Price
можно было бы сформатировать в методе действия и пе­
редать его представлению. Такой подход работает, но разрушает преимущество шаб­
лона
MVC
и сокращает возможность реагировать на изменения в будущем. Как уже
упоминалось, позже мы еще возвратимся к этой теме, но вы должны запомнить, что
инфраструктура
MVC
ASP.NET Core MVC
не принуждает надлежаще применять паттерн
и нужно учитывать влияние решений, принимаемых во время проектирования
и написания кода.
Обработка или форматирование данных
Важно отличать обработку данных от их форматирования. Представления форматируют
данные, поэтому в предыдущем разделе представлению передается объект
Product,
а
не отображаемая строка с форматированными свойствами объекта. За обработку данных,
включая выбор объектов данных для отображения, отвечает контроллер, который будет об­
ращаться к модели с целью получения и модификации требующихся данных. Иногда трудно
понять, где проходит черта между обработкой и форматированием, но в качестве эмпири­
ческого правила рекомендуется занять осторожную позицию и выносить из представления
в контроллер все кроме простейших выражений.
136
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Вставка значений данных
Самое простое, что можно делать с помощью выражения
Razor -
вставлять зна­
чения данных в разметку. Наиболее распространенный способ пре.цусматривает ис­
пользование выражения
@Model.
Представление
Index
уже содержит примеры при­
менения такого подхода вроде показанного ниже:
<p>Product Name: @Model.Name</p>
Вставлять значения можно таюке с использованием объекта
рый применялся для установки содержимого элемента
ViewBag
ViewBag.
кото­
в компоновке. Объект
можно использовать для передачи данных из контроллера в представление,
дополняющее модель (листинг
Листинг
ti tle
5.15.
Применение
из папки
5.15).
ViewBag
в файле
HomeController. cs
Controllers
using Microsoft.AspNetCore.Mvc;
using Razor.Models;
namespace Razor.Controllers
puЫic class HomeController : Controller
ViewResult Index() {
Product myProduct = new Product {
ProductID = 1,
Name = "Kayak",
Description = "А boat for one person",
Category = "Watersports",
Price = 275М
puЬlic
};
ViewBag. StockLevel
= 2;
return View(myProduct);
Свойство
ViewBag
возвращает динамический объект, который может использо­
ваться для определения произвольных свойств. В листинге
установлено в
2
свойство по имени
StockLevel.
5.15
бьmо определено и
Так как свойство
ViewBag
является
динамическим. объявлять имена свойств заранее необязательно, но это означает, что
для свойств
ViewBag
среда
Visual Studio
не в состоянии предоставлять предполагае­
мые варианты автозавершения.
Знание того. когда применять
ViewBag,
а когда расширять модель
-
вопрос опы­
та и сложившихся предпочтений. Мой персональный стиль заключается в том, что­
бы использовать объект
ViewBag
только для предоставления визуальных подсказок
о способе визуализации данных и не применять его для значений данных, которые
отображаются пользователю. Но это просто то, что подходит лично мне. Если вы хо­
тите использовать
ViewBag
для данных, отображаемых пользователю, тогда обращай­
тесь к значениям с помощью выражения
@ViewBag
(листинг
5.16).
Глава 5. Работа с Razor
Листинг
5.16.
Отображение значения
из папки
137
ViewBag в файле Index. csh tml
Views/Home
@mo del Pr odu c t
@{
ViewBa g . Ti t le
"Pr od uct Name";
=
<p> Produc t Name : @Model.Name</p>
<p> Produc t Pr ic e: @($"{Model.Pri ce:C2 }")</p>
<p>Stock Level : @ViewBag . StockLevel</p>
Результат можно видеть на рис.
)(
D ProdU<! ~•m•
С
5.9.
Ф localhost бD?S'
Product Information
Prodttct Naine: Kayak
Pгoduct
P1·ice: ±:275.00
Stock Level: 2
Рис.
5.9.
Применение выражений
Razor
_J
для вставки значений данных
Установка значений атрибутов
Во всех рассмотренных до сих пор примерах устанавливалось содержимое элемен­
тов. но выражения
Razor
можно использовать также для установки з начений
бurnoв элемента . В листинге
@ViewBa g
Листинг
5.17
для установки содержимого атрибутов в эл е ментах
5.17.
Установка значений атрибутов в файле
из папки
ampu@Mod el и
представления Index .
демонстрируется применение выражений
Index. csh tml
Views/Home
@mo del Pr oduct
@{
ViewBag.Tit l e = " Pr oduct Name ";
<div data-productid="@Мodel.ProductID" data-stocklevel="@Viewвag.StockLevel">
<p>Product Name: @Mode l . Name</p >
<p> Pr oduc t Pr i ce : @($ "{Mode l .Pri ce : C2} " )</p>
<p>Stoc k Level : @ViewBag . St oc kLeve l </p>
</div>
138
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Выражения
Razor
данных в элементе
применяются для установки значений некоторых атрибутов
di v.
Совет. Атрибуты данных, которые представляют собой атрибуты с именами, начинающимися
на
da t.a - , в течение многих лет были неформальным способом создания специальных
HTML5. Чаще всего они ис­
пользуются так, что код JavaScript может находить специфические элементы, или так, что
стили CSS могут применяться более ограниченным образом.
атрибутов и затем сделаны частью формального стандарта
Если запустить пример приложения и просмотреть НТМL-разметку, отправленную
браузеру, то легко увидеть, как механизм
Razor
установил значения атрибутов:
<div data-productid="l" data-stocklevel="2">
<p>Product Name: Kayak</p>
<p>Product Price: $275.00</р>
<p>Stock Level: 120</р>
</div>
Использование условных операторов
Механизм
Razor
способен обрабатывать условные операторы, что означает воз­
можность настройки вывода из представления на основе значений данных представ­
ления. Такой прием является наиболее важной частью
сложные
и
изменчивые
компоновки.
которые
понимании и сопровождении. В листинге
представления
Листинг
Index,
5. 18
Razor
по-прежнему
и позволяет создавать
остаются
простыми
в
приведено обновленное содержимое
включающее условный оператор.
5.18. Применение условного оператора Razor в файле Index. csh tml
из папки Views/Home
@model Product
@{
ViewBag.Title = "Product Name";
<div data-productid="@Model.ProductID" data-stocklevel="@ViewBag.StockLevel">
<p>Product Name: @Model.Name</p>
<p>Product Price: @($"{Model.Price:C2}")</p>
<p>Stock Level:
@switch (ViewBag.StockLevel)
case О:
@: Out of Stock
break;
case 1:
case 2:
case 3:
<b>Low Stock (@ViewBag.StockLevel)</b>
break;
default:
@: @ViewBag.StockLevel in Stock
break;
</р>
</div>
Глава 5. Работа с Razor
139
Чтобы начать условный оператор. нужно поместить символ @ перед условным клю­
чевым словом С#
(swi tch в
текущем примере). Блок кода завершается закрывающей
фигурной скобкой (})подобно обычному блоку кода С#.
Внутри блока кода
Razor в
вывод представления можно включать НТМL-элементы
и значения данных, просто определяя НТМL-разметку и выражения
Razor:
<b>Low Stock (@ViewBag.StockLevel)</b>
Помещать элементы или выражения в кавычки либо отмечать их каким-то спе­
циальным образом не требуется
-
механизм
Razor
будет интерпретировать это как
вывод, подлежащий обработке. Тем не менее, если нужно вставить в представление
литеральный текст. когда он не содержится в НТМL-элементе, тогда о данном факте
понадобится сообщить
@:
Razor.
добавив к строке префикс:
Out of Stock
Символы @: предотвращают обработку механизмом
Razor
строки как оператора
С#, что является стандартным поведением в отношении текста. Результат выполне­
ния такого условного оператора показан на рис.
С
5.10.
-
Ф localhost:60753
Product Information
Ргоdнсt Nаше:
Ргоdнсt
Kayak
Price: f.275.00
Stock Level: Lo\-v Stock (2)
L_.____________.
Рис.
5.1 О.
Применение оператора
sw i tch
в представлении
Условные операторы играют важную роль в представлениях
Razor
Razor.
поскольку они
позволяют варьировать содержимое на основе значений данных, которые представ­
ление получает от метода действия. В качестве дополнительной демонстрации в лис ­
тинге
5.19
приведено представление
I ndex. csh tml
с добавленным оператором
еще одним распространенным условным оператором.
i f -
140
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Листинг
5.19.
Использование оператора
if в файле Index.cshtml из папки Views/Home
@model Product
@{
ViewBag.Title = "Product Name";
<div data-productid="@Model.ProductID"
data-stocklevel="@ViewBag.StockLevel">
<p>Product Name: @Model.Name</p>
<p>Product Price: @($"{Model.Price:C2}")</p>
<p>Stock Level:
@if (ViewBaq.StockLevel == О) {
@: Out of Stock
else if (ViewBaq. StockLevel > О && ViewBaq. StockLevel <== 3) {
<b>Low Stock (@ViewBaq.StockLevel)</b>
else {
@: @ViewBaq.StockLevel in Stock
</р>
</div>
Условный оператор
if
выдает те же самые результаты, что и оператор
switch,
но
мы просто хотели продемонстрировать применение условных операторов С# в пред­
ставлениях
Razor. Дополнительные
21, где представления
найти в главе
сведения о работе условных операторов можно
рассматриваются более подробно.
Проход по содержимому массивов и коллекций
При разработке приложения
MVC
часто необходимо выполнять проход по содер­
жимому массива или другой разновидности коллекции объектов с генерацией подроб­
ной информации для каждого объекта. Чтобы продемонстрировать. как это делается.
в листинге
Home,
5.20
показан модифицированный метод действия
Index () в контроллере
Product.
который теперь передает представлению массив объектов
Листинг
5.20.
Использование массива в файле
из папки
HomeController. cs
Controllers
using Microsoft.AspNetCore.Mvc;
using Razor.Models;
namespace Razor.Controllers {
puЫic class HomeController : Controller
IActionResult
Product[] array
new Product {Name
new Product {Name
new Product {Name
new Product {Name
puЬlic
={
};
return View(array);
Index() {
=
=
=
=
"Kayak", Price = 275М),
"Lifejacket", Price = 48.95М},
"Soccer ball", Price = 19. SOM},
"Corner flaq", Price = 34. 95М}
Глава 5. Работа с Razor
Метод действия
Inde x ( )
создает объект
значения данных, и передает его методу
стандартного представления. В листинге
I ndex
и с помощью цикла
Листинг
5.21.
fo r e ach
Pr oduc t [].
View ()
5.21
141
который содержит простые
для визуализации с применением
изменен тип модели для представления
производится проход по объектам в массиве.
Проход по массиву в файле Index. cshtml из папки Views/Home
@model Product[]
@{
Vi ewBag. Tit l e
"Product Name";
<tаЫе>
<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>
</tаЫе>
Оператор
@fo reach
выполняет проход по содержимому массива объектов моде­
ли и генерирует для каждого элемента массива строку в таблице. Как видите, в цик­
ле
f oreach
создается локальная переменная по имени р, на свойства которой про­
изводится ссылка с использованием выражений
Результат приведен на рис.
D Product №mo
С
,Ф
Razor
вида @р .
Na me
5.11.
х
localhost 60753
P1·oduct Information
Name
Price
Kayak
f275.00
Lifejacket f48.95
Sоссег ball fl9.50
Согпег flag f34.95
Рис.
5.11.
Применение
Razor
для прохода по массиву
и @ р.
Pr i c e .
142
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Резюме
В главе был предложен обзор механизма визуализации
Razor
и показано. как его
использовать для генерации НТМL-разметки. Мы выяснили, каким образом ссылать­
ся на данные, передаваемые из контроллера, через объект модели представления и
объект
ViewBag,
а также продемонстрировали применение выражений
Razor
для
подстройки ответов пользователю на основе значений данных. В оставшихся главах
книги вы увидите много разных примеров использования
Razor.
а в главе
21 найдете
MVC. В следую­
предлагаемые Vlsual Studio для работы
подробное обсуждение функционирования механизма визуализации
щей главе будут описаны некоторые средства.
с проектами
ASP.NET Core
МVС.
ГЛАВА
6
Работа с
Visual Studio
настоящей главе рассматриваются основные средства, предоставляемые Visual
в Studio
6.1
ASP.NET Core MVC.
В табл.
для разработки проектов
приведена свод­
ка по главе.
Таблица
6. 1.
Сводка по главе
Листинг
Задача
Решение
Добавление пакетов к проекту
Используйте инструмент
пакетов
.NET
и
Bower для
NuGet для
6.6-6.8
пакетов
клиентской стороны
Просмотр результатов изменения
Применяйте модель итеративной
представления или класса
разработки
Отображение подробных сообщений
в браузере
разработчика
Получение детальной информации и
Применяйте отладчик
6.9-6.11
Используйте страницы исключений
6.12
6.13
контроля над выполнением приложения
Повторная загрузка одного или боль­
Используйте средство
шего числа браузеров с применением
(Ссылка на браузер)
Browser
Liпk
6.14,6.15
Visual Studio
Сокращение количества НТТР-запросов
Применяйте расширение Buпdler
и ширины полосы пропускания, требуе­
Minifier
мой для файлов
JavaScript
и
&
6.16-6.23
(Упаковщик и минификатор)
CSS
Подготовка проекта для примера
Для целей текущей главы создадим новый проект по имени Wо r k i n gW i t h
VisualStudio типа ASP.NET Соге Web Application (Веб-приложение ASP.NET Core) из
группы .NET Core, используя шаблон Empty (Пустой). Включим инфраструктуру MVC
с ее стандартной конфигурацией в классе Startup (листинг 6.1).
Листинг
6.1.
Включение инфраструктуры
из папки
using
using
using
using
MVC
в файле
WorkingWi thVisualStudio
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Startup. cs
144
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
using
using
using
using
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
namespace WorkingWithVisualStudio {
puЫic class Startup {
puЫic
void ConfigureServices(IServiceCollection services) {
services.AddМvc();
}
puЬlic
void Configure(IApplicationBuilder
арр,
IHostingEnvironment env) {
app.UseМvcWithDefaultRoute();
}
Создание модели
Создадим папку
Models
и добавим в нее файл класса по имени
ределением, показанным в листинге
Листинг
6.2.
Содержимое файла
Product. cs
с оп­
6.2.
Product. cs
из папки Мodels
namespace WorkingWithVisualStudio.Models
puЬlic
class Product {
string Name { get; set; }
puЫic decimal Price { get; set;
puЬlic
Чтобы создать простое хранилище объектов
файл класса по имени
веденное в листинге
Листинг
6.3.
SimpleRepository.cs
Product,
добавим в папку
Models
и поместим в него определение, при­
6.3.
Содержимое файла
SimpleReposi tory. cs
из папки Мodels
using System.Collections.Generic;
namespace WorkingWithVisualStudio.Models
puЬlic class SimpleRepository {
private static SimpleRepository sharedRepository
= new SimpleRepository();
private Dictionary<string, Product> products
= new Dictionary<string, Product>();
puЬlic
puЫic
static SimpleRepository SharedRepository => sharedRepository;
SimpleRepository() {
var initialitems = new[] {
new Product { Name
"Kayak", Price = 27 SM } ,
new Product { Name
"Lifejacket", Price = 48.95М ),
new Product { Name
"Soccer ball", Price
19. SOM } ,
new Product { Name
"Corner flag", Price = 34.95М 1
1;
Глава 6. Работа с Visual Studio
145
foreach (var р in initialitems) {
AddProduct(p);
puЫic
puЬlic
Класс
IEnumeraЫe<Product> Products => products.Values;
void AddProduct(Product р) => products.Add(p.Name,
SimpleRepository хранит
в модель изменения утрачиваются,
р);
объекты модели в памяти, т.е. любые внесенные
когда приложение останавливается или переза­
пускается. Для примеров, приводимых в настоящей главе, непостоянного хранилища
вполне достаточно, но такой подход не может применяться во многих реальных про­
ектах; в главе
8
рассматривается пример создания хранилища, которое запоминает
объекты модели на постоянной основе, используя реляционную базу данных.
На заметку! В листинге
6.3 определено статическое свойство по имени SharedReposi tory,
SimpleReposi tory, который может
предоставляющее доступ к единственному объекту
применяться повсюду в приложении. Это не является установившейся практикой, но я
хочу продемонстрировать распространенную проблему, с которой вы столкнетесь при
разработке приложений
MVC; в главе 18 будет описан более эффективный способ работы
с разделяемыми компонентами.
Создание контроллера и представления
Добавим в проект папку
HomeController. cs,
Листинг
6.4.
и поместим в нее файл класса по имени
Controllers
в котором определен контроллер, показанный в листинге
Содержимое файла
HomeController. cs
из папки
6.4.
Controllers
using Microsoft.AspNetCore.Mvc;
using WorkingWithVisualStudio.Models;
namespace WorkingWithVisualStudio.Controllers
puЫic class HomeController : Controller {
puЫic IActionResult Index()
=> View(SimpleRepository.SharedRepository.Products);
Здесь имеется единственный метод действия
екты модели и передает их методу
View ()
Index
(),который получает все объ­
для визуализации стандартного представ­
ления. Чтобы добавить стандартное представление, создадим папку
поместим в нее файл представления по имени
приведено в листинге
Index. csh tml,
6.5.
Листинг
6.5.
@model
IEnumeraЬle<WorkingWithVisualStudio.Models.Product>
Содержимое файла
@{ Layout = null;
<!DOCTYPE html>
Index. cshtml
из папки
Views/Home
и
содержимое которого
Views/Home
146
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
<html >
<head>
<meta name ="viewport" co ntent="width =device-width" />
<t itle>Working with Vi s ual Studi o</ title>
</ head >
<b o dy>
<t аЫе>
<thead >
<tr> <td >Name</td ><t d >Price</td >< /tr>
</thead >
<tb o dy >
@f o r eac h (var р i n Model )
<tr >
<td >@p.Name</td >
<td >@p.Price</td>
</tr >
</ tb ody >
< /tаЫе>
< /body>
</html>
Представление включает таблицу . в которой с помощью Rаzоr-цикла
f o rea c h
со­
здаются строки для всех объектов модели, причем каждая строка содержит ячейки
для значений свойств
Name
и
Price.
6. 1.
После запуска примера приложения отобразятся
результаты, показанные на рис.
С [G
-i _-l_o_c_a-lh_o_s_t_:64
- 405 _ -==--=-=-=--===:-=::::::::-=====
Name
Kayak
Lifejacket
Soccerball
Comer flag
Price
275
48.95
19.50
34.95
Рис.
6.1.
Выполнение примера приложения
Управление программными пакетами
Проектам
ASP.NET Core
МVС требуются два разных вида программных пакетов.
Такие типы пакетов будут описаны в последующих разделах вместе с инструментами.
которые среда
Visual Studio
предлагает для управления ими.
Глава 6. Работа с Visual Studio
Инструмент
Среда
кетами
NuGet
Visual Studio
.NET.
147
предоставляет графический инструмент для управления па­
включаемыми в проект. Чтобы открыть инструмент
NuGet, выберем в
Tools<:>NuGet Package Maпager (Сервисе=> Диспетчер пакетов NuGet) пункт Мапаgе
NuGet Packages for Solutioп (Управление пакетами NuGet для решения) . Инструмент
NuGet откроется и отобразит список пакетов, которые уже установлены (рис. 6 .2).
меню
lnstalled
Browse
Updates
Consolidate
Р
Sear<h (Ct,f+E)
"G
Manage Packages for Solution
О lncludt prereleilst
Package sourc~ nuget.org · - · - ·
~
Microso~.AspNetcore.All
•
Vtrsion(s) · 1
•
Q
v2.0.0
Microsoft . NEТCore . App Ьу м;crosoft
А
Proj<ct
О Worl<:ingWrthVisu1tStudio
s.tt of .NЕТ APl 's that ,щ~ included in tht def•ult .N ЕТ Cort •pplic"•tion modtl.
е8Ь88б 1ас 71•f042c8711<2f9f 2d04c98b69f 28d
lnst.tffed: 2.0.О
tin r~ t.il
Vcrsion: 1Uttst staЫt 2.0.О
~"~
9 °",.,.,,
Each ~ck•3t is licмsed to you Ьу its owntr. NuGtt 1s not t6pons1ble for, nor dots it gr•nt апу
l1censts to, thtrd·p•rty p.1c.kJge.s.
О Оо not show th1s 19lin
Deкrtption
Рис.
6.2.
Использование диспетчера пакетов
NuGet
На вкладке lпstalled (Установленные) приводится сводка по пакетам. уже установ­
ленным в проекте. Вкладку
Browse
тановки новых пакетов, а вкладку
(Обзор) можно применять для нахождения и ус ­
Updates
(Обновления)
-
чтобы получить список
пакетов, для которых были выпущены свежие версии .
Пакет Мicrosoft . AspNetCore
Если вы работали с более ранними версиями
ASP.NET Core,
мости добавления в новый проект длинного списка пакетов
.All
тогда вам известно о необходи­
NuGet.
В версии
ASP.NET Core 2
принят другой подход , который опирается на единственный пакет по имени
Mi c r o s oft .
Asp Net Core . Al l.
Пакет
Mi c r o s of t. Asp NetCo re. Al l
индивидуальные пакеты
турой
MVC,
NuGet,
представляет собой мета-пакет, содержащий все
которые требуются платформой ASP.NEТ
Core
и инфраструк­
т.е. добавлять пакеты по одному не придется . При опубликовании приложения
любые пакеты, которые входят в состав мета-пакета, но в приложении не используются,
будут удалены, гарантируя тем самым , что лишние пакеты развертываться не будут.
148
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Список и местоположение пакетов
Инструмент
NuGet
NuGet отслеживает пакеты
проекта в файле <имя
проекта>.
csproj.
В текущем примере приложения это означает, что детали. касающиеся пакетов
NuGet.
хранятся в файле по имени
не отображает файл
WorkingWithVisualStudio. csproj. Среда Vlsua\ Studlo
. cspro j в окне Solution Explorer. Чтобы отредактировать данный
файл, щелкнем правой кнопкой мыши на имени проекта в окне
Solution Explorer и
Edit WorkingWithVisualStudio.csproj (Редактировать
WorkingWi thVisualStudio. csproj). Среда Visua\ Studio откроет файл для редак­
тирования. Файл . csproj имеет формат ХМL и содержит элемент вроде показанного
ниже. который добавляет к проекту мета-пакет ASP.NET Core:
выберем в контекстном меню пункт
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" / >
</ItemGroup>
Пакет указывается с помощью своего имени и номера требуемой версии. Хотя мета­
пакет включает все функциональные средства. требующиеся для
ASP.NET Core МVС.
вам
по-прежнему понадобится добавлять пакеты в проект. чтобы появилась возможность
применения дополнительных средств. Пакеты можно добавлять с использованием гра­
фического интерфейса, показанного на рис.
6.2,
мандной строки . Кроме того, файл
можно редактировать и среда
. cspr o j
либо с применением инструментов ко­
Visual Studio
обнаружит изменения, после чего загрузит и установит добавленные пакеты.
Когда для добавления в проект какого-то пакета используется инструмент
NuGet,
пакет автоматически устанавливается вместе со всеми пакетами, от которых он за­
висит. Исследовать пакеты
NuGet и их зависимости можно, раскрыв в окне Solution
DependenciesqNuGet (ЗависимостиqNuGеt), что приведет к отображе­
нию всех пакетов в файле . csproj и их зависимостей. Мета-пакет ASP.NET Core со­
держит большое количество зависимостей, часть которых видна на рис. 6.3.
Explorer
узел
Solution Explorer
G\ а"· 1 'ф
Se~r<h
~
•
l\\i1
Г® ... в
Solut1on Explorer (Ctrl+;)
l:jj Solution 'WorkingWithVisualStudio' (1 project)
~
"
q,
.~-.·
1>
"
Connected Seivices
Dependencies
'(у-.: Analyzers
·~ NuGet
" ·~ Microsoft.AspNetCore.All (2.0.0)
1> ·~ Microsoft.AspNetCore (2.0.0)
1> ·~ Microsoft.AspNetCore.Antiforgery (2.0.0)
1> ·~ Microsoft.AspNetCore.Applicationlnsights.HostingStartup (2.0.0)
~ ·~ Microsoft.AspNetCore.Authentication (2.0.0)
1> ·~ Microsoft.AspNetCore.Authentication.Abstractions (2.0.0)
1> ·~ Microsoft.AspNetCore.Authentication.Cookies (2.0.0)
1> ·~ Microsoft.AspNetCore.Authentication.Core (2.0.0)
1> ·~ Microsoft.AspNetCore.Authentication.Face ook (2.0.0)
•о
Рис.
6.3.
Узел
~pNe f•ще
""tjr.
DependenciesqNuGet
·~.
в окне
Solution Explorer
Глава 6. Работа с Visual Studio
Инструмент
149
Bower
Пакет клиентской стороны включает содержимое , отправляемое кл иенту, в чис­
ле которого файлы
таблицы стилей
JavaScript.
CSS
либо изображения . Для управле ­
NuGet, но инфраструктура
ASP.NET Соге MVC теперь полагается на инструмент под названием Bower. Инструмент
с открытым кодом Bower был разработан за пределами Microsoft и мира .NET и широ­
ко применялся при разработке неб-приложений, отличных от ASP.NET.
ния пакетами подобного рода также можно задействовать
На заметку! Недавно инструмент
Bower
был объявлен устаревшим. Вы можете видеть пре­
Bower все
Visual Studio. Можно
дупреждения, рекомендующие альтернативные инструменты; тем не менее ,
еще активно сопровождается, а поддержка
ожидать, что в какой-то момент в
Microsoft
Bower
интегрирована в
решат поддерживать другой инструмент для
управления пакетами клиентской стороны, но пока этого не случилось, вы должны про­
должить пользоваться
Список пакетов
Bower.
Bower
b ower. j s o n. Чтобы создать файл bower. j s o n ,
Wo r kingWithVisua lStudio в окне
Solutioп Explorer. выберем в контекстном меню пункт AddqNew ltem (ДобавитьQНовый
элемент) и укажем шаблон элемента Bower Coпfiguratioп File (Файл конфигурации
Bower) из категории ASP.NET Core QWebQGeпeral (ASP.NET СоrеQВебQОбщие) , как по ­
казано на рис . 6.4.
Пакеты
Bower указываются
в файле
щелкнем правой кнопкой мыши на имени проекта
" lnstalled
Core
Code
General
Web
ASP.NET
General
Scripts
Content
А ASP.NEТ
А
~
ASP.NEТ
Configuration File
npm Configuration File
ASP.NET Core
Gulp Configuration File
ASP.NEТ
rJS
rJS
Online
g
6J
J)
ASP.NET Core
Grunt Configuration File
ASP.NET Core
JSON File
ASP.NEТ Соге
JSON Schema File
ASP.NEТ Core
XML File
ASP.NEТ
Name: bower.json
Рис.
6.4.
Core
Создание файла конфигурации
Bower
Core
Ту
150
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Среда
ке
Visual Studio установит bower. j son в кач естве имени
и после щелчка на кноп­
(Добавить) добавит в проект файл со стандартным содержимым (листинг
Add
Листинг
6.6.
Стандартное содержимое файла
bower. j son
6.6).
из папки
WorkingWithVisualStudio
" na me " : " asp . net ",
" private ": t r ue ,
" dependencies ":
В ли стинг е
bower. j son .
демонстрируется добавление п а к ета кли е нтской стороны в файл е
6. 7
что делается включением записи в р аздел
Совет. По адресу
ht t p : //bower.io/search
dependencies.
находится хранилище пакетов
Bower,
где
можно искать пакеты для добавления в свои проекты.
Листинг
6. 7.
Добавление пакетов в файле
bower. j son
из папки
WorkingWithVisualStudio
" na me " : " as p .net
" private ": tr ue ,
"dependencies ": {
11
,
"bootstrap": "3. 3. 7"
Выдел е нная полужирным строка обеспечивает доб авление в пример проект а СSS­
пакета
Bootstrap.
При редактировании файла
bower. j son
среда
Vis ual Studio
6 .5).
пр едло­
жит с писок имен пакетов и перечислит доступные версии пакетов (ри с .
~ \'lorking\AfrthV1sualStudio
-
r:I
Schema: http://json.schemastore.org/ bower
l
8 {
2
1
"name" : "" a sp.n~t" J
"private" : true,
3
4
"dependencies" : {
s
6
7
8
100%
"Ьootstrap" :
t }
'}
!!! ~ The latffi staЫe version of the package
т ЛЗ.3.7
т -3.3.7
•
Рис.
6.5.
Перечисление доступных версий пакетов клиентской стороны
Х
.
+
Глава 6. Работа с Visual Studio
151
bootstrap была 3.3.7.
Visual Studio предлагает три варианта: 3 . 3 . 7, л 3 . 3 . 7
и -3. 3. 7. Номера версий в файле bower. j son могут указываться в виде диапазо­
на разнообразными способами, наиболее полезные из которых описаны в табл. 6.2.
На момент написания главы последней версией пакета
Однако обратите внимание, что
Самый безопасный способ указания пакета предусматривает применение явного но­
мера версии. Это гарантирует, что вы всегда будете работать с определенной версией
до тех пор, пока преднамеренно не обновите файл
bower. j son
для запроса другой
версии.
Таблица
6.2. Распространенные форматы для номеров версий в файле Ьower. j son
Формат
3.3.7
Описание
Выражение номера версии напрямую приведет к установке пакета с точ­
но совпадающим номером версии, например,
*
3.3.7
Использование символа звездочки позволяет инструменту
Bower загру­
жать и устанавливать любую версию пакета
>3.3.7
>=3.3.7
> или >= позволяет инструменту
версию пакета, которая больше либо боль­
Снабжение номера версии префиксом
Bower устанавливать любую
ше или равна заданной версии
<3.3.7
<=3.3.7
< или <= позволяет инструменту
версию пакета, которая меньше либо мень­
Снабжение номера версии префиксом
Bower устанавливать любую
ше или равна заданной версии
-3.3.7
Снабжение номера версии префиксом в виде символа
трументу
Bower устанавливать
-
позволяет инс­
версии, даже если номер уровня исправ­
лений (последнее число в номере версии) не совпадает. Например, ука­
-3. З. 7 позволяет инструменту Bower устанавливать версию 3.3.8
3.3.9 (которая будет исправлена до версии 3.3.7), но не версию 3.4.0
зание
или
(которая будет новым младшим выпуском)
лз.З.7
Снабжение номера версии префиксом в виде символа л позволяет инс­
трументу
Bower устанавливать
версии, даже если номер младшего вы­
пуска (второе число в номере версии) или номер исправления не совпа­
дает. Например, указание л3.
3.3.1, 3.4.0
и
3.5.0,
3. О позволит Bower устанавливать
4.0.0
версии
но не версию
Совет. Для примеров в настоящей книге файл
bower. j son
создавался и редактировал­
ся напрямую. Редактировать файл очень просто, к тому же это способствует получению
предсказуемых результатов при проработке примеров. Среда
ставляет графический инструмент для управления пакетами
крыть, щелкнув правой кнопкой мыши на имени файла
тном меню пункт Мапаgе
Bower Packages
Visual Studio также предо­
Bower, который можно от­
bower. j son
(Управление пакетами
и выбрав в контекс­
Bower).
Visual Studio отслеживает изменения в файле bower. j son и автоматически
Bower для загрузки и установки пакетов. После сохранения
изменений, внесенных в файл согласно листингу 6.7, среда Visual Studio загрузит па­
кет Bootstrap и установит его в папку wwwroot/lib (рис. 6.6).
Среда
задействует инструмент
152
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
...
Solution Explorer
·l fэ·!;@f® I ~--
&.)
Search Solution Explorer (Ctrl+ ;)
~~·~~~~~~
~
NuGet
Р·
lib
Рис.
Подобно
~ х
~
bootstrap
~
jquery
Добавление в проект пакетов клиентской стороны
6.6.
инструмент
Bower управляет
зависимостями пакетов, добавляемых
в проект. Некоторые расширенные функциональные средства пакета
сят от JavaScript-библиoтeкиjQuery. из-за чего на рис .
6.6
Bootstrap
зави­
видны два пакета. Для про­
смотра списка пакетов и их зависимостей необходимо развернуть узел Depeпdencies
(Зависимости) в окне
Solution Explorer
(рис.
6. 7).
Solution Explore.r
"' t:I
Х
· 1 0·~Sil'i№ ! J---
111-
Se3rch Solution Explorer (Ctrl•;)
.~i·
~
Dependencies
Anal)ILers
•.,, NuGet
~
~ SDK
~
'fi~
"
Рис.
6. 7.
1·1
bootstrap (3.З .7)
•·• jquery (З.2.1)
Исследование пакетов клиентской стороны и их зависимостей
Обновление пакета
Bootstrap
В оставшемся материале книге применяется предварительный выпуск инф­
раструктуры
Bootstrap
Bootstrap CSS.
Во время написания книги команда разработчиков
занималась построением версии
Bootstrap 4
выпусков. Выпуски были помечены словом
alpha.
и произвела несколько ранних
но обладали высоким качеством.
достаточным для их использования в примерах данной книги. Находясь перед вы­
бором, применять версию
Bootstrap 3.
4, я
предварительный выпуск Bootstгap
которая скоро выйдет из употребления, или
решил использовать новую версию, несмотря
на то, что имена классов. применяемых для стилизации элементов
HTML.
в финаль­
ном выпуске вполне могут измениться. Это означает, что для получения ожидаемых
результатов от примеров вы должны использовать ту же самую версию
Чтобы обновить пакет
тинг
6.8).
Bootstrap.
изменим номер версии в файле
Bootstrap.
bower. j son (лис­
Глава 6. Работа с Visual Studio
Листинг
6.8.
Изменение версии пакета
из папки
Bootstrap
в файле
153
bower. j son
WorkingWi thVisualStudio
"name": "asp. net",
"private": true,
"dependencies": (
"bootstrap": "4. О. 0-alpha. 6 11
После сохранения изменений, внесенных в файл
загрузит новую версию пакета
bower. j son,
среда
Visual Studio
Bootstrap.
Итеративная разработка
Разработка веб-приложений часто может быть итеративным процессом, когда вы
вносите небольшие изменения в представления или классы и запускаете приложение,
чтобы протестировать результат. Инфраструктура МVС и среда
Visual Studio
на пару
поддерживают такой итеративный подход. делая наблюдение за влиянием изменений
быстрым и легким.
Внесение изменений в представления
Razor
Во время разработки изменения, внесенные в представления
Razor.
вступают в
силу, как только поступает НТТР-запрос от браузера. Чтобы продемонстрировать,
каким образом это работает, запустим приложение, выбрав пункт
(Запустить отладку) в меню
Debug
отображающей данные, внесем в файл
листинге
Листинг
Start Debugging
(Отладка), и после открытия вкладки браузера.
Index. cshtml
изменения, приведенные в
6.9.
6.9.
Внесение изменений в файле
Index. cshtml из папки Views/Home
@model IEnumeraЬle<WorkingWithVisualStudio.Models.Product>
@( Layout = null; }
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Working with Visual Studio</title>
</head>
<body>
<hЗ>Products</hЗ>
<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>
154
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
</tbody>
< /tаЫе>
</body>
</h tml>
Сохраним изменения в представлении
I ndex
и перезагрузим текущую веб-стра­
ницу с помощью кнопки обновления в браузере. Изменения в представлении (добав­
ление заголовка и форматирование свойства
Price
объекта модели как денежного
значения) оказывают воздействие и отображаются в браузере (рис.
D Worl:ing with VRu•I Sl\J.
С
6.8).
)(
Lф localhost:52589
P1·odt1cts
Naine
Price
Кауаk
5275.00
Lifejacker 548.95
Socce1· ball 519.50
Соrне1·
flag 534.95
Рис.
6.8.
Результаты внесения изменений в представление
Совет. Процесс подготовки к использованию представлений
Razor
рассматривается в главе
21 .
Внесение изменений в классы С#
Для классов С# , включая контроллеры и модели, способ обработки изменений за­
висит от того. как запускается приложение. В последующих разделах будут показаны
два доступных подхода, которые инициируются посредством разных пунктов в меню
Debug
и кратко описаны в табл.
Таблица б.З. Пункты меню
Пункт меню
6.3.
Debug
Описание
Start Without Debugging
Классы в проекте компилируются автоматически, когда посту­
(Запустить без отладки)
пает НТТР-запрос, делая возможной более динамичную прак­
тику разработки. Приложение запускается без отладчика, что
не позволяет взять под контроль выполнение кода
Start Debugging
При таком стиле разработки потребуется явно компилиро­
(Запустить отладку)
вать проект и перезапускать приложение, чтобы изменения
вступили в силу. Во время выполнения к приложению присо­
единен отладчик, позволяя инспектировать его состояние и
анализировать причины возникновения любых проблем
Глава 6. Работа с Visual Studio
155
Автоматическая компиляция классов
В течение нормальной стадии разработки быстрый итеративный цикл позволяет
видеть результаты изменений немедленно независимо от того, связаны они с добав­
лением нового действия или с изменением способа. которым выбираются данные мо­
дели представления. Для разработки такого вида среда
Visual Studio
поддерживает
обнаружение изменений. как только от браузера получен НТТР-запрос, и автомати­
ческую перекомпиляцию классов. Чтобы посмотреть, как это работает. выберем пункт
Start Without Debugging (Запустить без отладки) в меню Debug (Отладка) среды Visual
Studio. После отображения данных приложения в браузере внесем в контроллер Home
изменения. показанные в листинге 6.1 О.
Листинг
6.10.
Фильтрация данных модели в файле HomeController . cs
из папки Views/Home
u si ng Mi c roso ft.AspNet Co re.Mvc ;
u si ng Wo rkingWithVisualStudio.Mo dels;
using System.Linq;
namespace WorkingWithVisualStudi o .Controllers
puЫic class Ho meController : Con troller {
p u Ыi c
I Ac ti o nResult Index ( )
=> View(SimpleRepository.Shared.Repository.Products
.Where(p => p.Price < 50));
Изменения заключаются в применении
LINQ
для фильтрации объектов
чтобы представлению передавались только те из них , свойство
значение меньше
50. Сохраним
Pri ce
Prod uct,
которых имеет
изменения. произведенные в файле класса контрол ­
лера, и обновим окно браузера. не останавливая или не перезапуская приложение в
Visual Studio.
Отправленный браузером НТТР-запрос инициирует процесс компиля­
ции. и приложение будет запущено повторно с использованием модифицированного
класса контроллера. генерируя результаты, в которых из таблицы исчез товар
(рис.
6.9).
D Working v1ith Visшl S
С
Ф localhost52589
P1·od11cts
Price
№ше
Lifejacket $48.95
Soccer ball $ 19. 50
Соше1· flag $34.95
Рис.
6.9.
Автоматическая компиляция классов
Ka yak
156
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Средство автоматической компиляции удобно, когда все идет по плану. Его недо­
статок в том, что ошибки этапа компиляции и времени выполнения отображаются в
браузере, а не в
Visual Studio,
затрудняя выяснение причины возникновения пробле­
мы. В качестве примера в листинге
6.11
демонстрируется добавление ссьmки
null
в
коллекцию объектов моделей внутри хранилища.
Листинг
6.11.
Добавление ссыпки
из папки
null в файле 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Ыic
static SimpleRepository SharedRepository => sharedRepository;
SimpleRepository() {
var initialitems = new[] {
"Kayak", Price = 27 5М } ,
new Product { Name
"Lifejacket", Price = 48.95М ),
new Product { Name
19. 50М } ,
"Soccer ball", Price
new Product { Name
"Corner flag", Price = 34.95М)
new Product { Name
puЬlic
};
foreach (var р in initialitems) {
AddProduct(p);
products.Add("Error", null);
puЬlic IEnumeraЬle<Product>
puЫic
Products => products.Values;
void AddProduct(Product
Проблема вроде ссылки
null
р)
=> products.Add(p.Name,
р);
не будет видна до тех пор, пока приложение не на­
чнет выполняться. Перезагрузка страницы в браузере приведет к тому, что класс
скомпилируется, а приложение запустится заново. Когда ин­
SimpleRepository
фраструктура
MVC
создает экземпляр класса контроллера для обработки НТТР­
запроса от браузера, конструктор
SimpleRepository,
HomeController
создаст экземпляр класса
который в свою очередь попытается обработать ссьmку
добавленную в листинге
6.11.
Ссьmка
null
null,
вызовет проблему, но ее сущность не бу­
дет очевидной, потому что браузер не выводит сколько-нибудь полезное сообщение.
Включение страниц исключений разработчика
Во время процесса разработки при возникновении проблемы удобно отображать в
окне браузера более полезную информацию. Это можно делать, разрешив страницы
исключений разработчика, что требует изменения конфигурации в классе
как показано в листинге
6.12.
Startup,
Глава 6. Работа с Visual Studio
Роль класса
Startup
157
подробно объясняется в главе
что вызов расширяющего метода
14, а пока достаточно знать,
UseDeveloperExceptionPage () устанавливает
описательные страницы ошибок.
Листинг
6.12.
Разрешение страниц исключений разработчика в файле startup. cs
из папки
using
using
using
using
using
using
using
using
WorkingWi thVisualStudio
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencylnjection;
namespace WorkingWithVisualStudio {
puЫic class Startup {
puЫic
void ConfigureServices(IServiceCollection services ) {
services.AddMvc();
puЫic
void Configure(IApplicationBuilder
арр,
IHostingEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseMvcWithDefaultRoute();
Если перезагрузить окно браузера, то процесс автоматической компиляции пере­
строит приложение и выдаст более полезное сообщение об ошибке (рис.
С! lntornol Sorvor Error
С
!Ф
6.1 О).
х
localhost:64405
An unhandled exception occurred while processing the request.
NullReferenceExcep(ion: Objec reference no set to an instance of ап object.
WorkingW1thVisualStudio.Controllers.HomeController+ <>C.<lndex> b_O_O(Product pJ in
OL1ery
Cookies
Кomecontroll•r. cs, line
Headers
NullReferenceExcep iоп: Object reference not set to an instance of ап object.
\'>'o":ir'g~Vit
Visua 5 udю.Cortrcl:ers l"'OmeCo" tro er-<>C.<I
dex>b_O_O(P rodl.д о)
HomeController . cs
Рис.
6.1 О.
Страница исключения разработчика
1n
10
158
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Отображаемого в браузере сообщения об ошибке может быть достаточно для вы­
явления простых проблем, особенно из-за того, что итеративный стиль разработки
означает, что причиной. вероятно, стали самые последние изменения. Но для устра­
нения более сложных проблем
-
медленно
-
требуется отладчик
и проблем. которые не становятся очевидными не­
Visual Studio.
Использование отладчика
Среда
Visual Studio также
поддерживает выполнение приложения
MVC
с примене­
нием отладчика, который позволяет останавливать приложение для инспектирования
его состояния и пути прохождения запроса по коду. Это требует другого стиля раз­
работки, поскольку модификации классов С# не будут применены до тех пор, пока
приложение не запустится заново (хотя изменения, вносимые в представления
Razor,
по-прежнему вступают в силу автоматически).
Такой стиль разработки не настолько динамичен. как использование средства
автоматической компиляции, но отладчик
Visual Studio
великолепен и может предо­
ставлять намного более глубокое проникновение в суть способа работы приложения.
нежели то, что возможно с помощью сообщений, отображаемых в окне браузера.
Чтобы запустить приложение с применением отладчика, выберем пункт
Start
Debug (Отладка) среды Visual Studio. Перед за­
пуском приложения среда Visual Studio скомпилирует классы С# в проекте. но код
можно также скомпилировать вручную, используя пункты меню Build (Построение).
Debugging
(Запустить отладку) в меню
Пример приложения по-прежнему содержит ссылку
null, т.е. необработан­
NullReferenceExcepti o n. которое генерируется в классе
SimpleRepo si tory, прервет приложение и передаст управление выполнением от­
ное
исключение
ладчику (рис.
6. l l ).
.
1
;\ using Microsoft.AspНetCore .Мvс;
using WOrkingl-lithVisu•lStudio.llodels;
using System. l inq;
8 namespace WOrki ngWi thVisualStudio.Cont rollers {
В
puЫic class Нol\"e{ontroller : Controller {
1
т
Q
10
11
12
l
puЬlic
!ActionResult Index(}
•> View( Si111pleReposi tory . ShllredReposi tory. Pгoduc ts
.Where( p •> p.Price < 50}); О
l}
Exception Thrown
13
System.NullReferenceException: 'OЬject reference
not set to "" 1nst1nce of an object.'
View
А
100,.-.
!Жa•ls
Сору Deta~s
Exception Settings
0 8re6k \Yhen th1s except1on type is thrown
Except \•"hen thro\•m from:
О 'NorkingWithVisualStudio.dll
...
Рис.
6. 11.
Возникновение необработанного исключения
q
х
Глава 6. Работа с Visual Studio
159
Совет. Если отладчик не перехватывает исключение, тогда понадобится выбрать пункт
Settings (ОкнаqНастройки исключений) в меню Debug (Отладка)
Visual Studio и удостовериться, что в списке Commoп Laпguage Runtime
Exceptions (Общие исключения языка во время выполнения) отмечены флажки для всех
WiпdowsqException
среды
типов исключений.
Настройка точки останова
Отладчик вовсе не показывает основную причину проблемы. а только место, где
она проявилась. Подсвечиваемый
Visual Studio
оператор указывает на то, что про­
блема случилась при фильтрации объектов с применением
LINQ,
но для получения
деталей и выявления лежащей в основе причины потребуется проделать небольшую
работу.
Точка останова
-
это инструкция, которая сообщает отладчику о необходимости
остановить выполнение приложения и передать управление программисту. Вы може­
те исследовать состояние приложения,
посмотреть, что произошло, и при желании
возобновить выполнение.
Чтобы создать точку останова, щелкнем правой кнопкой мыши на операто­
ре кода и выберем в контекстном меню пункт
Breakpointq lnsert Breakpoint
(Точка
остановаqВставить точку останова). В целях демонстрации поместим точку останова
на метод
AddProduct ()
в классе
new Product { Name •
14
1
15
16
17
18
19
l
~
2е
•
6.12).
Price • 27SM } ,
new Product { ~lame • "Lifejac ket", Price • 48.951-' },
new Pt"Oduct { rll!1me • "Sоссег b4ll". Price • 19. sем } ,
new Product { f4ам • "Corner flag"' , Price • 34.9SH }
};
foreach (var р in initialtt~s) {
AddProduct(p);
puЫic Ifnшreгt1Ьle<Pгoduct >
27
Products •> products..Valuesi
puЫic void AddProd~t( Pгoduct р) •> Ml§ldft@lф.!M!)J;
•
28
29
100 %
" Каувk" ,
(рис.
products .Add( "Error" , null);
21
22
23
24
25
2б
Simp l eRepo si t o r y
l}
Рис.
Выберем пункт меню
6.12.
Создание точки останова
Debugq Start Debugging
(Отладкаq Запустить отладку),
чтобы запустить приложение, используя отладчик. или
Debugq Restart
(Отладкаq
Перезапустить), если приложение уже выполняется . Во время обработки начального
НТТР- запроса от браузера будет создан экземпляр класса
Simpl e Reposi to ry,
а вы ­
полнение кода достигнет точки останова, где произойдет пауза.
Во время паузы можно применять пункты меню
Debug среды Vlsual Studio или эле­
менты управления в верхней части окна для управления выполнением приложения
либо использовать разнообразные представления отладчика, доступные через меню
Debug~Windows (ОтладкаqОкна). чтобы инспектировать состояние приложения.
160
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
Просмотр значений данных в редакторе кода
Наиболее распространенное применение точек останова связано с отслеживанием
дефектов в коде. Прежде чем можно будет исправить дефект, потребуется выяснить.
что происходит. и одним из самых полезных средств, предлагаемых
Visual Studio.
яв­
ляется возможность просмотра и наблюдения за значениями переменных прямо в ре­
дакторе кода. Если навести курсор мыши на аргумент р в методе
AddProduct () . под­
свеченном отладчиком. то появится всплывающее окно. которое показывает текущее
значение р (рис.
поэтому на рис.
'
·~
tf
1'•
6.13). Рассмотреть всплывающее окно может
6.13 показана также его увеличенная версия.
!
··~'·,~
.... 1~ Workin9WrthVisu1IStud~Мodels.SimpltRep · f19 Stmpltkpository()
ntw Product { filee • "1(1yak" , Price • 27SМ },
ntw Product { tlame • "lifejacktt". Price • 48 . 9SМ },
nht ProduGt { па111е • "Soccer ball w, Price • 19. sен },
3
14
"
tj
-
- ~07 m~Vasu11Studio
10
17
18
19
быть затруднительно.
х
[+
nnr1 Product { Nal\f: • "Corner fla1" , Price • '4 . 9S.Н }
};
'
foreech (var
F.
р
in inir--.,....,-..,-----..,--..,------":----,,,
• ~
{ 1'Jolicin WithVisu11IStudio.Models.Product} Q"
AddProduct(p!;
2е
products . Add( "Erгcr",
21
)'
№me
)' Price
22
23
24
puЫic IEn.neraЫe < P"'Oduct >
,.
Products •> products.V111lues;
2S
Q
27
28
29
100%
..
Рис.
6.1 З.
Инспектирование значения данных
Результат не кажется особо впечатляющим, т.к. объект данных определен в том же
конструкторе. что и точка останова, но данное средство работает для любой перемен­
ной. Можно исследовать переменные, чтобы увидеть значения их свойств и полей.
Правее каждого значения находится небольшая кнопка с изображением канцелярс­
кой кнопки, которую можно использовать для наблюдения за значением переменной.
когда выполнение кода продолжится. Наведем курсор мыши на переменную р и щел­
кнем на кнопке с изображением канцелярской кнопки, чтобы закрепить ссылку на
Product. Развернем закрепленную ссылку, чтобы также закрепить свойства Name
Price, получив в итоге результат, который представлен на рис. 6.14 .
. ull);
t"roducts •> products. Valu~s;
•uct
р)
•> products.Add(p . 11....,,
•
р);
-р
/'p.Name
"p.Pnet
{Worlcingl'frthVisualStudio Modtls.Product)
р
-
'Коу> k"
275
.
Рис. б. 14. Закрепление значений в редакторе кода
и
Глава 6. Работа с Visual Studio
Выберем пункт
Continue
(Продолжить) в меню
Debug
(Отладка) среды
161
Visual Studio.
чтобы продолжить выполнение приложения. Поскольку приложение выполняет
цикл
foreach.
когда точка останова встретится опять , то снова возникнет пауза .
Закрепленные значения показывают, как изменяется объект. присвоенный перемен­
ной р, и его свойства (рис.
6.15).
' 8QI р
{WorkingWithVisualStudio.Мodels.Product}
JJ p.Nc1me
Р
•ufejacket'
iJ p.Prke --48-.9-S_ _ _ _ _ _ _ _ _ _
Рис.
6.15.
J
Наблюдение за изменением состояния с применением
закрепленных значений
Использование окна
Locals
Связанным средством является окно
тем выбора пункта меню
Locals отображаются
Locals (Локальные), которое открывается пу­
Debugc::>Windowsc::>Locals (Оrладкас::>Окнас::>Локальные). В окне
значения данных похожим на закрепленные значения образом, но
здесь присутствуют все локальные объекты. относящиеся к точке останова (рис .
loc•ls
Name
Value
Туре
Count = 2
System.C ollections.Goneric .IEnumerable<Workir
System.Collections.Generic.Di<tionary< string, W
."· ,.
• [oil
• )' Products
• ~ products
• ~ St•tic members
,
~
р
Count: 2
{WorkingWithVisualStudio.Models.Product)
' Soccer boll
19.SO
)' Name
)' Price
Рис.
6.16.
Каждый раз. когда выбирается пункт
новляется и в цикле
6. 16).
for e ach
\'Joricingl'frthVisu•IStudio.Models.Product
• string
decimal
Окно
Locals
Continue.
выполнение приложения возоб­
обрабатывается новый объект. Продолжая поступать
так, можно будет увидеть ссылку
nu l l
в окне
Locals
и в закрепленных зн ачениях ,
отображаемых внутри редактора кода. За счет применения отладчика для управления
выполнением приложения появляется возможность отслеживать его продвижение по
коду и получить представление о том, что происходит.
Устранить проблему со ссылкой
объектов
Produc t,
nu1 1
можно было бы путем очистки коллекции
но альтернативный подход предусматривает повышение надеж­
ности контроллера. как показано в листинге
ч ений
null
6.13.
где для проверки на предм ет зна­
используется null -условная операция (описанная в глав е
Листинг 6.13. Исправление проблемы со ссылкой
из папки
Views/Home
u s ing Mi c rosoft .As pNet Co re.M vc ;
u si n g Wo rk i n g WithV i s u a l St ud i o .Models ;
u s in g System .Li n q ;
4).
null в файле HomeController. cs
162
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
namespace WorkingWithVisualS t udi o .Contr o lle r s
puЫic class HomeController : Controller {
IActionResult Index ()
=> View(SimpleRepository.S har edReposit ory . r roducts
. Where (р => р?. Price < 50)) ;
puЫic
Для отключения точки останова необходимо щелкнуть правой кнопкой мыши
на
операторе
кода.
где она
находится.
BreakpointqDelete Breakpoint (Точка
и
выбрать в
контекстном меню
ного запуска приложения появится простая таблица с данными (рис.
D Working \~ith Visual Stu
С
пункт
остановаqУдалить точку останова) . После повтор­
6.17).
х
<D localhost;64405
P1·oducts
Name
Price
Lifejacket f48.95
Soccer bal\ f19.50
Corner flag f34.95
Рис.
6.17.
Устранение дефекта
Рассмотренная проблема решалась просто. если сравнить ее с проблемами. кото­
рые требуют реальной охоты за ошибками, но отладчик
Visual Studio
действительно
великолепен, и с помощью многочисленных доступных представлений приложения и
управления выполнением можно легко углубляться в детали с целью выяснения. что
пошло не так.
Использование средства
Средство
Browser Link
Browser Link
(Ссылка на браузер) позволяет упростить процесс разра ­
ботки за счет помещения одного или большего числа браузеров под контроль
Studio.
Visual
Оно особенно полезно, если нужно видеть влияние изменений в некотором
диапазоне браузеров . Средство
Browser Link
работает с отладчиком или без него. но
оно наиболее полезно в случае применения средства автоматической компиляции
классов, потому что появляется воз можность модифицировать любой ф айл в проекте
и видеть влияние изменения . не переходя в окно браузера и не перезагружая страни ­
цу вручную.
Настройка средства
Browser Link
Включение средства
S tartup
(листинг
6.14).
Browser Link
требует изменения конфигурации в классе
Глава 6. Работа с Visual Studio
Листинг
6. 14.
Browser Liпk в файле Startup. cs
WorkingWithVisualStudio
Включение средства
из папки
using
using
using
using
using
using
using
using
163
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
namespace WorkingWithVisualStudio {
puЫic class Startup {
void ConfigureServices(IServiceCollection services) {
services.AddMvc();
puЬlic
void Configure(IApplicationBuilder
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
app.UseMvcWithDefaultRoute();
puЫic
Использование средства
арр,
IHostingEnvironment env) {
Browser Link
Чтобы выяснить, как работает средство Browser Link, выберем пункт Start Without
Debugging (Запустить без отладки) в меню Debug (Отладка) среды Visual Studio. Среда
Visual Studio запустит приложение и откроет новую вкладку в окне браузера для отоб­
ражения результатов. Просмотрев НТМL-разметку, отправленную браузеру. можно за­
метить, что она содержит дополнительный раздел, подобный приведенному ниже:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Working with Visual Studio</title>
</head>
<body>
<h3>Products</h3>
<tаЫе>
<thead>
<tr><td>Name</td><td>Price</td></tr>
</thead>
<tbody>
<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"
164
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
id="
browserLink initializationData">
-
-
{"requestld":"968949d8affc47c4a9c6326de21dfa03","requestмappingFro
mServer":false}
</script>
<script type="text/javascript"
src="http://localhost:55356/dla038413c804e178ef009a3be07b262/
browserLink"
async="async">
</script>
<!-- Конец Browser Link -->
</body>
</html>
Совет. Если дополнительный раздел отсутствует, тогда нужно выбрать пункт ЕnаЫе
Link
(Включить
Browser Link)
в меню , показанном на рис.
6.18,
Browser
и перезагрузить страницу
в браузере.
Среда
ментов
Visual Studio
script,
добавляет в НТМL-разметку. посылаемую браузеру. пару эле­
которые применяются для открытия долговечного НТТР- подключения
к серверу приложений, чтобы среда
Visual Studio
могла обеспечить принудитель­
ную перезагрузку страницы браузером. (Если вы не видите элементы
удостоверьтес ь в том. что отмечен пункт ЕпаЫе
рис.
6 . 18.)
В листинге
6.15
Browser Link
приведено изменение. внесенное в представление
которое проиллюстрирует результат использования средства Bгowser
Toofs
Test
script. тогда
в меню. показанном на
CodeMaid
Window
Index.
Link.
Help
;
Refresh Linked Browsers
Browser Linlc Oashboard
Ctrl+Alt+ Enter
~
ЕnаЫе Browser Link
Рис.
6.18.
Применение средства
Листинг
6.15.
Добавление временной отметки в файле
из nаnки
Browser Link
для перезагрузки страницы в браузере
Index. cshtml
Views/Home
@mo del IEnumera Ыe< W or kingWith Visua lStudio.M ode l s.P rodu c t >
@{ Layout = null; }
< 1 DOCTYPE htm l>
<html >
<t1ea d>
<meta n a me = " viewpor t " co nten t= "width=device-widt h" />
Глава 6. Работа с Visual Studio
165
<title>Working with Visual Studio</title>
</head>
<body>
<h3>Products</h3>
<p>Request Time: @DateTime .Now. ToString ("HH:mm: ss") </р>
<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>
Сохраним изменение в файле представления и выберем пункт
Browsers
Refresh Linked
Browser Link внутри пане­
средство Browser Link не работа­
(Обновить связанные браузеры) в меню средства
ли инструментов
Visual Studio
(см. рис.
6.18).
(Если
ет, тогда можно попробовать перезагрузить страницу в браузере или перезапустить
Visual Studio и повторить попытку.)
Код JavaScript, встроенный в отправляемую
браузеру НТМL-разметку, будет пере­
загружать страницу. демонстрируя результат добавления, которым является появле­
ние простой временной отметки. Каждый раз, когда выбирается пункт меню
Linked Browsers,
Refresh
браузер будет делать новый запрос к серверу. Результатом запроса
окажется визуализация представления
Index и
генерирование новой НТМL-страницы
с обновленной временной отметкой.
На заметку! Элементы
script,
относящиеся к средству
Browser Link,
встраиваются толь­
ко в успешные ответы. Это значит, что если во время компиляции класса, визуализации
представления
ду браузером и
Razor или обработки запроса возникает исключение, то подключение меж­
Visual Studio утрачивается и страницу придется перезагружать, используя
сам брауэер, пока проблема не будет устранена.
Использование нескольких браузеров
Средство Вгоwsег
Link
может применяться для отображения приложения в не­
скольких браузерах одновременно. что удобно, когда нужно уладить вопросы несходс­
тва реализаций между браузерами (особенно при реализации специальных таблиц
стилей
CSS)
или увидеть, как приложение визуализируется набором настольных и
мобильных браузеров.
Чтобы отобрать браузеры, которые будут использоваться, выберем пункт
With
(Просмотреть с помощью) в меню. связанном с кнопкой
трументов
Visual Studlo
(рис.
6. 19).
llS Express
Browse
в панели инс­
166
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Browsers (select one or more):
Firefox
Add."
~leChrome
&ldjrlf,f,,Ijff,gJЩiat1fm"
P m
lnternal Web Browser
lntern~t Explorer
',
Microsoft td е '
Opera lnternet Browser
,
Set as Default
Progr~m:
Size of browser window:
Default
Browse
Рис.
Среда
Visual Studio
6. 19.
Cancel
Выбор множества браузеров
отобразит список известных ей браузеров. На рис.
6.20
пока ­
заны браузеры, установленные в моей системе; часть из них установлена вместе с
Windows (Internet Explorer
и
Edge).
а часть
-
лично мною по причине их широкого
распространения.
Tools
Test
CodeMaid
Window
Help
llS Express
llS Express
WorkingWithVisualStudio
Рис.
Среда
6.20.
Отбор браузеров из списка
Visual Studio ищет общие браузеры во
Add (Добавить) можно указывать
мощью кнопки
время процесса установки . но с по­
браузеры. которые не были обнару­
жены автоматически . Кроме того. можно настраивать инструменты тестирования от
независимых поставщиков наподобие
Browser Stack. которые
запускают браузеры на
расположенных в облаке виртуальных машинах. так что во время тестирования не
придется управлять крупной матрицей операционных систем и браузе ров.
Глава 6. Работа с Visual Studio
167
На рис
6.20 выбраны три браузера: Chrome, lnternet Explorer и Edge. Щелчок на
Browse (Обзор) приводит к запуску всех трех браузеров и загрузки в них URL
примера приложения (рис . 6.21 ).
кнопке
~ Woticing with VisU41 Stu
+
Е! Worki119 with Visual Stu Х
Х
с
-------~~
P1·oducts
Roquest Time: 13:18'33
Request Тime :
6.21.
С)
1 localhost6ФIOS
Request Time: 13:18:34
1З : 18: 34
Name
Price
Lifejacket f:48.95
Soccer ball f:19.50
Corner ftag f:З4.95
Рис.
~
х
Products
Products
Name
Price
Lifejacket ЫS .95
Soccer ball !19.50
Com<r flag !34.95
f-
о
Name
Pr1ce
Lifejacket
Е48 .95
Soccer Ьall El 9.50
Comer flag 04.95
Работа с несколькими браузерами
Чтобы посмотреть, какими браузерами управляет средство
Browser Link, выберем
Browser Link Dashboard (Инструментальная панель Browser Link) в меню средс­
тва Browser Llnk; откроется 01шо Browser Link Dashboard (Инструментальная панель
Browser Link). представленное на рис. 6.22. Инструментальная панель показывает
URL. отображаемый каждым браузером. и позволяет обновлять страницу в каждом
пункт
браузере по отдельности.
• 1:1
Browser link Dashboard
.А
х
Workin9WithVisu11IStudio (3 connections)
.А
Connections
Chrome.,..
Microsoft Edge .,..
Mozilla"'
-1
-1
-1
learn more about Browser link
Рис.
6.22.
Окно
Browser Link Dashboard
Подготовка файлов JavaScript
и CSS для развертывания
При создании клиентской части веб-приложения обычно будет создаваться ряд
специальных файлов
JavaScript
и
CSS, которые дополняют аналогичные файлы в па­
Bower. Такие файлы требуют специальной обра­
кетах, установленных инструментом
ботки для оптимизации их доставки в производственную среду. Цель в том, чтобы ми-
168
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
нимизировать количество НТГР-запросов и долю полосы пропускания, необходимую
для доставки файлов клиенту. Процесс известен как пакетирование и минификаr~ия.
В этом разделе объясняется, как включить доставку статического содержимого и ка­
ким образом подготовить такое содержимое для развертывания.
Включение доставки статического содержимого
Инфраструктура
файлов из папки
лона
Empty,
ASP.NET Core
wwwroot
располагает поддержкой для доставки статических
клиентам, но когда проект создается с применением шаб­
упомянутая поддержка по умолчанию отключена. Чтобы включить под­
держку доставки статического содержимого, в класс
один оператор (листинг
Листинг
us1ng
using
using
using
using
using
using
using
Startup
необходимо добавить
6. 16).
6.16. Включение поддержки доставки статического содержимого
в файле Startup.cs из папки WorkingWithVisualStudio
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
namespace WorkingWithVisualStudio {
puЫic class Startup {
void ConfigureServices(IServiceCollection services) {
services.AddMvc();
puЫic
puЫic
void Configure(IApplicationBuilder
арр,
IHostingEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
Добавление в проект статического содержимого
Чтобы продемонстрировать процесс пакетирования и минификации. понадо­
бится добавить в проект какое-нибудь статическое содержимое и внедрить его в
пример приложения. Для начала создадим папку
шению хранятся специальные файлы
wwwroot/css,
в которой по согла­
CSS. Затем добавим файл по имени first.
css, используя шаблон элемента Style Sheet (Таблица стилей), как показано на рис.
6.23. Шаблон Style Sheet находится в разделе ASP.NET CoreqWebqContent (ASP.NET
СоrеqВебqСодержимое).
Глава 6 Работа с Visual Studio
" ASP.NET Core
Code
G•neral
" Web
Г'ti
HTML Page
ASP .NЕТ Core
~
Style Sheet
ASP.NEТ Core
LESS Style Sheet
ASP.NП
Core
SCSS Style Sheet (SASS)
ASP.NEТ
Core
c•e)J
ASP.NEТ
General
Scripts
Content
~
Online
first.css
Name:
Рис.
Поместим в файл
Листинг
hЗ
169
6.17.
6.23.
first. css
Создание таблицы стилей
стили
Содержимое файла
CSS
из листинга
first. css
из папки
CSS
6.17.
wwwroot/css
{
fon t-size: 18pt;
font-family: sans -serif;
td {
border: 2рх solid Ыасk;
border -collapse:co llap se;
padding: Spx;
font-family: sans-serif;
t аЫе ,
Повторим процесс для создания в папке
по имени
Листинг
second. css
6.18.
wwwroot / css
еще одной таблицы стилей
с содержимым , приведенным в листинге
Содержимое файла
second. css
из папки
6 . 18.
wwwroot/css
р
f ont -fami ly: sans-serif;
font-size: l Opt ;
color: darkgreen;
background-co lor : a n tiquewhite;
borde r: lpx so l id Ыасk;
padding: 2 р х;
По соглашению специальные файлы
JavaScript
хранятся в папке
wwwroot/ js.
JavaScript File (Файл
6.24). Шаблон JavaScript File находится
Создадим такую папку и с применением шаблона элемента
JavaScript) добавим в нее файл thi rd. j s (рис.
в разделе ASP.NET CoreqWebqScripts (ASP.NET СоrеqВеб q Сценарии) .
170
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
I"
lnst•llod
ASP . NEТ
"
@
Core
Codt
~·
~·
Gentral
А
Web
ASP . NEТ
General
r.r
S<npts
Conttnt
Г"
"Online
(i>'J
JavaXnpt f1le
ASP.NET Core
Typ6cr1pt File
ASP.NET Core
TypeS<ript JSX File
ASP . NEТ
Core
TypeS<:ript JSON Configuration File
ASP.NEТ
Core
JSXFile
ASP.NEТ
Cort
Angul,rJs Controller
ASP.NEТ
Core
Angul11rJs Controller using Sscope
ASP.NET Core
AngularJs D1rectrve
ASP.NET Core
№me:
Рис.
6.24.
Создание файла
Поместим в новый файл простой код
Листинг
6.19.
Содержимое файла
JavaScript
JavaScript,
third. j s
представленный в листинге
из папки
6. 19.
wwwroot/ j s
document.addEventListener("DOMContentLoaded", function ()
var element = documen t . crea teElement( "p");
element.textContent = "This is the element from the third.js file";
document. querySelector ( "body") . appendChild ( element) ;
) ) ;
Нам необходим еще один файл
имени
fourth. j s
Листинг
6.20.
JavaScript.
Создадим в папке
с содержимым, показанным в листинге
Содержимое файла
fourth. js
из папки
wwro ot/j s
файл по
6.20.
wwwroot/js
document.addEventListener("DOMContentLoaded", function ()
var element = document . cre ateEl ement ( "р");
element.textContent = "This is the element from the fourth. js file";
documeлt. querySelector ( "b ody " ). appendChild (element) ;
}) ;
Обновление представления
Последний
Index. cshtm l
(листинг
Листинг
подготовительный
шаг
связан
с
обновлением
для использования новых таблиц стилей
CSS
представления
и файлов
JavaScript
6.21) .
6.21. Добавление элементов script и link в файле Index. cshtml
из папки Views/Home
@model IEnumer aЫe<WorkingWithVisualStud i o
@{ Layout = null; )
.M odels .Pr oduct>
Глава 6. Работа с Visual Studio
171
< ! DOCTYPE h t ml >
< html>
<head>
<meta name="viewport" content="width=device-wi d th" />
< title>Working with Visual St u d i o</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>
<b o dy>
<h 3>Produ c ts< / h 3 >
<p>Reque s t Time : @Da teT ime. Now . ToSt ring ( "НН : mm : ss") < / р>
< tаЫе >
<thead >
<tr ><t d >Name</td >< td >Pr ice </ td ></ tr >
< /thead >
<tbody>
@forea c h (var р in Model )
<tr>
< td >@p.Name </td>
< td > @( $" ( р. Pr i с е: С2 } " ) < / td >
</tr>
</ tbody >
< / tаЫе>
< / body>
</ html>
В результате запуска примера приложения появится содержимое, приведенное на
рис .
CSS.
Существующее содержимое было стилизовано по с редством таблиц стилей
6.25.
а код
JavaScript добавил
новое содержимое.
D Worlcing wilh Vrs~I S
С
Ф localhost 04405
Products
l ~IR-eq-u-es-t~n-m-e~1-5~08~5~0~~~~~~~~~~~~~~~~~~~~~~~~1 1
Name
Price
Lifejacket
f:48 95
Soccer ball f:19 50
Cornerflag f:34.95
11
!•" ••• " •••"•••"".J• ••
~IT=his=~==th=ee=l•=m=e=nt=f•=om==th=ei:ou=rth==js=fll=e====================================I
Рис.
6.25.
Выполнение примера приложения
Часть 1. Введение в инфраструктуру ASP. NET Саге MVC 2
172
Пакетирование и минификация в приложениях
MVC
В настоящий момент есть четыре статических файла. и браузер должен делать че­
тыре запроса, чтобы получить эти статические файлы . К тому же каждый такой файл
при доставке клиенту отнимает больше полосы пропускания, чем должен, поскольку
содержит пробельные символы и имена переменных. которые являются содержатель­
ными для разработчика. но не имеют никакого значения для браузера .
Объединение файлов одного и того же типа называетс я
пакетированием.
Уменьшение размеров файлов называется мин11фика4ией. Обе задачи выполня­
ются в приложениях
ASP.NET Core MVC с помощью
Visual Studio.
расширения
Bundler & Minifler
(Упаковщик и минификатор) для
Установка расширения
Visua/ Studio
Первый шаг заключается
Tools~Extensions
в установке расширения .
and Updates (Сервис~Расширения
Выберем
пункт меню
и обновления) и щелкнем на кате­
гории Online (Онлайновые). чтобы отобразить галерею доступных расширений Visual
Studio. В поле поиска справа вверху введем слово Bun dler (рис. 6.26). Отыщем расши­
рение Bundler & Minifier и щелкнем на кнопке Download (Загрузить). чтобы добавить
его в Visual Studio. Перезапустим Visual Studio для завершения процесса установки.
bltl'SIOns ~nd Updates
?
'
Sort Ьу. {Ititv.n
-ce- - - = = i
Online
~
-А
Bundlt~
~1' Bundler & Minifier
lle~ Adds support for bundling .1nd minifying JJvaScript,
Visu&I Studio M1rketplece
[ Oown1oad]
CS:S .1nd HTML filts in •ny projtct.
" Controls
to Templatn
t> Tools
Se.11ch Resutts
Х
Created Ьу: Mads Kristensen
Version: 2.4.340
Downloods: 408992
Rollup Task Runner
T1sk Runner Explorer support for RollupJs - The next 9ener11tion
J1v1Script modult bundler.
Roting:
(89 Vot")
Мо1е lnformat1on
Report ble:nsion to Mюosoft
ii Upd1tes
11
Ro1ming Ьtension Minager
S<:he-duied For 11\Std:
None
S<heduled For Upclote:
None
S<:heduied For Uninsi.11:
None
CNngt your &l:tn~ions and Upd1t" ~ettings
Рис.
6.26.
Поиск расширения
Visual Studio
пакетирование и минификация файлов
После установки расширения перезапустим
Visual Studio
и откроем проект при­
мера . Благодаря добавлению расширения появилась возможность выбирать в окне
Solution Explorer
множество файлов одного типа. пакетировать их вместе и минифи ­
цировать их содержимое. В качестве примера выберем файлы
в окне
first. c ss
и
seco nd.
Solution Explorer. щелкнем на них правой кнопкой мыши и выберем в
контекстном меню пункт Bundler & MinifierqBundle and Minify Files (Упаковщик и
минификаторqПакетировать и минифицировать файлы) . как показано на рис . 6.27.
css
Глава 6. Работа с Visual Studio
• t:i
Solution Explorer
~
173
Х
·l 'Ф·!;"G})! j )'-Р·
Search Solutioi> Explorer (Ctr1•;)
@wwwroot
~
css
~ Open
js
OpenWith".
!J fourthJs
!J third.js
{} Cleanup Selected Cod•
lib
Collapse R..:ursively
'0
Run Web Cod• Analysis
Bundlor & Minifi•r
Т
-----
:- 1
----
Bundle and Minify Files ~~ft+ Alt+~
N..-, Solut•on Explor<r Vi.-."
Exclude From Proj•ct
.)(, Cut
Ctrl+X
OJ
Сору
Ctrl+C
х
D•lel•
D•I
Renarne
)'
Рис.
Alt+Enter
Properties
6.27.
Пакетирование и минификация файлов
Сохраним выходной файл под именем
рение обработает файлы
CSS.
bundle . css,
В окне Solutioп
Explorer
CSS
в результате чего расши­
отобразится новый элемент
bundle. css, который можно раскрыть и увидеть минифицированный файл с именем
bundle .min. css. Открыв минифицированный файл , вы заметите, что содержимое
обоих отдельных файлов
CSS
было объединено. а все пробельные символы удалены.
Работать с таким файлом напрямую вы вряд ли захотите . но он меньше по размеру и
требует только одного НТГР- подключения для доставки стилей
Повторим процесс с файлами
bundle . j s
и
bundle. min. j s
в
third. j s и fourth. j s.
папке wwwroot/ j s.
CSS
клиенту.
чтобы создать новые файлы
Внимание! Удостоверьтесь, что выбираете файлы в порядке их загрузки браузером, чтобы
предохранить порядок следования стилей или операторов кода в выходном файле. Таким
образом , например , выбирайте файл
th ird. j s
перед файлом
f ou r t h. j s,
чтобы обес­
печить выполнение кода в правильном порядке.
В листинге
6 .22
показано представление
Index. cshtml,
в котором элементы
link
для отдельных файлов заменены одним таким элементом, запрашивающим пакетиро­
ванные и минифицированные файлы.
Листинг 6.22. Применение пакетированных и минифицированных файлов
в файле
Index. csh tml
из папки
Views/Home
@model I En umeraЫe<Work ingWi t h Vis ualStudio.M od els.Produ ct>
@{ Layout = n u ll ; )
< ! DOCTYPE html >
<html >
<head>
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
174
<meta name="viewport" content="width=device-width" />
<title>Working with Visual Studio</title>
<link rel="stylesheet" href="css/bundle.min.css" />
<script src="js/bundle.min.js"></script>
</head>
<body>
<h3>Products</h3>
<p>Request Time: @DateTime. Now. ToStr ing ( "НН: mm: ss") </р>
<tаЫе>
<thead>
<tr><td>Name</td><td>Price</td></tr>
</thead>
<tbody>
@foreach (var р in Model)
<tr>
<td>@p.Name</td>
<td>@($"{p.Price:C21")</td>
</tr>
</tbody>
</tаЫе>
</body>
</html>
После запуска приложения никаких визуальных отличий не будет заметно, но
приложение использует пакетированные и минифицированные файлы. предоставляя
браузеру все стили и код, которые были определены в отдельных файлах.
По мере выполнения операций пакетирования и минификации расширение ведет
учет обработанных файлов в файле по имени
bundleconfig. j son.
находящемся в
корневой папке проекта. Вот конфигурация, которая была сгенерирована для файлов
в примере приложения:
"outputFileName": "wwwroot/css/bundle.css",
"inputFiles": [
"wwwroot/css/first.css",
"wwwroot/css/second.css"
1.
{
"outputFileName": "wwwroot/js/bundle.js",
"inputFiles": [
"wwwroot/js/third.js",
"wwwroot/js/fourth.js"
Расширение автоматически отслеживает входные файлы на предмет изменений
и заново генерирует выходные файлы, когда происходят изменения. гарантируя от­
ражение в пакетированных и минифицированных файлах любых модификаций. Для
демонстрации в листинге
6.23
приведено изменение, внесенное в файл
third. j s.
Глава 6. Работа с Visual Studio
Листинг
175
6.23. Внесение изменения в файл third. js из папки wwwroot/js
d o cument.addEventListener("DOMContentLoaded", f u nction () (
v ar element = document. createE l ement ( "р" ) ;
element. textContent = "This is the element from the (modified) third. js
file";
document. querySelector ( "body" ) . appendChild (el ement);
)) ;
После сохранения файла расширение заново сгенерирует файл
Перезагрузив страницу в браузере. можно увидеть изменение (рис.
CJ
Working with V1Sual Stu.
С
bundle .min. j s.
6.28).
Х
1Ф localhost 64_405
*J
Products
jRequesl Time 15 51 08
Name
Price
Lifejacket
f:48.95
Soccer ball f:19.50
Corner flag
ЕЗ4 . 95
jThis is lhe element from the (modified) third.js file
IThis is the element from the fourlh js file
Рис.
6.28.
Обнаружение изменений в пакетированных и минифицированных файлах
Резюме
В главе были описаны возможности, которые
Visual Studio
предлагает для раз­
работки веб-приложений, включая автоматическую компиляцию классов, средство
Browser Link,
а также пакетирование и минификацию. В следующей главе объясняет­
ся, как подвергать модульному тестированию проекты
ASP.NET Core MVC.
ГЛАВА
7
Мо,пульное тестирование
приложений МVС
главе будет показано, как проводить модульное тестирование при­
в настоящейMVC.
ложений
Модульное тестирование
это вид тестирования, при котором
индивидуальные компоненты изолируются от остальной части приложения, позволяя
тщательно проверить их поведение. Инфраструктура
ASP.NET Core MVC
была спро­
ектирована так, чтобы облегчить задачу создания модульных тестов, и среда
Studio
Visual
предлагает поддержку широкого диапазона инфраструктур модульного тести­
рования. В главе объясняется, как настроить проект модульного тестирования, каким
образом установить одну из самых популярных инфраструктур тестирования и что
собой представляет процесс написания и прогона тестов. В табл.
7.1
приведена свод­
ка по главе.
Таблица
7.1.
Сводка по rлаве
Задача
Создание модульного теста
Реwение
Листинг
Создайте проект модульного тестиро­
7.5, 7.6
вания, установите тестовый пакет и до­
бавьте классы, которые содержат тесты
Изоляция компонентов для модуль­
Используйте интерфейсы для разделения
ного тестирования
компонентов приложения и применяйте в
7.7-7.14
модульных тестах фиктивные реализации
с ограниченными тестовыми данными
Прогон тех же самых тестов xUпit.пet
е разными значениями данных
Используйте параметризованный модульный тест либо получайте тестовые
7.15-7.17
данные из метода или свойства
Упрощение процесса создания фик­
тивных тестовых объектов
Применяйте инфраструктуру имитации
7.18, 7.19
177
Глава 7. Модульное тестирование приложений MVC
Проводить ли модульное тестирование?
Наличие возможности легко выполнять модульное тестирование является одним из преиму­
ществ использования инфраструктуры ASP.NEТ
Core MVC,
но такая процедура предназначе­
на не для всех, и я не намерен притворяться, что дело обстоит иначе.
Мне нравится модульное тестирование, и я применяю его в своих проектах, но далеко не во
всех и не настолько согласованно, как вы могли ожидать. Я предпочитаю концентрироваться
на написании модульных тестов для средств и функций, о которых знаю, что они будут труд­
ны в реализации и вполне вероятно станут источником ошибок после развертывания. В та­
ких ситуациях модульное тестирование помогает структурировать мои мысли о том, как луч­
ше всего реализовать то, что необходимо. Я считаю, что одно лишь размышление о том, что
нужно тестировать, способствует появлению идей относительно потенциальных проблем,
причем до того, как доведется иметь дело с действительными ошибками и дефектами.
Тем не менее, модульное тестирование
-
инструмент, а не догма, и только лично вы зна­
ете, в каком объеме оно должно проводиться. Если вы не находите модульное тестирова­
ние полезным или располагаете другой методологией, которая вам больше подходит, то не
должны думать, что обязаны использовать модульное тестирование просто потому, что это
модно. (Однако если вы не располагаете более эффективной методологией и не тестируете
вообще, то вероятно даете возможность пользователям обнаруживать имеющиеся ошибки,
что редко оказывается идеальным решением. Вы не обязаны выполнять модульное тести­
рование, но на самом деле должны проводить тестирование какого-то вида.)
Если вы не сталкивались с модульным тестированием ранее, тогда я предлагаю опробовать
его и посмотреть, как оно работает. Если вы не поклонник модульного тестирования, то мо­
жете пропустить данную главу и перейти к чтению главы 8, где начнется построение более
реалистичного приложения
MVC.
Подготовка проекта для примера
В главе мы продолжим пользоваться проектом
рый был создан в главе
объектов
Product
6.
WorkingWithVisualStudio,
кото­
Здесь мы добавим в него поддержку для создания новых
в хранилище.
Включение встроенных вспомогательных функций дескрипторов
В данной главе применяется одна из встроенных вспомогательных функций де­
скрипторов для установки атрибута
href
элемента а. Работа вспомогательных
функций дескрипторов подробно объясняется в главах
23-25,
но пока мы просто
должны включить их. Создадим файл импортирования представлений, щелкнув пра­
Views, выбрав в контекстном меню пункт Addt:::>New
ltem (Добавитьо:::>Новый элемент) и указав шаблон элемента MVC View lmports Page
вой кнопкой мыши на папке
(Страница импортирования представлений МVС) из категории
Studio
автоматически назначит файлу имя
кнопке
Add
Среда
Visual
а щелчок на
(Добавить) приведет к его созданию. Поместим в файл содержимое. пока­
занное в листинге
Листинг
ASP.NET.
Viewimports. cshtml,
7 .1.
7. l.
Содержимое файла
_ Viewimports. csh tml
из папки
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Views
Часть 1. Введение в инфраструктуру ASP.NET Соге MVC 2
178
Оператор в листинге
7.1
включает встроенные вспомогательные функции дескрип­
торов, в том числе ту, которая вскоре будет использоваться в представлении
Можно было бы добавить операторы
using
Index.
для импортирования пространств имен
из проектов, но представления не являются важной частью рассматриваемого здесь
примера приложения. так что ссылка на типы моделей с их пространствами имен
проблем не вызовет.
Добавление действий к контроллеру
Первый шаг связан с добавлением действий к контроллеру
Home.
который будет
визуализировать представление для ввода данных и для получения этих данных от
браузера (листинг
главе
2
Листинг
7.2).
Действия следуют тому же шаблону. который применялся в
и подробно обсуждается в главе
7.2.
17.
Добавление методов действий в файле HomeController. сз
из папки
Controllers
using Microsoft.AspNetCore.Mvc;
using WorkingWithVisualStudio.Models;
using System.Linq;
namespace WorkingWithVisualStudio.Controllers
puЫic class HomeController : Controller {
SimpleRepository Repository
puЫic
= SimpleRepository.SharedRepository;
IActionResul t Index () => View (Reposi tory. Products
. Where (р => р?. Price < 50)) ;
[HttpGet]
IActionResul t AddProduct ()
puЬlic
=>
View (new Product () ) ;
[HttpPost]
puЬlic 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>
Глава 7. Модульное тестирование nриложений MVC
179
<head>
<meta name="viewport" content="width=device-width" />
<title>Working with Visual Studio</title>
<link rel="stylesheet" href="/liЬ/bootstrap/dist/css/bootstrap.min.css" />
</head>
<body class="p-2">
<hЗ class="text-center">Create Product</hЗ>
<form asp-action="AddProduct" method="post">
<div class="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>
<div class="text-center">
<button type="submit" class="Ьtn Ьtn-primary">Add</button>
<а asp-action="Index" class="btn btn-secondary">Cancel</a>
</div>
</form>
</body>
</html>
Представление содержит НТМL-форму. которая применяет НТТР-запрос
для отправки значений
Narne
и
Price
действию
Содержимое стилизовано с использованием пакета
AddProduct
CSS
из
контроллера
POST
Horne.
Bootstrap.
Обновление представления rndex
Последний подготовительный шаг предусматривает обновление представления
Index,
чтобы включить в него ссылку на новую форму (листинг
7.4).
Кроме того, поя­
вилась возможность удалить файлы JavaScгipt. используемые в предыдущей главе, и
заменить специальные таблицы стилей
ся к элементам
Листинг
7.4.
HTML в
CSS
стилями
Bootstrap,
которые применяют­
представлении.
Обновление содержимого в файле
Index.cshtml
из папки
Views/Home
@model IEnumeraЫe<WorkingWithVisualStudio.Models.Product>
@( Layout = null;}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Working with Visual Studio</title>
<link rel="stylesheet"
href="/liЬ/Ьootstrap/dist/css/Ьootstrap.JD.in.css"
</head>
<body class="p-1">
<hЗ class="text-center">Products</hЗ>
<taЬle class="taЬle taЬle-bordered taЬle-striped">
<thead>
<tr><td>Name</td><td>Price</td></tr>
</thead>
/>
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
180
< tb od y >
@forea c h (var р in Mo del)
<tr >
<td> @p.Name</td >
<td>@( $" {p.Pri c e: C2) " ) </ t d>
< / tr >
</tbod y>
< /tаЫе>
<div class="text-center">
<а class="btn Ьtn-primary" asp-action="AddProduct">
Add New Product
</а>
</div>
< /body>
</ html>
Запустив пример приложения, можно увидеть по-новому стилизованное содержи ­
мое и кнопку
Add New Product
(Добавить новый товар). щелчок на которой приводит
к открытию формы для ввода данных. Отправка формы добавит в хранилище новый
объект
Pr oduct
и перенаправит браузер для отображения первоначального представ­
ления приложения (рис .
7. 1) .
DWattUfl9wtttlVl\~I~
С
)(
Ф loc11hot1 б4АО5
tr
;
Products
Create Product
н"m,
1
/ l•f•J•cke<
Soccer
н ....
Price
lifej&ekt\
S<S.95
So<:cer
Ьаt!
$19.SO
Corner flj]g
SЗ4.95
Runntng Shoes
b~ll
Comtrfl>g
48 50
S"8.50
+Ш§iij§i
-l
Рис.
7 .1.
Выполнение примера приложения
Совет. Не забывайте, что в этом примере объекты хранятся только в памяти, т.е . любые со­
здаваемые товары при перезапуске приложения будут утеряны.
Модульное тестирование приложений
MVC
Модульны е тесты используются для проверки поведения отдельных компонентов
и функциональных средств в приложении, а платформа
тура
MVC
ASP.NET Core
и инфраструк­
спроектированы так. чтобы максимально облегчить настройку и запу с к
модульных тестов для веб-приложений. В последующих разделах объясняется, как
настроить модульное тестирование в
Visual Studio.
и демонстрирует с я написание
Глава 7. Модульное тестирование приложений MVC
модульных тестов для приложений
MVC.
181
Кроме того, будут представлены полезные
инструменты, которые делают модульное тестирование проще и надежнее.
Доступен целый ряд разных пакетов для модульного тестирования. В книге приме­
xUnit.net; он выбран из-за хорошей интегра­
Visual Studio, к тому же данный пакет использовался командой разработчиков
Microsoft при написании модульных тестов для ASP.NET Core. В табл. 7.2 приведена
сводка, позволяющая поместить xUnit.net в контекст.
няется один из них, который называется
ции с
Таблица
7.2. Помещение xUnit.net в контекст
Вопрос
Ответ
Что это такое?
xUnit.net -
это инфраструктура модульного тестирования, которая
может применяться для тестирования приложений
ASP.NET
Соге
MVC
Чем она полезна?
xUnit.net -
хорошо написанная инфраструктура модульного тести­
рования, которая легко интегрируется со средой
Как она используется?
Visual Studio
Тесты определяются как методы, аннотированные с помощью атри­
бута
Fact или Theory. Внутри тела метода используются методы
Assert для сравнения ожидаемого результата теста с тем,
класса
что получилось в действительности
Существуют ли какие­
Главный просчет при модульном тестировании связан с недоста­
то скрытые ловушки
точной изоляцией тестируемого компонента. За дополнительными
или ограничения?
сведениями обращайтесь в раздел "Изолирование компонентов для
модульного тестирования" далее в главе. Самой крупной проблемой, специфичной для
xUnit.net,
является нехватка документации.
Кое-какая базовая информация доступна на веб-сайте
xuni t. gi thub. io,
h t tp: / /
но расширенное применение требует метода
проб и ошибок
Существуют ли
Доступно множество инфраструктур модульного тестирования.
альтернативы?
Двумя популярными альтернативами являются
тва
Microsoft)
и
MSTest
(производс­
NUnit
На заметку! Практически все в области модульного тестирования является предметом пер­
сональных предпочтений и вопросом шумных разногласий. Некоторым разработчикам не
нравится отделять модульные тесты от кода приложения, и они предпочитают определять
тесты в том же самом проекте или даже в том же самом файле класса. Я описываю здесь
распространенный подход, которому следую сам, но если он не кажется вам правиль­
ным, тогда вы должны поэкспериментировать с различными стилями тестирования, пока
не подберете для себя подходящий.
Создание проекта модульного тестирования
Для приложения
ASP.NET Core
обычно создается отдельный проект
Visual Studio,
содержащий модульные тесты, каждый из которых определяется как метод в классе
С#. Применение отдельного проекта означает, что приложение можно развернуть, не
развертывая одновременно тесты.
Чтобы создать проект тестирования. щелкнем правой кнопкой мыши на элементе
решения
WorkingWithVisualStudio
тном меню пункт
Addo::>New Project
в окне
Solution Explorer и выберем в контекс­
(Добавить<::> Новый проект).
182
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
В категории
Visual C#G.NET Саге выберем шаблон xUnit Test Project (.NET Core)
(.NET Core)). как показано на рис. 7.2.
(Проект тестирования xUпit
@E1mowort4.б.1
Р Rec~nt
• lnst1lled
:\
=J So11Ьy: (D=rf=•"'u~'------·_,l
Console Арр (.NЕТ Cort)
Visu1IC~
WindO'Ns Classic Desktop
Yleb
CI•" Library (.NET Со")
Visual (J"
.NEТCore
"
UnitTest Project (.NET Core)
Visual (:-
@
ASP.NEТ Cor. W.Ь Apploc1tion
.NET Stond•rd
Visual
с~
project th1t cont.ains xUn1t.net ttsts th1t
can run on .NH Core on Windows. Unux
1nd MocOS.
~ ~~~~.; i~; i>;~;~ (.Nй"с6 ••>·
Cloud
Test
~
Е: Seorch(Ctrl•E)
А
... Visual (#
о-
11'
Туре:
Visuo!ll 81s1c
SQl S.rver
Visual с:
Ontine
Not finding whlt you are looking for?
Open V1su•I Stud10 lмttlfer
WorkingWrthVisu.!IStudio.Tests
№mo:
@"i:ojtcts\Wortin9W~hVisu11Studio
locat1on:
Рис.
7.2.
Browse."
Выбор шаблона
xUnit Test Project (.NET Core)
Внимание! Удостоверьтесь в том, что выбираете правильный шаблон проекта. В
Visual Studio
предлагается несколько шаблонов для проектов модульного тестирования, которые име­
ют похожие названия.
По соглашению проекту модульного тестирования назначается имя <ИмяПрило ­
жения>.
Tests.
Установим имя нового проекта в
на кнопке ОК. Среда
Visual Studio
Wor kingWi th Visua .lStudi o . Tests и щелкнем
NuGet для ин ­
создаст проект и установит пакеты
фраструктуры xUпit.пet и ее зависимостей.
Удаление стандартных тестовых классов
Среда
Visual Studio
добавляет в проект тестирования файл класса С#. который
приведет в беспорядок результаты более поздних примеров . Щелкнем правой кноп­
кой мыши на файле
Uni tTest l. cs
в проекте Work ingWi thVisua.l Studio . Test s и
Delete (Удалить). В открывшемся диалоговом окне
Visual Studio удалит указанный файл класса .
выберем в контекстном меню пункт
щелкнем на кнопке ОК. и
Создание ссылки на проект
Чтобы сделать классы в главном проекте доступными для тестирования. щелкнем
правой кнопкой мыши на элементе
WorkingWi thVisua.l St udio. Tests в окне Solution
AddGReference (ДобавитьGСсылка).
Отметим флажок для элемента Worki ngWithVi sua.lSt udio в разделе Solution
(Решение). как иллюстрируется на рис. 7.3 .
Explorer
и выберем в контекстном меню пункт
Глава 7. Модульное тестирование приложений MVC
.:Refe1ence Ma~ager • WorkingW1thVi~ualStudio.Tests
.
. .'
·
Projects
А
~ame
~
Shared Projects
~
Browse
Path
../Cllt~~'lФ~·m••·~2zmп1~•11m.~ВФ1!1·•~··•••а·~··
lt;Б
Р·
Name:
WorkingWithVisualStudio
1 Browse."
Рис.
Х
?
Search (Ctrl+ Е)
Solution
183
j L-= ~к. ~11
Cancel
Создание ссылки на проект приложения
7.3.
Щелкнем на кнопке ОК для создания ссылки на проект приложения. В окне
Solutioп
Explorer может быть
виден работающий значок, отображающийся на элемен­
те Depeпdeпcies (Зависимости) для проекта тестирования , но после построения про ­
ектов он исчезнет.
Написание и выполнение модульных тестов
Теперь, когда все подготовительные шаги завершены, можно приступать к напи ­
санию некоторых тестов.
Первым делом добавим в проект Wo гkingWi thVisual Studi o
са по имени Pгoduct.Tests .
cs
. Tests файл клас ­
с определением, представленным в листинге
7.5.
Несмотря на простоту , класс содержит все, что требуется для начал а модульного
тестирования .
На заметку! В методе CanChang e Pгod uc tPгic e
( ) умышленно допущена ошибка, кото­
рая позже в разделе будет исправлена.
Листинг
7.5.
Содержимое файла ProductTests. cs
WorkingWithVisualStudio.Tests
из папки
usi ng Wor kin g Wi th Vi s u a l St ud io . Mode l s ;
using Xun it ;
namespace WorkingWithVisualStudi o .Te sts
puЫic c la ss Pr o du ct Tests {
[Fact ]
puЫi c v oid CanCha ngeProduct Name()
11
Орга ни заци я
v ar
11
p.Name
11
n e w Prodl!c t { Name
р =
=
"T est " , Pri ce
Де й ств и е
=
"New Name ";
Утвержден и е
Asse rt .Equa l ( "New Name", p . Name);
[ Fact ]
puЫi c
void Ca nCh angeP r o du ct Price() {
l OOM };
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
184
11
Организация
var
11
р =
new Product { Name
p.Price =
11
"Test", Price
lOOM );
Действие
200М;
Утверждение
Assert.Equal(lOOM, p.Price);
В классе
ProductTes ts присутствуют два модульных теста, проверяющих отдельные
Product из проекта WorkingWi thVisualStudio.
линии поведения класса модели
Проект тестирования может содержать много классов, которые способны включать
много модульных тестов.
По соглашению имя тестового метода описывает то, что делает тест, а имя клас­
са
-
то, что подвергается тестированию. Это облегчает структурирование тестов в
проекте и упрощает понимание того, какими будут результаты всех тестов, когда они
прогоняются средой
тесты для класса
Visual Studio.
Product,
Имя
ProductTests
можность изменения названия и цены объекта
Атрибут
Fact
указывает, что класс содержит
а имена методов говорят о том, что они проверяют воз­
Product.
применяется к каждому методу, указывая на то, что метод является
тестом. Внутри тела метода модульный тест следует шаблону, который называется
организация/действие/утверждение
(arrange/act/assert -А/А/А). Организация от­
- к выполнению теста, а утверж­
носится к настройке условий для теста, действие
дение
-
к проверке того, что результат оказался тем. который ожидали.
Разделы организации и действия этих тестов представляют собой обычный код
С#, но раздел утверждения обрабатывается инфраструктурой
Assert,
доставляет класс по имени
xUnit.net,
которая пре­
чьи методы используются для проверки. является
ли результат действия тем, что ожидался.
Совет. Атрибут
Fact
и класс
Assert
определены в пространстве имен
должен быть предусмотрен оператор
Методы класса
Assert
using
Xuni t,
для которого
в каждом тестовом классе.
определены как статические и применяются для выполне­
ния разных видов сравнений между ожидаемыми и действительными результатами.
В табл.
7.3
описаны распространенные методы класса
Каждый метод класса
Assert
Assert.
позволяет выполнять разные виды сравнений
и генерирует исключение, если результат оказывается не тем, который ожидался.
Исключение применяется для указания на то, что тест не прошел. В тестах из лис­
тинга
7.5
метод
Equal ()
использовался для определения. корректно ли изменилось
значение свойства:
Assert.Equal("New Name", p.Name);
Глава 7. Модульное тестирование приложений MVC
Таблица
7 .3.
Часто используемые методы класса
185
Assert из инфраструктуры xUnit.net
Метод
Описание
Equal(expected,
resul t)
Добавляет утверждение о том, что результат равен ожидаемо­
му исходу. Существуют перегруженные версии этого метода
для сравнения различных типов и для сравнения коллекций.
Имеется также версия, которая принимает дополнитель-
ный аргумент в форме объекта, реализующего интерфейс
IEqualityComparer<T> для
сравнения объектов
NotEqual(expected,
result)
Добавляет утверждение о том, что результат не равен ожидае­
True(result)
Добавляет утверждение о том, что результат равен
t rue
False (result)
Добавляет утверждение о том, что результат равен
false
I s Type(expected,
result)
Добавляет утверждение о том, что результат принадлежит
IsNotType(expected,
result)
Добавляет утверждение о том, что результат не принадлежит
IsNull (result)
Добавляет утверждение о том, что результат равен
IsNotNull(result)
Добавляет утверждение о том, что результат не равен
InRa nge (result, l ow,
high)
Добавляет утверждение о том, что результат находится между
No tinRange(resul t ,
l ow, high)
Добавляет утверждение о том, что результат не находится
Throws(exception,
expre s sion)
Добавляет утверждение о том, что указанное выражение гене­
мому исходу
указанному типу
указанному типу
low
и
между
null
null
high
l ow и high
рирует исключение заданного типа
Прогон тестов с помощью окна
Test Explorer
Среда
Visual Studio преДТiагает поддержку ДТIЯ поиска и прогона модульных тестов
посредством окна Test Explorer (Проводник тестов), которое доступно через пункт меню
Testc:::>Windowsc:::>Test Explorer (Тестс:::>Окнас:::>Проводник тестов) и показано на рис. 7.4.
•CJX
itst Explore.r
___________ Test Explorer
l,.a.:;
•.;l;
~=;.·.;5;..S
;,;
•'
;"
.;.
"
(-•
Н:
.3
•DX
Р·
Sei!lrth
Rtm Atl 1 Run~. .... 1 f)t.,yfist : АИ T~ts ..,
~
Failed Tests (1)
О Working\V1thVisui!llStudio.Tests.ProductTests.(4nChangeProductPnce 11 ms
---------1-- А Pиsed Tests (1)
(#
WorkingWithV1sualStudio.T~ts.ProductTests.CanChi!lngeProductN!me
Summary
t..rt Test Run fallod (Тot•I Run Time 0:0001.9799989)
~ 1 Test F4ifed
ti 1 Test P4ssed
Рис.
7.4.
Окно
Test Explorer
в
Visual Studio
4 ms
186
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Совет. Если вы не видите модульные тесты в окне
Test Explorer, тогда постройте реше­
ние. Компиляция инициирует процесс, с помощью которого обнаруживаются модульные
тесты.
Выполним тесты. щелкнув на пункте Ruп
Среда
Vlsual Studio
применит
xUnit.net
зультаты. Как отмечалось, тест
All
(Выполнить все) в окне
Test Explorer.
для прогона тестов в проекте и отобразит ре­
CanChangeProductPrice ()
содержит ошибку, ко­
торая приводит к тому, что он не проходит. Проблема связана с аргументом метода
Assert. Equal (),
Price, а не со
тва
из-за чего происходит сравнение с исходным значением свойс­
значением, на которое оно изменилось. В листинге
7.6
проблема
устранена.
Совет. Когда тест не проходит, то прежде чем просматривать компонент, на который он на­
целен, всегда полезно проверить правильность самого теста, особенно если тест только
что написан или недавно модифицировался.
Листинг
7.6.
Исправление теста в файле Productтests.
cs
из папки
WorkingWithVisualStudio.Tests
using WorkingWithVisualStudio.Models;
using Xunit;
narnespace WorkingWithVisualStudio.Tests
class ProductTests {
puЫic
[Fact]
puЫic
11
Организация
var
11
void CanChangeProductNarne()
р
= new Product { Narne = "Test", Price
p.Narne
11
lOOM };
Действие
=
"New Narne";
Утверждение
Assert.Equal("New Narne", p.Narne);
[Fact]
puЫic
11
Организация
var
11
void CanChangeProductPrice() {
р =
=
"Test", Price
lOOM };
Действие
p.Price
11
new Product { Narne
= 200М;
Утверждение
Assert.Equal(200M, p.Price);
}
При наличии большого количества тестов выполнение их всех может занять неко­
торое время. Таким образом, чтобы можно было работать быстро и итера'tивно, в окне
Глава 7. Модульное тестирование приложений MVC
Test Explorer
187
предлагаются различные варианты для выбора подмножества тестов.
подлежащих прогону. Самым полезным подмножеством является набор тестов. кото­
рые не прошли (рис.
7.5).
Запустим исправленный тест снова. и в окне
Test Explorer
будет показано, что не прошедшие тесты отсутствуют.
Tost Explorer
\:~
• Ei
Seшh
Run А11 1 Run.
"' Failod Т
0Tests
А Passed 1
$ Tests)
1 P!aylJ>t: All Tests •
Run f•iled Tests
11 ... р
Run Not Run Tests
б
Run P•ssed Tests
Tests.ProductTests.CanChangeProductPrrce
•
Ropoat Last Run
,
~
С•
0 1
ems
".
Ctrl+R. L
Surпmary
Summary
Last Tm Run Failed (Total Run T1me 0;00:04}
О 1 Test Failed
& 1 Test P•ssed
Рис.
Last Tffi Run Passed (ТоU.1 Run T1me 0;00:02}
1 Test P•ssed
$
7.5.
Избирательный прогон тестов
Изолирование компонентов для модульного тестирования
Написание модульных те стов для клас с ов моделей вроде
тью не отличается. Класс
Produc t
Produ c t
особой сложнос­
не тол ько прост. он также самодостаточен, т.е. при
выполнении какого-то действия над объектом
Pr o d uct
можно иметь уверенность в
том, что тестируется функциональность. предоставля е мая классом
Product.
С другими компонентами в приложении МVС ситуация сложнее, потому что между
ними есть зависимости. Следующий набор определяемых тестов будет оперировать
на контроллере . исследуя последовательность объектов
Product ,
которая передается
между контр оллером и пр едставлением .
При сравнении объектов, являющихс я э кземплярами специальных классов , пона­
добится использовать метод
Assert. Equa l () из xUnit.net,
IEqu a li tyCornpar er< T>,
гумент, реализующий инт е рфейс
который принимает ар­
так что объекты можно
сравнивать . Сн а чала необходимо добавить в проект модульного те стирования файл
класса по имени
Cornpa r er . cs
классов, приведенные в листинге
Листинг
и поместить в него определения вспомогательных
7.7.
7. 7. Содержимое файла Comparer. cs из папки
WorkingWithVisualStudio . Tests
using System ;
using System.Co l lec t ions . Ge n e ri c ;
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
188
namespace WorkingWithVisualStudio.Tests (
class Comparer (
static Comparer<U> Get<U>(Func<U, U, bool> func)
return new Comparer<U>(func);
puЫic
puЫic
(
class Comparer<T> : Comparer, IEqualityComparer<T>
private Func<T, Т, bool> comparisonFunction;
puЫic
Comparer(Func<T, Т, bool> func)
func;
comparisonFunction
puЫic
bool Equals (Т х, Т у) (
return comparisonFunction(x,
(
puЫic
у);
int GetHashCode(T obj)
return obj .GetHashCode();
puЫic
Показанные классы позволят создавать объекты
IEqualityComparer<T>
с при­
менением лямбда-выражений вместо определения нового класса для каждого типа
сравнения. которое нужно предпринимать. Это не является жизненно необходимым.
но упростит код в классах модульных тестов и сделает его легче для понимания и
сопровождения.
Теперь. когда можно легко делать сравнения, давайте рассмотрим проблему зави­
симостей между компонентами в приложении.
Добавим в проект
имени
WorkingWi thVisualStudio. Tests новый файл класса по
HomeControllerTests. cs и поместим в него определение модульного теста.
представленное в листинге
Листинг
7.8.
Содержимое файла
из папки
using
using
using
using
using
7.8.
HomeControllerTests. cs
WorkingWi thVisualStudio. Tests
Microsoft.AspNetCore.Mvc;
System.Collections.Generic;
WorkingWithVisualStudio.Controllers;
WorkingWithVisualStudio.Models;
Xunit;
namespace WorkingWithVisualStudio.Tests
puЫic class HomeControllerTests (
[Fact]
puЫic
void IndexActionModellsComplete()
/ / Организация
var controller = new HomeController();
11
Действие
var model = (controller.Index() as ViewResult) ?.ViewData.Model
as IEnumeraЬle<Product>;
Глава 7. Модульное тестирование nриложений MVC
11
189
Утверждение
Assert.Equal(SiтpleRepository.SharedRepository.Products,
(pl, р2) =>
&& pl.Price == p2.Price));
Coтparer.Get<Product>(
Модульный тест в листинге
7.8
pl.Naтe
==
тodel,
р2.Nате
проверяет, что метод действия
Index ()
передает
представлению все объекты в хранилище. (На раздел действия можно пока не обра­
щать внимания; класс
ViewResul t
будут объясняться в главе
17.
и роль, которую он играет в приложениях
MVC,
В настоящий момент достаточно знать, что здесь полу­
чаются данные модели, возвращаемые методом действия
Index () .)
Запустив тест, можно заметить. что он не проходит, указывая на отличие между на­
бором объектов в хранилище и набором объектов, которые возвратил метод
Index () .
Однако когда дело доходит до выявления причины, из-за чего тест не прошел, воз­
никает проблема: предполагается, что тест действует на контроллере Ноте, но класс
контроллера зависит от класса SiтpleRepository. Данный факт затрудняет выяс­
нение, отразил ли тест проблему с классом, на который он нацелен, или же проблему
в другой части приложения.
Пример приложения достаточно прост, чтобы можно было легко выявить про­
блему, всего лишь взглянув на код классов
HomeController
и
SimpleRepository.
В реальном приложении визуальный осмотр не настолько прост, т.к. цепочка зави­
симостей способна затруднить понимание того, что привело к отказу в прохождении
теста. Обычно хранилище будет полагаться на какую-то разновидность системы пос­
тоянного хранения, подобную базе данных, а также библиотеку, которая предоставля­
ет к ней доступ. Модульный тест может взаимодействовать со всей цепочкой сложных
компонентов, любой из которых может вызвать проблему.
Модульные тесты эффективны, когда они нацелены на небольшие части приложе­
ния, такие как отдельный метод или класс. Нам необходима возможность изоляции
контроллера Ноте от остальной части приложения, чтобы можно было ограничить
область действия теста и исключить влияние со стороны хранилища.
Изолирование компонента
Ключом к изолированию компонентов является использование интерфейсов
С#. Чтобы отделить контроллер от хранилища, добавим в папку
файл по имени
листинге
Листинг
IReposi tory. cs
7.9.
7.9.
Содержимое файла
IRepository.cs
using System.Collections.Generic;
namespace WorkingWithVisualStudio.Models
interface IRepository {
Products { get; )
void AddProduct(Product р);
puЫic
IEnumeraЫe<Product>
Models
новый
с определением интерфейса, которое показано в
из папки Мodels
190
Часть 1. Введение в инфраструктуру ASP.NEТ Core MVC 2
С интерфейсом
IRepository
не связано ничего примечательного (за исключе­
нием того, что в нем не определен полный набор операций, который обычно будет
нужен в веб-приложении; более реалистичный и завершенный пример можно найти
в главе
8).
Тем не менее, добавление интерфейса вроде
IRepository
позволяет лег­
ко изолировать компонент для тестирования. Первым делом нужно обновить класс
SimpleReposi tory.
Листинг
7. 1О.
чтобы он реализовывал новый интерфейс (листинг
Реализация интерфейса в файпе
Si.mpleReposi tory. cs
7.10).
из папки
Model s
using System.Collections.Generic;
namespace WorkingWithVisualStudio.Models
puЫic class SimpleRepository : IRepository
private static SimpleRepository sharedRepository
SimpleRepository();
private Dictionary<string, Product> products
= new Dictionary<string, Product>();
puЫic
new
static SimpleRepository SharedRepository => sharedRepository;
puЫic
SimpleRepository() {
var initialitems = new[] {
newProduct{Name
"Kayak",
new Product { Name
"Lifejacket",
new Product { Name
"Soccer ball",
new Product { Name
"Corner flag",
Price=275M
),
Price
48.95М ),
Price
19.50М ),
Price
34.95М)
);
foreach (var р in initialltems) {
AddProduct(p);
products.Add("Error", null);
puЫic IEnumeraЬle<Product>
puЬlic
Products => products.Values;
void AddProduct(Product
р)
=> products.Add(p.Name,
р);
Следующий шаг предусматривает модификацию контроллера. чтобы свойство,
применяемое для ссылки на хранилище, использовало интерфейс, а не класс (лис­
тинг
7. l l).
Совет. Инфраструктура
ASP.NET Core MVC
поддерживает более элегантный подход к реше­
нию такой проблемы, который называется внедрением зависимостей и описан в главе
18.
Внедрение зависимостей зачастую вызывает путаницу, поэтому в настоящей главе компо­
ненты изолируются более простым и ручным способом.
Листинг
7.11.
Добавление свойства
из папки
Reposi tory
Controllers
using Microsoft.AspNetCore.Mvc;
using WorkingWithVisualStudio.Models;
using System.Linq;
в файле
HomeController. cs
Глава 7. Модульное тестирование приложений MVC
191
narnespace WorkingWithVisualStudio.Controllers {
puЫic class HorneController : Controller {
= SiшpleReposi tory. SharedReposi tory;
puЬlic
IReposi tory Reposi tory
puЫic
IActionResult Index() => View(Repository.Products
. Where (р => р?. Price < 50) ) ;
[HttpGet]
IActionResult AddProduct() => View();
puЫic
[HttpPost]
IActionResult AddProduct(Product
Repository.AddProduct(p);
return RedirectT0Action( 11 Index 11 ) ;
puЫic
р)
{
Модификация может выглядеть незначительной, но она позволяет изменять хра­
нилище, которое контроллер применяет во время тестирования. что демонстрирует
способ изоляции контроллера. В листинге
7 .12
модульные тесты контроллера обнов­
лены, так что они используют специальную версию хранилища.
Листинг
7 .12.
Изоляция контроллера в модульном тесте внутри файла
HomeControllerTests. cs
using
using
using
using
using
из папки
Controllers
Microsoft.AspNetCore.Mvc;
Systern.Collections.Generic;
WorkingWithVisualStudio.Controllers;
WorkingWithVisualStudio.Models;
Xuni t;
narnespace WorkingWithVisualStudio.Tests
puЫic class HorneControllerTests {
class
ModelCoшpleteFakeRepository
puЬlic IEnwneraЫe<Product>
new
new
new
new
Product
Product
Product
Product
{
{
{
{
Nаше
Nаше
Nаше
Nаше
=
=
=
=
11
Pl 11
,
11
Р2 11
,
11
РЗ 11 ,
11
РЗ 11
,
: IRepository
Products { get; } = new Product [ ]
},
Price = 275М
Price = 48. 95М } ,
Price = 19. SOM } ,
Price = 34 . 95М }
} ;
puЬlic
void AddProduct {Product
р)
{
/ / Ничего не делать - для теста не требуется
[Fact]
puЫic
void IndexActionModelisCornplete()
//Организация
var controller = new HorneController();
controller.Repository
= new
ModelCoшpleteFakeRepository();
11 Действие
var rnodel = (controller.Index() as ViewResult)?.ViewData.Model
as IEnurneraЫe<Product>;
192
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
11
Утверждение
Assert.Equal(controller.Repository.Products, шodel,
Comparer.Get<Product>( (pl, р2) => pl.Name == p2.Name
&& pl. Price == р2. Price));
Мы определили фиктивную реализацию интерфейса
IReposi tory,
в которой при­
сутствует только свойство, необходимое для теста. и всегда применяются согласован­
ные данные (чего может не быть при работе с реальной базой данных. особенно в
случае ее совместного использования с другими разработчиками, вносящими собс­
твенные изменения).
Исправленный модульный тест по-прежнему не проходит. указывая на то, что при­
чиной проблемы является метод действия
Index ()
в классе
HomeController,
а не
компоненты, от которых он зависит. Метод действия. вызываемый модульным тес­
том, в достаточной степени прост для того, чтобы проблема стала очевидной в ре­
зультате его осмотра:
puЫic IActionResult Index() => View(Repository.Products.Where(p =>
p.Price < 50));
Проблема связана с применением метода
ся для фильтрации объектов
Product
Where ()
из
LINQ,
со значением свойства
который использует­
Price,
равным
50
или
больше. Здесь уже есть серьезное указание на причину проблемы, но передовой опыт
предполагает создание теста, который подтвердит наличие проблемы, прежде чем
вносить корректирующую правку (листинг
7.13).
Совет. В показанных тестах присутствует много дублированного кода. В следующем разделе
объясняется, как упростить тесты.
Листинг
7.13.
Добавление теста в файле HomeControllerTests.cs
из папки
using
using
using
using
using
WorkingWi thVisualStudio. Tests
Microsoft.AspNetCore.Mvc;
System.Collections.Generic;
WorkingWithVisualStudio.Controllers;
WorkingWithVisualStudio.Models;
Xunit;
namespace WorkingWithVisualStudio.Tests
puЫic class HomeControllerTests (
class ModelCompleteFakeRepository : IRepository
Products { get; 1 = new Product[] {
"Pl", Price
275М ),
"Р2", Price
48.95М },
"РЗ", Price
19.SOM ),
"РЗ", Price
34.95М}
puЫic IEnumeraЫe<Product>
new
new
new
new
);
Product
Product
Product
Product
(
{
{
{
Name
Name
Name
Name
Глава 7. Модульное тестирование приложений MVC
puЫic
11
void AddProduct(Product р) {
- для теста не
Ничего не делать
193
требуется
[Fact]
void IndexActionModelisComplete()
11 Организация
var controller = new HomeController{);
controller.Repository = new ModelCompleteFakeRepository();
puЬlic
11 Действие
var model = (controller.Index() as ViewResult)?.ViewData.Model
as IEnumeraЬle<Product>;
11 Утверждение
Assert.Equal(controller.Repository.Products, model,
Comparer.Get<Product>( (pl, р2) => pl.Name == p2.Name
&& pl.Price == p2.Price));
class
МodelCompleteFakeRepositoryPricesUnderSO
puЫic IEnumeraЫe<Product>
new
new
new
new
Product
Product
Product
Product
{
{
{
{
Name
Name
Name
Name
=
=
=
=
11
Pl 11
11
Р2 11 ,
11
РЗ 11 ,
11
РЗ 11
,
,
: IRepository {
Products { get; } = new Product []
Price = SM } ,
Price = 48. 95М } ,
Price = 19. SOM } ,
Price = 34 . 95М }
} ;
puЬlic
void AddProduct (Product
р)
{
/ / Ничего не делать - для теста не требуется
[Fact]
puЬlic
void
IndexActionМodelisCompletePricesUnderSO()
/ / Организация
var controller = new HomeController();
controller.Repository = new
МodelCompleteFakeRepositoryPricesUnderSO();
//Действие
var model = (controller.Index() as ViewResult)?.ViewData.Model
as IEnumeraЬle<Product>;
/ / Утвер-.цение
Assert.Equal(controller.Repository.Products, model,
Comparer. Get<Product> ( (pl, р2) => pl . Name = р2 . Name
&& pl. Price = р2. Price) ) ;
Мы определили фиктивное хранилище, которое содержит только объекты
со значениями свойства
Price
меньше
50,
Product
и применили его в новом тесте. Запустив
тест, можно заметить, что он проходит, подкрепляя идею о том, что проблема связана
с применением метода
Where ()
в методе действия
Index () .
194
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
В реальном проекте выяснение причины отказа теста означает необходимость со­
гласования цели теста со спецификацией для приложения. Вполне может оказаться.
что метод
Index ()
обязан фильтровать объекты
Product
по свойству
Price,
в случае
чего тест нуждается в пересмотре. Это распространенный итог, и тест, который не
прошел, вовсе не всегда указывает на присутствие реальной проблемы в приложении.
С другой стороны, если метод действия
Index ()
не должен фильтровать объекты мо­
дели, тогда потребуется внести корректирующую правку (листинг
7.14).
Понятие разработки через тестирование
В настоящей главе я придерживаюсь наиболее часто используемого стиля модульного тес­
тирования, при котором мы пишем функцию приложения и затем тестируем ее, чтобы удос­
товериться в том, что она работает требуемым образом. Такой стиль популярен, поскольку
большинство разработчиков думают сначала о прикладном коде, а затем о его тестирова­
нии (я определенно подпадаю под эту категорию).
Проблема такого подхода в том, что он склоняет к созданию модульных тестов только для
того прикладного кода, который было трудно писать или сложно отлаживать, оставляя ряд ас­
пектов каких-то функций лишь частично протестированными или вообще без тестирования.
Альтернативным
Developmeпt
подходом
- TDD).
является
разработка
Существует много вариаций
через
TDD,
тестирование
(Test-Driveп
но основная идея в том, что тесты
для функции пишутся до того, как она реализуется. Написание тестов первыми заставля­
ет более тщательно думать о реализуемой спецификации и о том, как узнать, что функция
была реализована корректно. Вместо погружения в детали реализации подход
TDD застав­
ляет заранее продумывать, чем будет измеряться успех или неудача.
Все создаваемые тесты изначально не будут проходить, т.к. новая функция еще не реали­
зована. Однако по мере добавления кода в приложение тесты постепенно будут переходить
из красного состояния в зеленое, и ко времени завершения функции все тесты окажутся
пройденными. Подход
TDD требует дисциплины, но приводит к получению более полного
набора тестов и может дать в результате более надежный и устойчивый код.
Листинг
7.14.
Удаление фильтрации посредством
из папки
LINQ
в файле
HomeController. cs
Controllers
using Microsoft.AspNetCore.Mvc;
using WorkingWithVisualStudio.Models;
using System.Linq;
namespace WorkingWithVisualStudio.Controllers
puЫic class HomeController : Controller (
puЫic IRepository Repository = SimpleRepository.SharedRepository;
puЫic
IActionResul t Index () => View (Reposi tory. Products) ;
[HttpGet)
puЫic IActionResult AddProduct() => View(new Product());
[HttpPost)
IActionResult AddProduct(Product
Repository.AddProduct(p);
return RedirectToAction("Index");
puЫic
р)
{
Глава 7. Модульное тестирование приложений MVC
Запустив тесты снова. легко заметить. что все они проходят (рис.
Тest
С:-11> 1~:
"S
Run All
1 Run". ... 1 Playlist : All Tests •
.А
7.6) .
.... !:!
Explorer
195
х
Search
Passed Tests (4)
t) \.Yorking~/ithVisualStudio Tests.Hon eCon rollerTes s.lndexActio11Modell". 41 ms
t) WorkingWithVisualStudio.Tests.Hon eControllerTes s.lndexActio11Modells". 2 ms
t) \NorkingWithVisualStudio.Tests.ProductTбts.CanChangeProduc ame
1 ms
&
Norking~/ithVisualStudio Tests.Prod с Tests CanChangeProductPrice
12 ms
Su пнnary
Last Test Run Passed (Тotal Run Time ~00:0 5590048)
t.) 4 Tests Oassed
Рис.
7.6.
Прохождение всех тестов
Может показаться, что мы проделали слишком много работы для устранения та­
кой простой проблемы, однако возможность протестировать отдельный компонент
жизненно важна при построении реального приложения. Достичь точки. где пробле­
ма идентифицирована и написаны тесты для проверки исправления , удастся лишь
тогда. когда вы способны эффективно изолировать компоненты.
Улучшение модульных тестов
В предыдущем разделе был представлен базовый подход к написанию модульных
тестов и прогону тестов в
Visual Studio,
а также акцентировано внимание на важ­
ности изоляции тестируемого компонента. В этом разделе рассматриваются расши­
ренные инструменты и средства.
которые можно применять для написания тестов
более согласованным и выразительным образом. Если вы сильно увлечетесь модуль­
ным тестированием, то можете в итоге получить настолько много тестового кода. что
его ясность становится очень важной , особенно с учетом необходимости пересмотра
тестов для отражения изменений в приложении, вносимых во время разработки и
сопровождения.
Параметризация модульного теста
Тесты, написанные для класса
HorneCon tro ll e r.
выявили проблему . которая при ­
сутствовала только для определенных значений данных. Чтобы проверить такое ус­
ловие, были созданы два похожих теста. каждый из которых содержал собственное
фиктивное хранилище. При таком подходе появляется дублированный код. тем бо­
лее с учетом того, что единственное отличие между двумя тестами связано с набором
значений
de c irna l,
которые указываются для свойства
фиктивных хранилищах .
Pr i c e
объектов
Produ c t
в
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
196
Инфраструктура
xUnlt.net
предлагает поддержку параметризованных тестов.
когда данные, используемые в тесте. из него удаляются. так что единственный ме­
тод может применяться для множества тестов. В листинге
7. 15
с помощью средс­
тва параметризованных тестов устраняется дублированный код в тестах для класса
HomeCon troller.
Листинг
7.15.
Параметриэация модульного теста в файле HomeControllerTests.cs
из папки
using
using
using
using
us ing
WorkingWi thVisualStudio. Tests
Microsoft.AspNetCore.Mvc;
System.Collections.Generic;
WorkingWithVisualStudio.Controllers;
WorkingWithVisualStudio.Models;
Xuni t;
namespace WorkingWithVisualStudio.Tests
puЫic class HomeControllerTests (
class ModelCompleteFakeRepository : IRepository
puЬlic 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Ьlic void IndexActionМodelisComplete(decimal pricel, decimal price2,
decimal priceЗ, decimal price4)
11 Организация
var controller = new HomeController();
controller.Repository = newModelCompleteFakeRepository
Products = new Product [] {
new Product {Name = "Pl", Price = pricel } '
new Product {Name
"Р2", Price = price2
}'
new Product {Name
"РЗ", Price = priceЗ
} '
new Product {Name
"Р4", Price = price4
}'
=
=
=
} ;
11 Действие
var model
(controller.Index{) as ViewResult)?.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));
Глава 7. Модульное те стирование nриложений MVC
Параметризованные модульные тесты помечаются атрибутом
рибута
вместо ат­
который используется для стандартных тестов . Здесь также применя­
Fact,
ется атрибут
Th eory
197
InlineDat.a,
который позволяет указывать значения для аргументов,
определяемых методом модульного теста. Язык С# ограничивает способ выражения
значений данных в атрибутах, поэтому мы определили четыре аргумента
тестового метода и посредством атрибута
Inline Data
decimal
предоставили для них значе­
ния. Значения
тестового метода используются для генерации масси ­
ва объектов
применяется для установки свойства
de c i mal внутри
Pr o du c t, который
Produ cts
объ­
екта фиктивного хранилища.
Каждый атрибут
InlineData
определяет отдельный модульный тест, который по­
казан как индивидуальный элемент в окне
Запись в окне
Test Explorer
Test Explorer среды Visual Studlo (рис . 7. 7).
отображает значения . которые будут использоваться для
аргументов метода модульного теста.
• !:!
х
Р -
Rul'IAIJ 1 Run... • 1 P.•yt.U:All'Тt1b •
Summary
~ Pas.иd Tкts (<1}
$
&
WoНcfn9\Vid\Vi1ualStWio.Т..1.1.HorмCOF1VolltrTeJts.lnc!.xAФonModt!ll~p~eo(ptic•11275, рпсе2: 48,95, pt1(.e): 10.S, pr.c.4: lol.95)
WorЩWith\li1ualStudio.Tests.Нome<:ontroll~T1tsts.ll'ldtxActюnМode!l,Comp~pric:elr S. priu2: 48.95, priceЗ: 19.S. pr1e~: 24.9S)
-40 rns
1 ms
~ Workint;W1thl/iJu•!Studio.1'e-sts.ProductТtotts.C.neh&~ProductNaiм
1 ms
f) Worktng\V'ltt\V11ua1Studic.1tsts.Ptoduuit1ts.UinCNi1'941PтodLКWnte
11 rns
Рис.
7.7.
Параметризованные тесты в окне
' t..ast Tttt Run P•sstd (Тot.11 R.un Ttmt O:OQ.i)t,
t) 4 т~s Piutd
Test Explorer среды Visual Studio
Получение тестовых даннь1х из метода или свойства
Ограничения . налагаемые на выражение данных в атрибутах, сокращают по­
лезность атрибута
Inl i n e Da ta .
Альтернативный подход предусматривает создани е
статического метода или свойства, возвращающего объект, который требуется для
тестирования. В такой ситуации нет никаких ограничений относительно того , как
определяются данные, и можно создавать более широкий диапазон тестовых значе­
ний . Чтобы продемонстрировать подход в работе, добавим в проект модульного тес­
тирования файл класса по имени
в листинге
Листинг
Produc t TestDa ta . cs
с определением . показанным
7.16.
7 .16.
Содержимое файла ProductTes tDa ta. cs
WorkingWithVisualStudio.Tests
из папки
us ing System. Collec ti on s;
us ing System. Collecti o ns.Gene ric;
us ing Wo rkingWith Vi sual St ud io . Mode l s ;
name s p ace Wor ki n gW i thV i sualSt udio . Tes t s
p u Ы i c class Pr oductTestData : IEn u meraЬle < object[]>
puЫic IEn umerator<object[ ] > GetEnumerator() {
yield retur n new obje c t[J { Ge t Pr ic es UnderSO() );
yield re t ur n n ew objec t[ ] { GetPricesOverS O };
I Enumera t or IEnu mera Ы e.Ge t Enumerator()
return this . GetEnumerator() ;
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
198
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 [] GetPricesOverSO => new Product[J
new Product { Name
"Pl", Price
5 },
new Product ( Name
"Р2", Price
48.95М } ,
new Product ( Name
"РЗ", Price
19.SOM ) ,
new Product ( Name
"Р4 ", Price
24.95М }
};
Тестовые данные предоставляются через класс, который реализует интерфейс
IEnumeraЫe<object []>.возвращающий последовательность массивов объектов.
Каждый массив объектов в последовательности содержит один набор аргументов. ко­
торые будут передаваться тестовому методу. Мы переопределим тестовый метод. что­
бы он принимал массив объектов
один уровень
-
Product.
который добавит к тестовым данным еще
перечисление массивов объектов
Product.
Такая глубина структуры
тестовых данных может сбивать с толку. но важно понимать, что тесты не будут ра­
ботать, если количество аргументов, которые
xUnit.net
пытается передать тестовому
методу, не соответствует сигнатуре метода.
Мне нравится структурировать классы тестовых данных так. чтобы закрытые ме­
тоды или свойства определяли индивидуальные наборы тестовых данных, которые
затем с помощью метода
GetEnumerator ()
объединялись в последовательности мас­
сивов объектов. Для иллюстрации различных приемов массивы объектов
Product
были созданы с применением и метода. и свойства. но я предпочитаю использовать в
своих проектах один подход (на его выбор влияет вид данных, с которыми проводится
тестирование). В листинге
ньхх с атрибутом
Theory
7.17
показано, как можно применять класс тестовых дан­
для настройки тестов.
Совет. Если вы хотите включить тестовые данные в тот же самый класс, который определяет
модульные тесты, тогда можете использовать атрибут MemЬerData вместо
Атрибут
MemberData
ClassData.
конфигурируется с применением строки, указывающей имя стати­
ческого метода, который будет предоставлять реализацию
I En ume r аЫ е< оЬ j
ее t
[ ] >,
где каждый массив объектов в последовательности является набором аргументов для тес­
тового метода.
Листинг
7. 17.
Использование класса тестовых данных в файле
HomeControllerTests. cs
using
using
using
using
using
из папки
WorkingWi thVisualStudio. Tests
Microsoft.AspNetCore.Mvc;
System.Collections.Generic;
WorkingWithVisualStudio.Controllers;
WorkingWithVisualStudio.Models;
Xunit;
Глава 7. Модульное тестирование приложений MVC
199
namespace WorkingWithVisualStudio.Tests {
puЫic class HomeControllerTests {
class ModelCompleteFakeRepository : IRepository
puЬlic IEnumeraЫe<Product>
puЫic
11
Products { get; set;
void AddProduct(Product
Ничего не делать
-
{
р)
для теста не требуется
(Theory)
[ClassData(typeof(ProductTestData))]
puЫic void IndexActionМodelisComplete(Product[] products) {
11 Организация
var controller = new HomeController();
controller.Repository = new ModelCompleteFakeRepository
Products
= products
};
11 Действие
(controller.Index() as ViewResult)?.ViewData.Model
var 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
конфигурируется с типом класса тестовых данных, которым
в рассматриваемом случае является
инфраструктура
xUnit.net
ProductTestData.
Во время выполнения тестов
создаст новый экземпляр класса
ProductTestData,
пос­
ле чего будет применять его для получения последовательности тестовых данных для
теста.
На заметку! Если вы посмотрите на список тестов в окне
IndexActionModelisComplete ()
ProductTestData предоставляет два
Test Explorer,
то увидите, что для
тестов
предусмотрена единственная запись, хотя
класс
набора тестовых данных. Так происходит в
ситуации, когда объекты тестовых данных не удается сериализировать, и проблему можно
решить, применив к тестовым объектам атрибут SerializaЫe.
Улучшение фиктивных реализаций
Эффективная изоляция компонентов требует фиктивных реализаций классов, что­
бы предоставить тестовые данные или проверить, ведет ли себя компонент так, как
должен. В предшествующих примерах создавался класс, реализующий интерфейс
IReposi tory.
Такой подход может быть эффективным, но он приводит к созданию
классов реализаций для каждого вида теста, который желательно прогнать. В качес­
тве примера в листинге
демонстрируется добавление теста, который проверяет,
что метод действия
вызывает метод
7.18
Index ()
Products ()
в хранилище только один
200
Часть 1. Введение в инфраструктуру ASP.NEТ Соге MVC 2
раз. (Тест подобного вида распространен, когда имеется опасение, что компонент де­
лает дублирующиеся запросы к хранилищу, становясь причиной множества запросов
к базе данных.)
Листинг
7 .18.
Добавление модульного теста в файле
из папки
using
using
using
using
using
using
HomeControllerTests. cs
WorkingWi thVisualStudio. Tests
Microsoft.AspNetCore.Mvc;
System.Collections.Generic;
WorkingWithVisualStudio.Controllers;
WorkingWithVisualStudio.Models;
Xunit;
System;
namespace WorkingWithVisualStudio.Tests
puЫic class HomeControllerTests {
class ModelCompleteFakeRepository : IRepository
puЬlic IEnumeraЬle<Product>
puЫic
11
Products ( get; set;
void AddProduct(Product р) {
- для теста не
Ничего не делать
требуется
[Theory]
[ClassData(typeof(ProductTestData))]
puЬlic void IndexActionModelisComplete(Product[] products ) {
11
Организация
var controller = new HomeController();
controller.Repository = new ModelCompleteFakeRepository
Products = products
};
11
Действие
var model
(controller.Index() as ViewResult)?.ViewData.Model
as
11
IEnumeraЬle<Product>;
Утверждение
Assert.Equal(controller.Repository.Products, model,
Comparer. Get<Product> ( (pl, р2) => pl. Name == р2. Name
&& pl.Price == p2.Price));
class PropertyOnceFakeRepository : IRepository
puЫic int PropertyCounter { get; set; } =О;
puЬlic IEnumeraЫe<Product>
Products
get {
PropertyCounter++;
return new [] { new Product { Name = "Pl" , Price
puЬlic
void AddProduct (Product
р}
/ / Ничего не депать - для теста не требуется
= 100
} };
Глава
7. Модульное тестирование приложений MVC
201
[Fact]
puЫic
void RepositoryPropertyCalledOnce()
//Организация
var repo = new PropertyOnceFakeRepository();
var controller = new HomeController ( Reposi tory
= repo
};
//Действие
var resul t
= controller. Index ()
;
/ / Утверждение
Assert.Equal(l, repo.PropertyCounter);
}
Фиктивные реализации
-
не всегда простые источники данных; они также могут
использоваться для оценки способа, которым компоненты выполняют свою работу.
В этом случае было добавлено простое свойство счетчика, которое увеличивается
каждый раз. когда читается свойство
метода
Assert. Equal ()
Products
фиктивного хранилища. и с помощью
выполнена проверка, что обращение к свойству производи­
лось только один раз.
Добавление инфраструктуры имитации
Создание фиктивных объектов такого рода выходит из-под контроля. и лучший
способ вернуть себе контроль
-
применить инфраструктуру фиктивных объектов,
также называемую инфраструктурой имитац~щ. (Существует формальная разница
между фиктивными и имитированными объектами, но для облегчения использования
современные инструменты тестирования размывают ее, а потому данные термины
будут применяться взаимозаменяемо.) В главе используется инфраструктура
торая описана в табл.
Таблица
7 .4.
Moq, ко­
7.4.
Помещение
Moq
Вопрос
Ответ
Что это такое?
Moq -
в контекст
зто программный пакет для создания фиктивных реализа­
ций компонентов в приложении
Чем она полезна?
Инфраструктура имитации облегчает создание фиктивных ком­
понентов с целью изоляции частей приложения для модульного
тестирования
применяет лямбда-выражения для определения функциональ­
Как она
Moq
используется?
ности фиктивных компонентов и требует определения только тех
средств, которые используются при тестировании
Существуют ли
Привыкание к синтаксису может потребовать некоторых усилий.
какие-то скры­
Документация и примеры находятся по адресу
тые ловушки или
github.com/Moq/moq4
h t tps : / /
ограничения?
Существуют ли
Доступно несколько альтернативных инфраструктур, в том числе
альтернативы?
NSubstitute (https: / /nsubstitute. github. io) и FakeltEasy
(https: / /fakeiteasy. github. io/). Все они предлагают похо­
жие возможности, и выбор между ними связан только с предпочи­
таемым вами синтаксисом
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
202
Чтобы установить Moq. щелкнем правой кнопк о й мыши на проекте
Wo r kingW i thVisualS t udio. Tes t s в окне Solution Explorer и выберем в контекстном
меню пункт Manage NuGet Packages for Solutioп (Управление пакетами NuGet длл ре­
шенил). Перейдем на вкладку Browse (Обзор) и введем Moq в поле поиска. Выберем
Mo q из списка пакетов (рис. 7.8) и щелкнем на кнопке lпstall (Установить) длл добав­
ленил пакета в проект.
На заметку! Пакет
Moq
добавляется в проект модульного тестирования , но не в проект
приложения.
Browse
lnstalled
UpdatesO
NuGet Package Manager: WorkingWithVisualStudio.Tests
.
moq
о
oq
Moq Ьу 0.11nitl C1uulino, lau, 14.2М downloads
Moq is the most popular 1nd frie.лdly mocking fr1mework (ог .NЕТ
~
v4.7.99
Version: [Latest st1Ьle4.7.9!: "
Autofac.Extras.Moq Ьу Autofac.Ьtr1s.Moq, 124К downlo1d'
L
lnst1ll
v4.1.0·rc5-246
The Moq integrltion posck19t 1llows you to 1utom1tically create mock
both concrete 1nd mock. abstract insti!lncts 1n unit tбts".
P.tt•••и de:pender'Юes for
Ш1ШJ Moq.Contrib by kzu.nц 71.бKdownlo•ds
v0.3.0
Grace.Moq
Ьу lan Johnsol\ 8.34К downloads
Oescription
Moq ts the most popular and fr1endly mock1ng
fr•mework for . NЕТ
Contributed features and third-party integr.tion for Moq.
v2A.2
Gra<t.Moq is • comp1nion libr•ry for Gract 1nd Moq
Version:
4.7.99
Author(s):
D1nitl C1zzulino, lczu
Lkeмei
Mtps://
r•\~. itlшbustrcontent с
Рис.
7 .В.
Добавление пакета в проект модульного тестирования
После установки пакета
тами
m
Moq
средой
Visual Studio
закроем окно управления паке ­
NuGet.
Соэдание имитированного объекта
Создание имитированн о го объекта о зн ач ает н е обходимост ь со об щенил
Moq
о
том, какого вида объект интересует. конфигурирование его поведенил и применени е
этого объекта к тестируемой сущности. В листинге
7. 19
инфраструкту ра
Moq
ис­
пользуетсл длл зам е ны двух фиктивных хранилищ в тестах, относящихсл к классу
HomeContro ller.
Листинг
us ing
us ing
us ing
using
us in g
ti si ng
7.19. Применение имитированных объектов в файле HomeControllerTests.
св из папки WorkingWi thVisualStudio. Tests
Micros o ft. As pNetC ore.Mvc;
System.Coll e ctions.Gener i c ;
WorkingWi t hVi sualStudi o.Contro lle r s;
WorkingWi t hVi s ua l Studi o.Mode l s ;
Xunit;
System;
Глава 7. Модульное тестирование приложений MVC
203
using Moq;
namespace WorkingWithVisualStudio.Tests
puЫic class HomeControllerTests {
[Theory]
[ClassData(typeof(ProductTestData))]
puЫic void IndexActionModelisComplete(Product[] products ) {
//Организация
var mock = new Мock<IRepository>();
mock.SetupGet(m => m.Products) .Returns(products);
var controller = new HomeController ( Repository = mock.Object } ;
//Действие
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 == p2.Name
&& pl. Price == р2. Price));
[Fact]
void RepositoryPropertyCalledOnce()
11 Организация
var mock = new Мock<IRepository>();
mock. SetupGet (m => m. Products)
.Returns(new[] { new Product ( Name = "Pl", Price = 100 } }) ;
var controller = new HomeController { Repository = mock.Object } ;
puЬlic
//Действие
var result
=
controller.Index();
11 Утверждение
mock.VerifyGet(m => m.Products, Times.Once);
}
Использование
IReposi tory
Moq
позволило удалить фиктивные реализации интерфейса
и заменить их всего лишь несколькими строками кода. Здесь не приво­
дятся детальные сведения о разных поддерживаемых
объясняется способ применения
адресу
Moq.
https: / /github. com/Moq/moq4.
различных типов компонентов
MVC
Moq
средствах, но на примерах
Примеры и документация по
Moq доступны
по
(При обсуждении модульного тестирования
в оставшихся главах книги также будут приво­
диться примеры.)
Первым делом создается новый объект
Mock
с указанием интерфейса, который
должен быть реализован:
var mock = new
Мock<IRepository>();
Созданный объект
Mock
будет имитировать интерфейс
IReposi tory.
Далее оп­
ределяется функциональность, которая требуется для теста. В отличие от обычной
реализации интерфейса классом для имитированного объекта конфигурируется толь­
ко поведение. нужное для теста. В первом имитированном хранилище понадобится
204
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
реализовать свойство
Products,
чтобы оно возвращало набор объектов
торый передается тестовому методу через атрибут
Product,
ко­
ClassData:
mock.SetupGet(m => m.Products) .Returns(products);
Метод
SetupGet ()
используется при реализации средства извлечения для свойс­
тва. Аргументом этого метода является лямбда-выражение, которое указывает подле­
жащее реализации свойство
(Products
в данном примере). Метод
вается на результате, полученном из метода
SetupGet (),
Returns ()
вызы­
чтобы указать результат.
который будет возвращаться, когда читается значение свойства. Для второго имити­
рованного хранилища применяется тот же самый подход, но указывается фиксиро­
ванное значение:
mock. SetupGet (m => m. Products)
.Returns (new[] { new Product { Name
В классе
Mock
определено свойство
Obj ect,
= "Pl",
Price
= 100
} }) ;
возвращающее объект, который ре­
ализует указанный интерфейс и обладает ранее определенным поведением. В обоих
модульных тестах свойство
Obj ect
используется, чтобы получить хранилище для
конфигурирования контроллера:
var controller = new HomeController { Repository = mock.Object };
Последним примененным средством
Products
Moq
была проверка того. что к свойству
осуществлялось только одно обращение:
mock.VerifyGet(m => m.Products, Times.Once};
Метод
VerifyGet ()
относится к тем методам класса
Mock,
которые инспектиру­
ют состояние имитированного объекта, когда тест завершен. В данном случае ме­
тод
VerifyGet () позволяет проверить, сколько раз читалось свойство Products.
Times. Once указывает. что метод VerifyGet () должен генерировать ис­
Значение
ключение, если свойство читалось не в точности один раз, и это приведет к тому. что
тест не пройдет. (Методы класса
Assert,
обычно используемые в тестах, генерируют
исключение, когда тест не проходит, и потому при работе с имитированными объек­
тами метод
VerifyGet ()
можно применять для замены метода класса
Assert.)
Резюме
rлава была сконцентрирована на модульном тестировании, которое способ­
но оказаться мощным инструментом, направленным на повышение качества кода.
Модульное тестирование не подойдет абсолютно каждому разработчику, но с ним по­
лезно поэкспериментировать, и оно может быть полезным, даже если используется
только для сложных функций или обнаружения проблем. Было описано применение
инфраструктуры тестирования
xUnit.net,
объяснена важность изоляции компонентов
для целей тестирования и продемонстрированы некоторые инструменты и приемы,
позволяющие упростить код модульных тестов. В следующей главе начнется разра­
ботка более реалистичного приложения
MVC
под названием
SportsStore.
ГЛАВА
8
SportsStore:
реальное приложение
главах мы создавали очень простое приложение MVC. Был
в предшествующихMVC,
описан nаттерн
основные средства языка С#, а также инструменты, не­
обходимые профессиональным разработчикам приложений
MVC.
Наступило время
собрать все вместе и построить несложное, но реалистичное приложение электрон­
ной коммерции.
Наше приложение под названием
SportsStore
будет следовать классическому под­
ходу, который повсеместно используется в онлайновых магазинах. Мы создадим он­
лайновый каталог товаров, который потребители смогут просматривать по категори­
ям и страницам, корзину для покупок, куда пользователи смогут добавлять и удалять
товары, и форму оплаты, где потребители смогут вводить сведения, связанные с до­
ставкой. Кроме того, мы создадим административную область, которая включает в
себя средства создания, чтения, обновления и удаления
CRUD)
(create, read, update. delete -
для управления каталогом товаров. и защитим ее так. чтобы изменения могли
вносить только зарегистрированные администраторы.
Цель настоящей и последующих глав
хожа реальная разработка приложений
- дать вам возможность увидеть, на что по­
MVC, за счет создания примера приложения,
который максимально приближен к реальности. Разумеется, мы будем ориентиро­
ваться на
ASP.NET Core MVC,
поэтому интеграция с внешними системами. такими
как база данных, предельно упрощена. а определенные части приложения, например,
обработка платежей, вообще отброшены.
Построение всех уровней необходимой инфраструктуры может показаться не­
сколько медленным, но первоначальные трудозатраты при разработке приложения
MVC
окупаются, обеспечивая удобный в сопровождении, расширяемый и хорошо
структурированный код с великолепной поддержкой модульного тестирования.
Модульное тестирование
Я уже достаточно много говорил о легкости проведения модульного тестирования в
MVC,
а
также о том, что модульное тестирование может быть важной и полезной частью процесса
разработки. Это будет демонстрироваться на протяжении данной части книги, поскольку
я описываю нюансы и приемы модульного тестирования в их взаимосвязи с основными
средствами
MVC.
206
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Я понимаю, что мое мнение не является единственно верным. Если вы не хотите прибе­
гать к модульному тестированию, то меня это вполне устроит. Таким образом, когда что-то
относится исключительно к тестированию, оно будет помещаться во врезку, подобную на­
стоящей. Если модульное тестирование вас не интересует, тогда можете смело пропускать
такие врезки, и приложение
SportsStore будет раба.тать не менее успешно. Чтобы восполь­
ASP.NET Core MVC, вовсе не обязательно проводить
зоваться преимуществами технологии
какое-либо модульное тестирование, хотя поддержка тестирования, конечно же, является
основной причиной перехода на
ASP.NET Core MVC.
Большинству средств МVС, используемых в приложении
SportsStore.
посвящены
отдельные главы далее в книге. Вместо того чтобы повторять весь материал, я сооб­
щаю ровно столько, сколько требуется для понимания этого примера приложения, и
указываю главу. в которой можно почерпнуть более подробные сведения.
Каждый шаг, необходимый для построения приложения. будет выделен, что поз­
волит понять, каким образом средства
MVC
сочетаются друг с другом. Вы должны
уделить особое внимание созданию представлений. Если вы не будете в точности сле­
довать примерам, то получите несколько странные результаты.
Начало работы
Если вы планируете писать код приложения
SportsStore
на своем компьютере
во время изучения материала текущей части книги, тогда вам придется установить
Visual Studio
и удостовериться в том, что установлен вариант
LocaIDB. который тре­
LocalDB устанавливается автома­
главы 2.
буется для постоянного хранения данных. Вариант
тически в случае следования инструкциям из
На заметку! Если вы просто хотите работать с проектом, не воссоздавая его, то можете за­
грузить готовый проект SportsStore из хранилища GitHub для настоящей книги (https: / /
github.com/apress/pro-asp.net-core-mvc-2).
Разумеется, вы вовсе не обяза­
ны повторять все действия. Я старался делать снимки экрана и листинги кода максималь·
но простыми в отслеживании на тот случай, если вы читаете книгу в поезде, кафе или
где-то еще.
Создание проекта
MVC
Мы будем следовать тому же самому базовому подходу, который использовался в
предшествующих главах и заключается в том, чтобы начать с пустого проекта и до­
бавлять в него все необходимые конфигурационные файлы и компоненты. Выберем
в меню
File
(Файл) среды
Visual Studio пункт New~Project (Создать~Проект) и ука­
ASP.NET Core Web Applicatioп (Веб-приложение ASP.NET Core).
рис. 8.1. Установим имя проекта в SportsStore и щелкнем на
жем шаблон проекта
как показано на
кнопке ОК.
Выберем шаблон
Empty
(Пустой). как проиллюстрировано на рис.
8.2. Прежде чем
SportsStore, удостоверимся в том, что
в списках в верхней части окна выбраны варианты .NET Core и ASP.NET Core 2.0, а
флажок ЕnаЫе Docker Support (Включить поддержку Docker) не отмечен.
щелкать на кнопке ОК для создания проекта
Глава 8. SportsStore: реальное прил оже н ие
207
L
А
lnstalled
~
R:,V
Vj]
Vj]
" Visual (;t
Windows Classic Desktop
Web
.NЕТ Core
.NЕТ Standard
Cloud
Test
~ Visual Basic
Console Арр (.NET Core)
Visual С#
Class Library (.NЕТ Core)
Visual (#
Unit Test Project (.NЕТ Core)
Visual (:t
xUnit Test Project (.NЕТ Core)
Visual С#
~ .ASP.NEТ Cor~ Web Applicat1on
. Visual (#
~()1 ~•rvPr
Not finding what you are looking for?
Open Visual Studio lnstaller
Name:
SportsStore
Location:
·!
/C:\Users\ adam\Documents\
Solution name:
Browse".
0
SportsStore
0
Рис.
.
j .NEТCore
1
ReactJs
~
WebAPI
ASP.NEТ
?
.
.
Core 2.0
@:]
l'leb
Application
Выбор типа проекта
8. 1.
N...., ASP NЕТ Core l'leb Applocot•on • Spolt<Store
Createdi
дddtoS
Х
~
@.1
е
Web
Angular
An empty project template for creating an ASP. NEТ
Cor!! application. This template does not have any
content in it.
AppliclJtion
(Model-ViewController)
@:>
ReactJs and
Redux
Ch~ngt Avthentication
Authentication No Authentication
О Еn•Ы• Docker Support
OS;
\V.ndows
Rtquir6 Oocker forWindows
Oocker support сап also Ье enaЫed later ~
ОК
Рис.
8.2.
Выбор шаблона проекта
\ J Cane<I
208
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Создание структуры папок
Следующий шаг заключается в добавлении папок, которые будут содержать ком­
поненты, требуемые для приложения
MVC: модели. контроллеры и представления.
8.1, щелкнем правой кнопкой мыши на эле­
Solutioп Explorer, выберем в контекстном меню
Чтобы создать папки, описанные в табл.
менте проекта
SportsStore в окне
Folder (Добавить~Новая
пункт Add~New
папка) и введем имя папки. Позже потребу­
ются дополнительные папки, но создаваемые сейчас папки отражают главные части
приложения
MVC
Таблица
Папки, требующиеся для проекта
8.1.
и для начала их вполне достаточно.
SportsStore
Имя
Описание
Models
Эта папка будет содержать классы моделей
Controllers
Эта папка будет содержать классы контроллеров
Views
Эта папка будет содержать все, что относится к представлениям,
в том числе индивидуальные файлы
Razor,
файл запуска представ­
ления и файл импортирования представлений
Конфмrурирование приложения
За конфигурирование приложения ASP.NEТ Core несет ответственность класс
В листинге
8.1
S tart up.
Startup для включения инфра­
приведены изменения, внесенные в класс
стрУК1УJ>Ы МVС и нескольких связанных средств, которые полезны при разработке.
На заметку! Класс
Startup
является важным функциональным средством
будет подробно описан в главе
Листинг
8.1.
ASP.NET Core
и
14.
Включение средств в файле
Startup. cs
из папки
Sportsstore
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Dependencyinjection;
namespace SportsStore {
puЫic class Startup {
puЫic void ConfigureServices(IServiceCollection services) {
services.AddНvc();
)
puЬlic
void Configure(IApplicationBuilder
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
арр. UseМvc (routes => {
}) ;
)
арр,
IHostingEnvironment env) {
Глава 8. SportsStore: реальное nриложение
Метод
ConfigureServices ()
209
применяется для настройки разделяемых объектов,
которые могут использоваться повсеместно в приложении через средство внедрения
зависимостей, которое рассматривается в главе
внутри
ConfigureServices (),
18.
Метод
AddMvc (),
вызываемый
является расширяющим методом, который настраи­
вает разделяемые объекты, применяемые в приложении МVС.
Метод
используется для настройки средств, которые получают и
Configure ()
обрабатывают НТТР-запросы. Каждый метод, вызываемый в методе
Configure (),
представляет собой расширяющий метод, который настраивает средство обработки
НТТР-запросов (табл.
8.2).
Таблица 8.2. Методы настройки первоначальных средств, вызываемые в классе
Startup
Метод
Описание
UseDeveloperExceptionPage ()
Этот расширяющий метод отображает детали исключения,
которое произошло в приложении, что полезно во время
процесса разработки. Он не должен быть включен в раз­
вернутых приложениях и при развертывании приложения в
главе
12 будет показано,
как отключить данное средство
Этот расширяющий метод добавляет простое сообщение
UseStatusCodePages()
в НТТР-ответы, которые иначе не имели бы тела, такие
как ответы
UseStaticFiles()
404 - Not Found (404 -
не найдено)
Этот расширяющий метод включает по,одержку для обслу­
wwwroot
живания статического содержимого из папки
UseMvc ()
Этот расширяющий метод включает инфраструктуру
ASP.NEТ
Core MVC
Далее понадобится подготовить приложение для представлений
правой кнопкой мыши на папке
ltem
Views,
(Добавить9Новый элемент) и укажем шаблон
импортирования представлений МVС) из категории
Щелкнем на кнопке
Add
MVC View lmports Page
ASP.NET
(рис.
Add9New
(Страница
8.3).
(Добавить), чтобы создать файл_Viewimports.
и приведем его содержимое в соответствие с листингом
Листинг
Щелкнем
Razor.
выберем в контекстном меню пункт
cshtml,
8.2.
8.2. Содержимое файла _ Viewimports. cshtml из папки Views
@using SportsStore.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Оператор @using позволит применять типы из пространства имен SportsStore.
Models в представлениях, не ссылаясь на это пространство имен. Оператор
@addTagHelper включает встроенные вспомогательные функции дескрипторов, ко­
торые бу.цут использоваться позже для создания элементов НТМL, отражающих кон­
фигурацию приложения
SportsStore.
Создание проекта модульного тестирования
Создание проекта модульного тестирования требует выполнения того же самого
процесса, который бьm описан в главе
решения
SportsStore
Add9New Project
в окне Solutioп
7. Щелкнем правой кнопкой мыши
Explorer и выберем в контекстном
(Добавить9Новый проект).
на элементе
меню пункт
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
210
,,,
ASP.NEТ
Core
Code
Gene:ral
~·
MVC View P•g•
ASP.NEТ
~·
MVC View L•yout P•g•
ASP.NEТ Со"'
~·
МVС
~
MVC V1<w lmports Page
ASP.NEТ Coro
Rozor T•g Holpor
ASP.NET Core
Middlewor• Cl•ss
ASP.NET Coro
Startup cl•ss
ASP.NEТ
Wtb Configuration File
ASP.NET Core
о
" Web
ASP.NEТ
General
Scripts
Contont
Online
~
Nome:
Viow Start Pago
Coro
ASP.NET Coro
Core
_Viowlmports.cshtml
[
Рис.
Укажем шаблон
8.3.
Создание файла импортирования представлений
xUnit Test Project (.NET Core)
как показано на рис.
Core)),
8.4,
(Проект тестирования
и установим имя проекта в
xUnit (.NET
Spo r tsStor e. Te s t s.
Щелкнем на кнопке ОК, чтобы создать проект модульного тестирования.
" lnstalled
'1
,,, Visual С#
Windows Classic Desktop
Web
Standard
Cloud
Test
Visual Basic
SQL Sorver
.NЕТ
~
Visua\CI!
~~
Class Library (.NET Core)
Visual С"
Vl5
Unit Тбt Project (.NET Core)
Visual CI!
~
xUnit Тбt Pr~joct (.NЕТ Coro)
VtSual (::
@
ASP.NET Coro Web Application
VisualCI!
[1~с•
.NEТCcre
~
Console Арр (.NЕТ Core)
А
project
can run о~
and МасО .
Online
Not finding what you are looking for?
Open Visual Stud10 lnstaller
№me:
Locotion:
SportsStore.Tбts
!C:\Users\adam\Documents\SportsStore
Рис.
8.4.
Создание проекта модульного тестирования
Browse".
Глава 8. SportsStore: реальное приложение
211
После того. как проект модульного тестирования создан. щелкнем правой кнопкой
Spo rtsStore. Tests в окне Solution Explorer и выберем в контекстном
(Редактировать Sports Store. Test s. cspro j).
Внесем в файл SportsStore. Tests. csproj изменения , показанные в листинге 8.3,
чтобы добавить пакет Moq в проект Spo rtsStore. Tests и создать ссьшку на главный
проект Spo rtsSt o re. Удостоверимся . что для пакета Moq указана та же версия , что
и в листинге 8.3.
мыши на проекте
меню пункт
Листинг
Edit SportsStore.Тests.csproj
8.3. Добавление пакета в файле SportsStore. Tes ts. csproj
из папки SportsStore. Tests
<Pro ject Sdk= " Microsoft .NET. Sdk">
<PropertyGro up >
<TargetFramework>netc o reapp 2 . 0</TargetFramework>
< I sPa c kaЬle>false</ I sPackaЫ e>
</ PropertyGr o up>
<ItemGroup>
<ProjectReference Include=" .. \SportsStore\SportsStore.csproj" />
</ItemGroup>
< It e mGro up >
<PackageReference I nclude= "M icros of t.NET.Test .Sdk"
Ve rsion= " lS.3.0-preview - 20 1 70628- 0 2 " / >
<P ackageReference In c lude= "x unit " Version=" 2.2 .0" />
<Pa ckageReference Include= " xunit .runner.vi suals t udio " Version= " 2 .2.0" />
<PackageReference Include="Moq" Version="4.7.99" />
</ ItemGr o up>
</ Pr o jec t >
Как только изменения будут сохра­
Visual Studio загрузит и ус­
пакет Moq в проект модульного
нены, среда
тановит
тестирования и создаст ссылку на глав­
ный проект
SportsStore,
Solution Explorer
Search $olulion &рlош (Ctrf+;)
Р·
так что содер­
жащиеся в нем классы смогут использо ­
"
ваться в тестах.
Проверка и запуск приложения
Проекты приложения и модульного
тестирования созданы, сконфигурирова­
ны и готовы к дальнейшей разработке.
Окно
-----
Solution Explorer должно
содержать
элементы, представленные на ри с .
8.5.
Если вы видите другие элементы или
элементы расположены не в тех позици ­
"
;;:] SportsStore
41 Conne<ted Services
~ .~.· Oependencies
~ }' Properties
@wwwroot
Controlfers
Models
Views
"
~ В _Viewlmports.cshtml
с• Program.cs
~
С" Startup.cs
~
15З SportsStore.Tests
.-:.• Oependencies
~
~
Со UnitТest1 .cs
ях. тогда возникнут проблемы. поэтому
уделите время проверке. что все элемен­
ты присутствуют и
местах.
находятся на своих
Рис.
8.5.
Окно
Solution Explorer для про­
SportsStore и модульного
ектов приложения
тестирования
212
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
В результате выбора в меню
Debug (Отладка) пункта Start Debugging (Запустить
Start Without Debugging (Запустить без отладки). если предпочтение от­
дается итеративному стилю разработки. описанному в главе 6, появится страница
ошибки (рис . 8 .6). Сообщение об ошибке отображается из-за того, что в настоящий
отладку) или
момент в приложении нет контроллеров для обработки запросов; проблему мы вскоре
устраним.
Status Code: 404; Not Found
Рис.
8.6.
Выполнение приложения
SportsStore
Начало работы с моделью предметной области
Все проекты начинаются с создания модели предметной области. которая являет­
ся центральной частью приложения
MVC.
Поскольку мы строим приложение элект­
ронной коммерции, то наиболее очевидная модель. которая необходима, относится к
товару. Добавим в папку
Models
приведенным в листинге
8.4.
Листинг
8.4.
Содержимое файла
файл класса по имени
Product. cs
из папки
Product. cs
с определением.
Model s
namespace Sp o rtsStore.Models {
class Product {
int Pr od uc t ID { get; set;
p u Ьli c string Name ( get; set; }
puЫ i c string Desc ri ption { get; se t;
puЬlic d ec imal Price { get; set;
puЫic str i ng Catego ry ( get; set; }
puЫic
puЬli c
Создание хранилища
Нам нужен какой-нибудь способ получения объектов
Product
из базы данных .
В главе З упоминалось . что модель включает в себя логику для сохранения и извле<1ения
данных из постоянного хранилища. Пока можно не беспокоиться о том , как будет реа­
лизовано постоянство данных. но необходимо начать процесс определения интерфей­
са для него. Добавим в папку
Models
новый файл по имени
IProduc t Reposi t.ory. cs
и поместим в него определение интерфейса. показанное в листинге
8.5.
Глава 8. SportsStore: реальное приложение
Листинг
8.5.
Содержимое файла
IProductReposi tory. cs
из папки
213
Models
using System.Linq;
namespace SportsStore.Models
puЫic
interface IProductRepository
Products { get;
IQueryaЫe<Product>
Интерфейс
волить
I ProductReposi tory
вызывающему
коду
получать
использует IQueryaЬle<T>,
последовательность
объектов
чтобы поз­
Р
r о d u с t.
Интерфейс IQueryaЫe<T> является производным от более знакомого интерфейса
IEnumeraЫe<T> и представляет коллекцию объектов, которые можно запрашивать,
вроде тех, что управляются базой данных.
Класс, зависящий от интерфейса
ты
Product
IProductReposi tory,
способен получать объек­
без необходимости знать детали того, как они хранятся, или каким обра­
зом класс реализации будет их доставлять.
Интерфейсы IEnumeraЬle<T> и IQueryaЫe<T>
Интерфейс IQueryaЬle<T> удобен, потому что он позволяет эффективно организо­
вать запрашивание коллекции объектов. Позже в главе будет добавлена поддержка для
извлечения из базы данных подмножества объектов
Product.
Применение интерфейса
IQueryaЫe<T> дает возможность запрашивать у базы данных именно те объекты, кото­
рые требуются, с помощью стандартных операторов
LINQ,
не нуждаясь в информации о том,
какой сервер баз данных хранит данные или как он обрабатывает запрос. Без интерфейса
IQueryaЫe<T> пришлось бы извлекать из базы данных все объекты
Product
и затем
отбрасывать ненужные, что с ростом объема данных, используемых приложением, превра­
щается в затратную операцию. Именно по этой причине в интерфейсах и классах хранилища
в форме базы данных обычно вместо IEnumeraЫe<T> применяется IQueryaЫe<T>.
Однако во время использования интерфейса IQueryaЬle<T> следует соблюдать осторож­
ность, поскольку каждый раз, когда происходит перечисление коллекции объектов, запрос
будет оцениваться заново, т.е. базе данных отправится новый запрос. В результате выго­
ды в плане эффективности от применения IQueryaЫe<T> могут быть сведены на нет.
В таких ситуациях IQueryaЫe<T> можно преобразовать в более предсказуемую форму,
используя расширяющий метод
ToList ()
или
ToArray ().
Создание фиктивного хранилища
Теперь, когда определен интерфейс, можно было бы реализовать механизм пос­
тоянства и привязать его к базе данных. но сначала необходимо добавить ряд дру­
гих частей приложения. Для этого мы создадим фиктивную реализацию интерфейса
IProductRepository,
которая будет замещать хранилище данных до тех пор, пока
мы им не займемся. Чтобы создать фиктивное хранилище, добавим в папку
файл класса по имени
FakeProductReposi tory. cs
представленное в листинге
8.6.
Models
и поместим в него определение,
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
214
Листинг
8.6.
Содержимое файла
FakeProductReposi tory. cs
из папки
Models
using System.Collections.Generic;
using System.Linq;
namespace SportsStore.Models {
puЫic
class FakeProductRepository : IProductRepository {
puЬlic
IQueryaЫe<Product> Products => new List<Product>
new Product { Name
"Football", Price = 25 ),
new Product { Name = "Surf board", Price = 179 ),
new Product { Name = "Running shoes", Price = 95 )
).AsQueryaЬle<Product>();
Класс
FakeProductReposi tory
реализует интерфейс
возвращая в качестве значения свойства
ектов
Product.
Метод AsQueryaЬle
()
Products
IProductReposi tory.
фиксированную коллекцию объ­
применяется для преобразования фиксиро­
ванной коллекции объектов в IQueryaЫe<Product>. чтобы реализовать интерфейс
IProductRepository
и позволить создавать совместимое фиктивное хранилище. не
имея дело с реальными запросами.
Регистрация службы хранилища
В МVС придается особое значение использованию слабо связанных компонентов,
что означает возможность вносить изменения в одну часть приложения, не внося со­
ответствующие изменения где-то в другом месте. Такой подход трактует части при­
ложения как службы, предлагающие функциональные средства, которые задейству­
ются в других частях приложения. Класс, предоставляющий службу, впоследствии
может быть модифицирован или заменен, не требуя внесения изменений в классы. в
которых он применяется. Подход более подробно объясняется в главе
приложения
SportsStore
18,
а в случае
понадобится создать службу хранилища, которая позволит
контроллерам получать реализующие интерфейс
IProductReposi tory
объекты, не
зная, какой класс используется. В итоге появится возможность начать разработку
приложения с применением простого класса
FakeProductRepository,
созданного в
предыдущем разделе, и позже заменить его реальным хранилищем, не внося измене­
ния во все классы, которым необходим доступ в хранилище. Службы регистрируются
в методе
ConfigureServices ()
класса
Startup;
в листинге
8.7
определена новая
служба для хранилища.
Листинг
8.7.
Соэдание службы хранилища в файле
Startup. cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Dependencylnjection;
using SportsStore.Models;
namespace SportsStore {
из папки
SportsStore
Глава 8. SportsStore: реальное приложение
puЫic
215
class Startup {
puЬlic
void ConfigureServices(IServiceCollection services) {
services.AddTransient<IProductRepository, FakeProductRepository>();
services.AddMvc();
void Configure(IApplicationBuilder
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
puЫic
арр,
IHostingEnvironment env) {
)) ;
Добавленный в метод
ре
ASP.NET Core
ConfigureServices ()
оператор сообщает инфраструкту­
о том, что когда компоненту вроде контроллера необходима реали­
IProductReposi tory, она должна получить экземпляр класса
FakeProductReposi tory. Метод AddTransient () указывает, что каждый раз, когда
требуется реализация интерфейса IProductRepository, должен создаваться новый
объект FakeProductReposi tory. Не беспокойтесь, если смысл кода пока не особен­
зация интерфейса
но понятен; вскоре вы увидите, как он вписывается в приложение, а детали того, что
происходит, будут представлены в главе
18.
Отображение списка товаров
Оставшийся материал главы можно было бы посвятить построению модели пред­
метной области и хранилища, вообще не касаясь других частей приложения. Однако
такой подход выглядит довольно скучным, поэтому мы пойдем другим путем и при­
ступим к интенсивному использованию
MVC,
вернувшись к добавлению функцио­
нальности модели и хранилища. когда она понадобится.
В текущем разделе нам предстоит создать контроллер и метод действия, который
может отображать сведения о товарах в хранилище. Пока ими будут только данные
из фиктивного хранилища. но позже мы решим эту задачу. Мы также подготовим на­
чальную конфигурацию маршрутизации, чтобы инфраструктура
MVC
знала, каким
образом сопоставлять запросы в приложении с контроллером. который будет создан.
Использование инструмента формирования шаблонов
Везде в книге контроллеры и представления
мыши на папке в окне Solutioп
Explorer,
MVC
MVC
в
Visual Studio
создаются за счет щелчка правой кнопкой
выбора в контекстном меню пункта
AddqNew ltem
(ДобавитьqНовый элемент) и указания шаблона элемента в открывшемся диалоговом окне
Add New ltem (Добавление нового элемента). Существует альтернативный прием, называ­
емый формированием шаблонов
контекстном меню
Add
(scaffolding),
при котором среда
Visual Studio
предлагает в
(Добавить) пункты, специально предназначенные для создания кон­
троллеров и представлений. После выбора таких пунктов меню предлагается выбрать сце­
нарий для создаваемого компонента, такой как контроллер с действиями, допускающими
чтение/запись, или представление, которое содержит форму, применяемую для создания
специфического объекта модели.
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
216
Формирование шаблонов в книге не используется. Код и разметка, генерируемые инстру­
ментом формирования шаблонов, являются настолько общими, что оказываются едва ли
не бесполезными, в то время как набор подцерживаемых сценариев ограничен и не решает
распространенные задачи разработки. Цель настоящей книги
знания, каким образом создавать приложения
MVC,
не только донести до вас
-
но также объяснить, как все работает
"за кулисами", и сделать это гораздо труднее, когда ответственность за создание компо­
нентов возлагается на инструмент формирования шаблонов.
Тем не менее, мы имеем еще одну ситуацию, когда ваш стиль разработки может отличаться
от моего, и в своих проектах вы вполне можете предпочесть работу с инструментом форми­
рования шаблонов. Ваше решение полностью приемлемо, хотя я рекомендую уделить время
на выяснение того, что именно инструмент формирования шаблонов делает, чтобы знать,
куда затем смотреть, если будут получены не те результаты, которые ожидались.
Добавление контроллера
Чтобы создать первый контроллер в приложении. добавим в папку
файл класса по имени
тинге
ProductController. cs
Controllers
с определением. показанным в лис­
8.8.
Листинг
8.8.
Содержимое файла
ProductController. cs
из папки
Controllers
using Microsoft.AspNetCore.Mvc;
using SportsStore.Models;
namespace SportsStore.Controllers
class ProductController : Controller
private IProductRepository repository;
puЫic
ProductController(IProductRepository repo) {
repository = repo;
puЫic
Когда
са
инфраструктуре
ProductController
MVC
необходимо
для обработки
создать
новый
НТТР-запроса,
экземпляр
клас­
проинспектирует
MVC
конструктор и выяснит, что он требует объекта. который реализует интерфейс
IProductReposi tory.
Чтобы определить, какой класс реализации должен использо­
ваться, инфраструктура
MVC
обращается к конфигурации в классе
сообщает о том, что нужно применять класс
FakeReposi tory
его новый экземпляр. Инфраструктура МVС создает новый объект
использует его для вызова конструктора
Startup,
которая
и каждый раз создавать
ProductController
FakeReposi tory
и
с целью создания объ­
екта контроллера, который будет обрабатывать НТТР-запрос.
Такой подход известен под названием внедрение зависимостей и позволяет объек­
ту
ProductController получать доступ к хранилищу приложения через интерфейс
IProductReposi tory без необходимости в знании того, какой класс реализации был
сконфигурирован. Позже мы заменим фиктивное хранилище реальным. а благодаря
внедрению зависимостей контроллер продолжит работать безо всяких изменений.
Глава 8. SportsStore: реальное приложение
217
На заметку! Некоторым разработчикам не нравится внедрение зависимостей, поскольку они
считают, что оно усложняет приложения. Я не придерживаюсь такой точки зрения, но если
вы не знакомы с внедрением зависимостей, тогда рекомендую вам прочитать главу
18,
прежде чем принимать решение.
Далее добавим метод действия по имени
List
(),который будет визуализировать
представление, отображающее полный список товаров из хранилища (листинг
Листинг
8.9.
Добавление метода действия в файле
из папки
8.9).
ProductController. cs
Controllers
using Microsoft.AspNetCore.Mvc;
using SportsStore.Models;
namespace SportsStore.Controllers
class ProductController : Controller
private IProductRepository repository;
puЬlic
ProductController(IProductRepository repo) {
repository = repo;
puЫic
puЫic
ViewResul t List () => View (reposi tory. Products) ;
}
Вызов метода
View ()
щает инфраструктуре
подобного рода (без указания имени представления) сооб­
MVC
о том, что нужно визуализировать стандартное представ­
ление для метода действия. Передача методу
View ()
коллекции объектов
Product
из хранилища снабжает инфраструктуру данными, которыми необходимо заполнить
объект
Model
в строго типизированном представлении.
Добавление и конфигурирование представления
Нам требуется создать представление для отображения содержимого пользовате­
лю. но предстоит проделать ряд подготовительных шагов, чтобы упростить написа­
ние представления. Сначала понадобится создать разделяемую компоновку, в кото­
рой будет определено общее содержимое, включаемое во все отправляемые клиентам
НТМL-ответы. Разделяемые компоновки
-
удобный способ обеспечить согласован­
ность представлений и наличие в них важных файлов
CSS;
их работа объяснялась в главе
Создадим папку
Layout Page
Views/Shared
JavaScript
и таблиц стилей
5.
и добавим в нее новый файл с шаблоном
(Страница компоновки представлений
MVC)
MVC View
и именем_ Layout.
cshtrnl,
Visual Studio элементу
Layout. cshtrnl. В стан­
которое является стандартным именем, назначаемым средой
такого типа. В листинге
8.10
приведено содержимое файла
дартное содержимое внесено одно изменение, связанное с установкой внутренностей
элемента
Листинг
ti tle
8.1 О.
в
Содержимое файла_Layout.
< ! DOCTYPE html>
<html>
SportsStore.
cshtml
из папки
Views/Shared
218
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
<head>
<meta name="viewport" content="width=device-width" />
<title>Sportsstore</title>
</head>
<body>
<div>
@RenderBody ()
</div>
</body>
</html>
Затем необходимо сконфигурировать приложение. чтобы файл
применялся по умолчанию, для чего в папку
View Start Page
Layout. cshtml
добавляется файл с шаблоном
MVC
(Файл запуска представления
Стандартное содержимое. добавляемое
новку по имени
Листинг
Views
MVC) и именем ViewStart. cshtml.
Visual Studio (листинг 8.11 ). выбирает компо­
которая соответствует файлу из листинга
Layout. cshtml,
8.10.
8.11. Содержимое файла_ViewStart. cshtml из папки Views
@{
Layout
" Layout";
Теперь нужно добавить представление, которое будет отображаться. когда для обра­
ботки запроса используется метод действия
List
и добавим в нее файл представления
по имени
разметку, показанную в листинге
Razor
8. 12.
().Создадим папку
List. cshtml.
Views/Product
Поместим в него
Листинг
8.12. Содержимое файла List. cshtml из папки Views/Product
@model
IEnumeraЫe<Product>
@foreach (var
<div>
р
in Model)
<hЗ>@p.Name</hЗ>
@p.Description
<h4>@p.Price.ToString("c")</h4>
</div>
Выражение
@model
в начале файла указывает, что представление будет получать
от метода действия последовательность объектов
С помощью выражения
@foreach
Product
в качестве данных модели.
осуществляется проход по этой последовательнос­
ти с генерацией простого набора НТМL-элементов для каждого полученного объекта
Product.
Представление не знает. откуда поступили объекты
Product,
как они были полу­
чены или охватывают ли они все товары, известные приложению. Взамен представ­
ление имеет дело только со способом отображения деталей каждого объекта
Product
с применением НТМL-элементов, что согласуется с принципом разделения обязаннос­
тей, который был описан в главе
3.
Глава 8. SportsStore: реальное приложение
Совет. Значение свойства
ToS tr ing ( 11 с 11
)
,
Price
219
преобразуется в строку с использованием метода
который визуализирует числовые значения в виде денежных зна­
чений в соответствии с настройками культуры, действующими на сервере. Например,
если в качестве настройки культуры сервера установлено
ToString ( 11 с 11 ) возвратит
культура en-GB, тогда тот же
значение
$1, 002. 30,
самый вызов возвратит
en-US,
то вызов
( 1002. 3) .
но если для сервера установлена
f.l, 002. 30.
Установка стандартного маршрута
Инфраструктуре
MVC
понадобится сообщить, что она должна отправлять запросы,
постуnающие для корневого URL приложения (ht tp: //мой-сайт/), мето.цу действия
List ()
классе
класса ProductController. Задача решается редактированием оператора в
Startup, который настраивает классы MVC, обрабатывающие НТГР-запросы
8.13).
(листинг
8.1 З. Изменение
SportsStore
Листинг
using
using
using
using
using
using
using
using
using
стандартного маршрута в файле
Startup. cs
из папки
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
SportsStore.Models;
namespace SportsStore {
puЫic
class Startup {
void ConfigureServices(IServiceCollection services) {
services.AddTransient<IProductRepository, FakeProductRepository>();
services.AddMvc();
puЫic
void Configure(IApplicationBuilder
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
puЬlic
app.UseМvc(routes
арр,
IHostingEnvirorunent env) {
=> {
routes.МapRoute(
name: "default",
template: "{controller=Product}/{action=List}/{id?}");
}) ;
1
Метод
Configure ()
класса
Startup
применяется для настройки конвейера за­
просов, состоящего из классов (известных как промежуточное программное обеспе­
чение), которые бу.цут инспектировать НТГР-запросы и генерировать ответы.
220
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Метод
UseMvc ()
настраивает промежуточное программное обеспечение
MVC.
при­
чем одним из параметров конфигурации является схема. которая будет использовать­
ся для сопоставления
URL
с контроллерами и методами действий. Система маршру­
тизации подробно рассматривается в главах
изменения, выделенные в листинге
8 . 13,
мости отправки запросов методу действия
в
URL
а пока просто следует знать, что
List ()
контроллера
о необходи­
MVC
Pr oduct ,
если только
запроса не указано иное.
Совет. Обратите внимание, что в листинге
не
15 и 16.
сообщают инфраструктуре
Produ ctContro ll er , являющееся
вании
вом
MVC,
8.13
имя контроллера установлено в
Product,
а
именем класса . Это часть соглашения об имено­
в рамках которого имена классов контроллеров обычно заканчиваются сло­
Contr olle r,
но при ссылке на класс данная часть имени опускается . Соглашение
об именовании и его влияние объясняются в главе
31.
Запуск приложения
Все основные компоненты в наличии . Мы имеем контроллер с методом действия.
который
MVC
будет применять, когда запрашивается стандартный
жения. Инфраструктура
MVC
создаст экземпляр класса
URL
FakeReposi tory.
для прило­
после чего
будет использовать его для создания нового объекта контроллера, обрабатывающего
запрос. Фиктивное хранилище снабдит контроллер простыми тестовыми данными ,
которые его метод действия передаст представлению
браузера будет включать детали каждого объекта
ответа инфраструктура
MVC
Razor,
так что НТМL-ответ для
Pr od uct.
При генерации НТМL­
объединит данные из представления, выбранные ме­
тодом действия, с содержимым разделяемой компоновки. порождая завершенный
НТМL-документ, который браузер в состоянии разобрать и отобразить. Запустив при­
ложение , можно увидеть результат, показанный на рис.
8.7.
Итак, мы исследовали типовой шаблон разработки для инфраструктуры
Саге
MVC.
ASP.NET
Начальные затраты времени на необходимую настройку являются обяза­
тельными, но затем основные средства приложения будут собираться очень быстро.
С) SportsSlor•
CD
С
х
localhost:53406
Football
S25.00
Sш·f
boa1·d
Sl 79.00
Rlшning
shoes
S95.00
Рис.
8. 7.
Просмотр основной функциональности приложения
Глава 8. SportsStore: реальное nриложение
221
Подготовка базы данных
Мы способны отображать простое представление, содержащее сведения о товарах,
но применяем тестовые данные, которые находятся в фиктивном хранилище. Прежде
чем можно будет реализовать хранилище с реальными данными, необходимо настро­
ить базу данных и заполнить ее данными.
SQL Server, а доступ к ней будет осущест­
Framework Core (EF Core) - инфраструктуры объект­
отображения (object-relational mapping - ORM) для Microsoft .NET.
Для базы данных будет использоваться
вляться с применением Eпtity
но-реляционного
Инфраструктура ОRМ представляет таблицы, столбцы и строки реляционной базы
данных посредством обычных объектов С#.
На заметку! Это область, где можно выбирать из широкого диапазона инструментальных
средств и технологий. Доступны не только различные реляционные базы данных, но мож­
но также работать с хранилищами объектов, хранилищами документов и рядом экзотичес­
ких альтернатив. Кроме того, существуют другие инфраструктуры
ORM
для .NЕТ, каждая
из которых использует слегка отличающийся подход; такие вариации позволяют выбрать
то, что лучше всего подходит для конкретных проектов.
Инфраструктура
Entity Framework Core
применяется по нескольким причинам:
LINQ (а мне нравится
ASP.NET Core MVC. Ранним выпускам
ее просто запустить в работу, она прекрасно интегрируется с
использовать
EF Core
LINQ)
и она хорошо работает с
были присущи мелкие недостатки, но текущие версии элегантны и богаты
возможностями.
Продукт
SQL Server
располагает удобным средством
Loca/DB,
которое представля­
ет собой не требуюIЦУю администрирования реализацию основной функциональнос­
ти
SQL Server,
специально спроектированную для разработчиков. Благодаря
LocalDB
можно пропускать процесс настройки базы данных на время построения проекта, а
развертывание проводить в полном экземпляре
ложений
MVC
SQL Server
позже. Большинство при­
развертываются в размещаемых средах, которые обслуживаются про­
фессиональными администраторами, и наличие средства
LocalDB
означает, что кон­
фигурирование баз данных остается в руках администраторов баз данных, позволяя
разработчикам приложений заниматься написанием кода.
Visual Studio вы не выбрали LocalDB, то придется сделать это
LocalDB может быть выбрано через раздел lпdividual Compoпeпts
(Индивидуальные компоненты) в программе установки Visual Studio. Если вы следовали
инструкциям, приведенным в главе 2, тогда средство LocalDB должно быть установлено
Совет. Если при установке
сейчас. Средство
и готово к применению.
Установка пакета инструментов командной
строки Entity Framework Core
Основная функциональность
чанию, когда
кет
NuGet,
Visual Studio
Entity Framework Core добавляется
в проект по умол­
создает проект. Нам необходим один дополнительный па­
предоставляющий инструменты командной строки, которые используются
для создания классов, готовящих базу данных к хранению данных приложения. Такие
классы известны как миграции.
222
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Чтобы добавить пакет в проект. щелкнем правой кнопкой мыши на элементе
проекта
SportsStore
в окне Solutioп
Explorer.
выберем в контекстном меню пункт
Edit SportsStore.csproj
(Редактировать
SportsStore. csproj
изменения, показанные в листинге
SportsStore. csproj)
8.14.
и внесем в файл
Важно применять
версию, указанную в листинге. Кроме того, пакет добавляется с использованием эле­
мента
DotNetCli ToolReference,
а не
PackageReference,
который предназначен
для добавления существующего пакета.
На заметку! Пакет инструментов командной строки Eпtity
ливаться за счет редактирования файла
Framework Core
SportsStore. csproj.
могут быть добавлены с применением диспетчера пакетов
NuGet
должен устанав­
Пакеты такого типа не
либо инструмента ко­
мандной строки dotпet.
Листинг
8.14.
Добавление пакета в файле
из папки
SportsStore. csproj
SportsStore
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<DotNetCliToolReference
Version="2.0.0" />
Include="Мicrosoft.EntityFrameworkCore.Tools.DotNet"
</ItemGroup>
</Project>
После сохранения файла среда
ментов командной строки
Visual Studio загрузит и установит пакет инстру­
Entlty Framework Core, а также добавит их в проект.
Создание классов базы данных
Класс контекста базы данных -
это шлюз между приложением и
EF Core,
кото­
рый обеспечивает доступ к данным приложения с использованием объектов моделей.
Чтобы создать класс контекста базы данных для приложения
в папку
Models
файл класса по имени
приведенным в листинге
Листинг
8.15.
добавим
с определением,
8.15.
Содержимое файла
ApplicationDbContext.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Dependencyinjection;
namespace SportsStore.Models {
puЬlic
SportsStore,
ApplicationDbContext. cs
class ApplicationDbContext
DbContext {
из папки Мodels
Глава 8. SportsStore: реальное приложение
223
ApplicationDbContext(DbContextOptions<ApplicationDbCon text> options)
: base ( options) { )
puЫic
DbSet<Product> Products { get; set; )
puЫic
Базовый класс
DbContext
предоставляет доступ к лежащей в основе функцио­
а свойство Products обеспечивает доступ к объек­
Product в базе данных. К.ласе ApplicationDbContext является производным
DbContext и добавляет свойства, которые будут применяться для чтения и записи
нальности
Entity Framework Core,
там
от
данных приложения. В текущий момент имеется только одно свойство, которое пре­
доставит доступ к объектам.
Создание класса хранилища
Хотя в настоящее время может показаться иначе. но большая часть работы, требу­
емой для настройки базы данных, завершена. Следующий шаr заключается в созда­
нии класса, который реализует интерфейс
с использованием инфраструктуры
файл класса по имени
IProductRepository
Entity Framework Core.
EFProductReposi tory. cs
представленным в листинге
и получает данные
Добавим в папку
Models
с определением класса хранилища,
8.16.
Листинг В. 1б. Содержимое файла
EFProductReposi tory. cs
из папки
Models
using Systern.Collections.Generic;
using Systern.Linq;
narnespace SportsStore.Models {
class EFProductRepository : IProductRepository {
private ApplicationDbContext context;
puЬlic
EFProductRepository(ApplicationDbContext ctx) {
context = ctx;
puЬlic
puЫic IQueryaЬle<Product>
Products => context.Products;
По мере добавления средств к приложению в класс будет добавляться соответству­
ющая функциональность, а пока реализация хранилища просто отображает свойство
IProductReposi tory, на свойство Products,
ApplicationDbContext. Свойство Products в клас­
се контекста возвращает объект DbSet<Product>, который реализует интерфейс
IQueryaЫe<T> и облегчает реализацию интерфейса IProductRepository, когда
Products,
определенное в интерфейсе
которое определено в классе
применяется
Entity Framework Core.
В результате гарантируется, что запросы к базе
данных будут извлекать только те объекты, которые требуются, как объяснялось ра­
нее в главе.
Определение строки подключения
Строка подключения указывает местоположение и имя базы данных, а также
предоставляет конфигурационные настройки, с которыми приложение должно под-
224
Часть 1. Введение в инфраструктуру ASP.NEТ Саге MVC 2
ключаться к серверу базы данных. Строки подключения хранятся в файле
названием
appsettings. j son,
нием шаблона элемента
который создан в проекте
ASP.NET
Coпfiguratioп
File
SportsStore
JSON
под
с использова­
(Конфигурационный файл ASP.NEТ)
из раздела Geпeral (Общие) диалогового окна
При создании файла
Add New ltem.
appsettings. j son среда Visual Studio
добавляет в него за­
полнитель для строки подключения, который необходимо заменить содержимым лис­
тинга
8.17.
Совет. Строка подключения должна быть выражена как единственная неразрывная строка,
что выглядит нормально в редакторе Visual Studio, но не умещается на печатной стра­
нице, чем и объясняется неуклюжее форматирование в листинге 8.17. При определе­
нии строки подключения в собственном проекте удостоверьтесь, что значение элемента
ConnectionString
Листинг
8.17.
находится в одной строке.
Редактирование строки подключения в файле
из папки
appsettings. json
SportsStore
"Data": {
"SportStoreProducts":
"ConnectionString": "Server= (localdЫ \ \MSSQLLocalDB;DataЬase=SportsStore;
Trusted Connection=True;МultipleActiveResultSets=true"
}
-
Внутри раздела
Da ta конфигурационного файла имя строки подключения устанав­
SportStoreProducts. Значение элемента ConnectionString указывает,
базы данных по имени SportsStore должно применяться средство Loca\DB.
ливается в
что для
Конфигурирование приложения
Далее необходимо прочитать строку подключения и сконфигурировать приложе­
ние для ее использования при подключении к базе данных. В листинге
8.18 показаны
изменения, которые потребуется внести в класс
ных конфигурации, содержащихся в файле
конфигурирования Eпtity
обеспечивается классом
Листинг
8. 18.
(Работа, связанная с чтением файла
который будет описан в главе
Конфигурирование приложения в файле
из папки
using
using
using
using
using
using
using
using
using
Framework Core.
Program,
Startup, чтобы получать детали дан­
appsettings. j son, и применять их для
s tartup. cs
SportsStore
System;
System.Collections.Gener ic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Bui lder;
Microsoft.AspNetCore.Hos ting;
Microsoft.AspNetCore.Http ;
Microsoft.Extensions.Dep endencyinjection;
SportsStore.Models;
usingМicrosoft.Extensions.Confiquration;
14.)
JSON,
Глава 8. SportsStore: реальное приложение
225
usin9Мicrosoft.EntityFrameworkCore;
namespace SportsStore {
class Startup {
puЫic
Startup(IConfiquration confiquration) =>
Confiquration = confiquration;
puЫic
IConfiquration Confiquration ( 9et; }
puЫic
void ConfigureServices(IServiceCollection services)
=>
options.UseSqlServer(
Confiquration["Data:SportStoreProducts:ConnectionStrin9 "]));
services.AddTransient<IProductRepository, EFProductRepository>();
services.AddMvc();
puЫic
services.AddDЬContext<ApplicationDЬContext>(options
puЬlic
void Configure(IApplicationBuilder
арр,
IHostingEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
routes.MapRoute(
name: "default",
template: "{controller=Product}/{action=List}/{id?}");
}) ;
Startup конструктор получает данные конфигурации из
appset tings. j son, которые представлены посредством объекта, реализу­
ющего интерфейс IConfiguration. Конструктор присваивает объект реализации
IConfiguration свойству по имени Configuration, так что он может использо­
ваться в остальном коде класса Startup.
Добавленный в класс
файла
14 будет показано, как читать и обращаться к данным конфигурации. Для
SportsStore мы добавили в метод ConfigureServices () последователь­
вызовов методов, которая настраивает Entity Framework Core:
В главе
приложения
ность
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Confiquration["Data:SportStoreProducts:ConnectionStrin9 "]));
Расширяющий метод
AddDbCon text ()
настраивает службы, предоставляемые
создан в
Entity Framework Core, для контекста базы данных, который был
листинге 8.15. Как объясняется в главе 14, многие методы, применяемые
в классе
Startup,
инфраструктурой
позволяют конфигурировать службы и средства промежуточного
программного обеспечения с использованием аргументов
да
AddDbContext ()
options.
Аргументом мето­
является лямбда-выражение, которое получает объект
options,
конфигурирующий базу данных для класса контекста. В этом случае база данных
конфигурируется с помощью метода
ния, которая получена из свойства
UseSqlServer ()
Configuration.
и указания строки подключе­
226
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Еще одним изменением, внесенным в класс
была замена фиктивного
Startup,
хранилища реальным:
services. AddTransient<IProductReposi tory, EFProductRepository> () ;
Компоненты в приложении, работающие с интерфейсом
которым в настоящий момент относится только контроллер
будvт получать объект
EFProductReposi tory.
IProductRepository. к
Product, при создании
предоставляющий им доступ к инфор­
мации в базе данных. Подробные объяснения приводятся в главе
18,
а пока просто
знайте, что результатом будет гладкая замена фиктивных данных реальными из базы
данных без необходимости в изменении класса
ProductController.
Отключение проверки области видимости
Применение
Entity Framework Core
требует внесения изменения в конфигура­
цию средства внедрения зависимостей, которое рассматривается в главе
Класс
несет ответственность за запуск и конфигурирование
перед
Program
передачей управления классу
Startup,
и в листинге
8.19
18.
ASP.NET Core
показано необходимое из­
менение. Без такого изменения попытка создать схему базы данных в следvющем раз­
деле приведет к генерации исключения.
Листинг
8. 19.
Подготовка для работы с Eпtity
из папки
using
using
using
using
using
using
using
using
using
Framework Core
в файле
Program. cs
SportsStore
System;
System.Collections.Generic;
System.IO;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore;
Microsoft.AspNetCore.Hosting;
Microsoft.Extensions.Configuration;
Microsoft.Extensions.Logging;
namespace SportsStore (
puЫic class Program (
puЬlic static void Main(string[] args) (
BuildWebHost(args) .Run();
static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseDefaultServiceProvider(options =>
options.ValidateScopes = false)
. Build ();
puЫic
Конфигурирование
ASP.NET Core
подробно объясняется в главе
14,
а прос­
то отметим, что это единственное изменение, которое требуется для приложения
SportsStore.
Глава 8. SportsStore: реальное приложение
227
Создание миграции базы данных
Framework Core способна
Инфраструктура Eпtity
генерировать схему для базы дан­
ных, используя классы моделей, с помощью средства, которое называется миграция­
ми. При подготовке миrрации инфраструктура
команды
EF Core создает
класс С#, содержащий
которые нужны для подготовки базы данных. Если необходимо моди­
SQL,
фицировать классы моделей, тогда можно создать новую миграцию, которая содер­
жит команды
SQL,
требуемые для отражения изменений. В результате не приходится
беспокоиться о написании вручную и тестировании команд
SQL
и можно сосредото­
читься на классах модели С# в приложении.
Команды Eпtity
Framework Core выполняются из командной строки. Оrкроем окно ко­
PowerShell, перейдем в папку проекта SportsStore (ту, что со­
мандной строки или окно
держит файлы
Startup. cs
и
appsettings. j son)
и запустим следУЮщую команд.У. что­
бы создать класс миграции. который подготовит базу данных к первому использованию:
dotnet ef migrations add Initial
Когда команда завершит свое выполнение, в окне Solution Explorer среды Visual
Studio появится папка Migrations. Именно в ней инфраструктура Eпtity Framework
Core хранит классы миграции. Одно из имен файлов будет выглядеть как отметка
времени, за которой следует_ Ini tial.
cs,
и в этом файле определен класс, приме­
няемый для создания начальной схемы базы данных. Просмотрев содержимое тако­
го файла, можно выяснить, каким образом класс модели
Product.
использовался для
создания схемы.
Что насчет команд Add-Мigration и Update-DataЬase?
Если вы имеете опыт разработки с помощью Eпtity
нению команды
Add-Migration для
Framework,
то могли привыкнуть к приме­
создания миграции базы данных и команды
Update-
Database для ее применения к базе данных.
С появлением платформы
.NET Core
в инфраструктуру
Entity Framework Core
были добавле­
dotnet, который использу­
Microsoft. Enti tyFrameworkCore. Tools. DotNet, добавленный к проекту
ны команды, интегрированные в инструмент командной строки
ет пакет
в листинге
8.14.
Такие команды применяются в настоящей главе, поскольку они согласуются
с другими командами
PowerShell
.NET
и могут использоваться в любом окне командной строки или
в отличие от команд
только в специальном окне
Add-Migration
и
Update-Database,
которые работают
Visual Studio.
Создание начальных данных
Чтобы заполнить базу данных и предоставить какие-то тестовые данные, добавим
в папку
Models
листинге
Листинг
using
using
using
using
файл класса по имени
SeedData. cs
с определением, приведенным в
8.20.
8.20.
Содержимое файла
SeedData. cs
из папки
System.Linq;
Microsoft.AspNetCore.Builder;
Microsoft.Extensions.Dependencylnjection;
Microsoft.EntityFrameworkCore;
namespace SportsStore.Models {
Models
228
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
puЫic
static class SeedData (
static void EnsurePopulated(IApplicationBuilder арр)
ApplicationDbContext context = app.ApplicationServices
.GetRequiredService<ApplicationDbContext>();
context.Database.Migrate();
if (!context.Products.Any())
context.Products.AddRange(
new Product (
Name = "Kayak",
Description = "А boat for one person",
Category = "Watersports", Price = 275 },
new Product (
Name = "Lifejacket",
Description = "Protective and fashionaЫe",
Category = "Watersports", Price = 48.95m ),
new Product (
Name = "Soccer Ball",
Description = "FIFA-approved size and weight",
Category = "Soccer", Price = 19. 50m } ,
new Product {
Name = "Corner Flags",
Description = "Give your playing field а professional touch",
Category = "Soccer", Price = 34.95m },
new Product {
Name = "Stadium",
Description = "Flat-packed 35, 000-seat stadium",
Category = "Soccer", Price = 79500 },
new Product {
Name = "Thinking Сар",
Description = "Improve brain efficiency Ьу 75i",
Category = "Chess", Price = 16 } ,
new Product {
Name = "Unsteady Chair",
Description = "Secretly give your opponent а disadvantage",
Category = "Chess", Price = 29.95m },
new Product (
Name = "Human Chess Board",
Description = "А fun game for the family",
Category = "Chess", Price = 75 } ,
new Product (
Name = "Bling-Bling King",
Description = "Gold-plated, diamond-studded King",
Category = "Chess", Price = 1200
puЫic
}
) ;
context.SaveChanges();
Статический метод EnsurePopula ted () получает аргумент типа IAppl i са t i on
Builder. представляющий собой интерфейс, который применяется в методе
Configure () класса Startup для регистрации компонентов промежуточного про-
Глава 8. SportsStore: реальное приложение
229
граммного обеспечения, обрабатывающих НТТР-запросы, и именно здесь будет обес­
печено наличие в базе данных содержимого.
EnsurePopulated () получает объект ApplicationDbContext через ин­
IApplicationBuilder и вызывает метод Database .Migrate (),чтобы га­
Метод
терфейс
рантировать применение миграции, что означает создание и подготовку базы данных
к хранению объектов
Product.
Далее проверяется количество объектов
Product
в
базе данных. Если объекты в базе данных отсутствуют, то база данных заполняется
с использованием коллекции объектов
затем записывается с помощью метода
Product посредством
SaveChanges ().
метода
AddRange ()
и
Финальное изменение заключается в заполнении базы данных начальной инфор­
мацией при запуске приложения, для чего в класс
EnsurePopulated (),как
Листинг
Startup добавляется
вызов метода
8.21.
8.21. Заполнение базы данных начальной информацией
в файле
using
using
using
using
using
using
using
using
using
using
using
показано в листинге
s tartup. св
из папки
Sportss tore
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencylnjection;
SportsStore.Models;
Microsoft.Extensions.Configuration;
Microsoft.EntityFrameworkCore;
namespace SportsStore {
class Startup {
Startup(IConfiguration configuration) =>
Configuration = configuration;
puЫic IConfiguration Configuration { get; )
puЬlic void ConfigureServices(IServiceCollection services)
services.AddDbContext<ApplicationDbContext>(optio ns =>
options.UseSqlServer(
Configuration["Data:SportStoreProducts:Connectio nString"J) );
services.AddTransient<IProductRepository, EFProductRepository>();
services.AddMvc();
puЫic
puЫic
void Configure(IApplicationBuilder арр, IHostingEnvironment env) {
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => (
routes.MapRoute(
name: "default",
template: "{controller=Product)/{action=List)/{id?}");
puЫic
}) ;
Seed.Data.EnsurePopulated(app);
1
230
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
В результате запуска приложения база данных создается. заполняется и применя­
ется для обеспечения приложения своими данными . (Запаситесь терпением : создание
базы данных может занять некоторое время).
URL
Когда браузер запрашивает стандартный
ция приложения сообщает
MVC
для приложения, конфигура­
о необходимости создания контроллера
для обработки запроса. Создание контроллера
Product
Product
означает вызов конструк­
тора класса
ProductC o ntr o ller. которому требуется объект. реализующий ин­
IProductR epo si t o ry. и новая конфигурация указывает MVC о том. что
для этого должен быть создан и применен объект EFProductReposi tory. Объект
EFProductReposi tory обращается к функциональности Entity Framework Core.
которая загружает данные из SQL Server и преобразует их в объекты Pro d uct. Вся
упомянутая работа скрыта от класса ProductControl l er. который просто получает
объект. реализующий интерфейс IPro ductRe p ository. и пользуется данными, ко­
терфейс
торые он предоставляет. В итоге окно браузера отображает тестовую информацию из
базы данных (рис.
8.8).
D SportsSto~
С
х
Ф localhost 534{)5
Kayak
А
boat for
011е pe!'SO!l
S275.00
Lift-jacket
Protective авd
fasllio11aЫe
S48.95
Soccei· Ball
FIFA-appro\'ed size a11d \veigllt
Рис.
8.8.
Использование хранилища в виде базы данных
Такой подход с применением
ных
SQL Server
Entity Framework Core
для представления базы дан­
в виде последовательности объектов моделей отличается простотой
ASP.NET Core
EF Core, и большое
и легкостью, позволяя сосредоточить внимание на инфраструктуре
MVC.
Я опустил множество деталей, касающихся оперирования
количество доступных конфигурационных параметров. Мне нравится инфраструкту­
ра
Entity Framework Core,
и я рекомендую уделить время на ее изучение . Хорошей
отправной точкой послужит веб-сайт
Microsoft
для
docs.microsoft.com/ru-ru/ef / #pivot=efcore)
Framework
Саге, которая выйдет в издательстве
Entity Framework
Саге
(h t tps: / /
Entity
или моя будущая книга по
Apress.
Глава 8. SportsStore: реальное приложение
231
Добавление поддержки разбиения на страницы
8.8
На рис.
видно, что представление
List. cshtml
отображает сведения о товарах
в базе данных на одной странице. В этом разделе мы добавим поддержку разбиения
на страницы, чтобы представление отображало на странице меньшее число товаров,
а пользователь мог переходить со страницы на страницу для просмотра всего ката­
лога. Для решения задачи добавим в метод действия
параметр (листинг
Листинг
8.22.
List ()
контроллера
Product
8.22).
Добавление поддержки разбиения на страницы в файле
ProductController. cs
из папки
Controllers
using Microsoft.AspNetCore.Mvc;
using SportsStore.Models;
using System.Linq;
namespace SportsStore.Controllers
class ProductController : Controller
private IProductRepository repository;
puЫic
puЫic
int PageSize = 4;
ProductController(IProductRepository repo) (
repository = repo;
puЫic
puЫic
ViewResul t List (int productPage = 1)
=> View(repository.Products
.OrderBy(p => p.ProductID)
. Skip ( (productPage - 1)
.Take(PageSize));
Поле
PageSize
*
PageSize)
указывает, что на одной странице должны отображаться сведения
о четырех товарах. В метод
List ()
добавлен необязательный параметр, который
означает, что в случае вызова метода без параметра
(List
())вызов трактуется так,
словно ему было передано значение, указанное в определении параметра
(List ( 1) ).
В результате метод действия отображает первую страницу сведений о товарах, когда
инфраструктура
MVC
получаем объекты
вызывает его без аргумента. Внутри тела метода действия мы
Product,
упорядочиваем их по первичному ключу, пропускаем то­
вары, которые располагаются до начала текущей страницы, и выбираем количество
товаров, указанное в поле
PageSize.
Модульное тестирование: разбиение на страницы
Модульное тестирование средства разбиения на страницы можно провести, создав
имитированное хранилище, внедрив его в конструктор класса
L i s t ( ) , чтобы
Product можно сравнить
ProductController
и вызвав метод
запросить конкретную страницу. Затем полученные
объекты
с теми, которые ожидались от тестовых данных в ими­
тированной реализации. Написание модульных тестов обсуждалось в главе
7.
Ниже пока­
зан созданный для этой цели модульный тест, который находится в файле класса по имени
ProductControllerTests. cs,
добавленном в проект
SportsStore. Tests:
232
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
using
using
using
using
using
using
Systern.Collections.Generic;
Systern.Linq;
Moq;
SportsStore.Controllers;
SportsStore.Models;
Xunit;
narnespace SportsStore.Tests
puЫic
class ProductControllerTests
[Fact]
puЬlic
11
void Can_Paginate() {
Организация
Mock<IProductRepository> rnock = new Mock<IProductRepository>();
rnock.Setup(rn => rn.Products) .Returns( (new Product[]
new Product {ProductID
1, Narne
"Pl"},
new Product {ProductID
2, Narne
"Р2"},
new Product {ProductID
3, Narne
"Р3"},
new Product {ProductID
4, Narne
"Р4"},
new Product {ProductID
S, Narne
"PS"}
)) .AsQueryaЬle<Product>());
ProductController controller
controller.PageSize = 3;
11
new ProductController(rnock.Object);
Действие
result
controller.List(2) .ViewData.Model as
IEnurneraЫe<Product>
11
IEnurneraЫe<Product>;
Утверждение
Product[) prodArray = result.ToArray();
Assert.True(prodArray.Length == 2);
Assert.Equal ("Р4", prodArray[O] .Narne);
Assert.Equal ("PS", prodArray[l] .Narne);
Получение данных, возвращаемых из метода действия, выглядит несколько неуклюже.
Результатом является объект
ViewResult,
и значение его свойства
должно быть приведено к ожидаемому типу данных. В главе
ViewData.Model
17 рассматриваются разнооб­
разные результирующие типы, которые могут возвращать методы действий, а также спосо­
бы работы с ними.
Отображение ссылок на страницы
Запустив приложение, вы увидите, что теперь на странице отображаются четыре
позиции каталога. Если нужно просмотреть другую страницу, тогда в конец
URL мож­
но добавить параметр строки запроса, например:
http://localhost:S000/?productPage=2
Вы должны указать в
URL
номер порта, который был назначен вашему проекту.
Используя такие строки запросов, можно перемещаться по каталогу товаров.
У пользователей нет какого-либо способа выяснить о существовании таких пара­
метров строки запроса, но даже если бы он был, то вряд ли бы они захотели иметь
233
Глава 8. SportsStore: реальное приложение
дело с навигацией подобного вида. Взамен мы должны визуализировать в нижней
части каждого списка товаров ссылки на страницы, чтобы пользователи могли пе­
реходить между страницами. Мы реализуем вспомогательную функцию дескриптора,
которая будет генерировать НТМL-разметку для требуемых ссьmок.
Добавление модели представления
Чтобы обеспечить поддержку вспомогательной функции дескриптора, мы собира­
емся передавать представлению информацию о количестве доступных страниц, теку­
щей странице и общем числе товаров в хранилище. Проще всего это сделать, создав
класс модели представления, который специально применяется для передачи дан­
ных между контроллером и представлением. Создадим в проекте
ку
Models/ViewModels
листинге 8.23.
Листинг
8.23.
SportsStore
пап­
и добавим в нее файл класса с содержимым, приведенным в
Содержимое файла
Paqinqinfo. cs
из папки Мodels/ViewModels
using System;
namespace SportsStore.Models.ViewModels
class
int
puЫic int
puЫic int
puЫic
puЬlic
Paginginfo {
Totalitems { get; set; }
ItemsPerPage { get; set;
CurrentPage { get; set; }
int TotalPages =>
(int)Math.Ceiling( (decimal)Totalitems / ItemsPerPage);
puЫic
Добавление класса вспомогательной функции дескриптора
Теперь, имея модель представления, можно создать вспомогательную функцию
SportsStore папку Infrastructure и добавим
PageLinkTagHelper. cs с определением. показанным
дескриптора. Создадим в проекте
в нее файл класса по имени
в листинге
8.24.
Вспомогательные функции дескрипторов являются крупной частью
инфраструктуры ASP.NEТ
Core
МVС, и в главах
23-25
объясняется, как они работают
и каким образом их создавать.
Совет. В папку
Infrastructure
будут помещаться классы, которые предоставляют прило­
жению связующий код, но не имеют отношения к предметной области приложения.
Листинг
8.24.
Содержимое файла
из папки
using
using
using
using
using
using
PaqeLinkTaqHelper. cs
Infrastructure
Microsoft.AspNetCore.Mvc;
Microsoft.AspNetCore.Mvc.Rendering;
Microsoft.AspNetCore.Mvc.Routing;
Microsoft.AspNetCore.Mvc.ViewFeatures;
Microsoft.AspNetCore.Razor.TagHelpers;
SportsStore.Models.ViewModels;
234
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
namespace SportsStore.Infrastructure {
[HtmlTargetElement("div", Attributes = "page-model")]
class PageLinkTagHelper : TagHelper {
private IUrlHelperFactory urlHelperFactory;
puЫic
PageLinkTagHelper(IUrlHelperFactory helperFactory)
urlHelperFactory = helperFactory;
puЬlic
[ViewContext]
[HtmlAttributeNotBound]
puЬlic ViewContext ViewContext { get; set;
puЬlic
Paginglnfo PageModel { get; set;
puЬlic
string PageAction { get; set; }
override void Process(TagHelperContext context,
TagHelperOutput output) {
IUrlHelper urlHelper = urlHelperFactory.GetUrlHelper(ViewContext);
TagBuilder result = new TagBuilder("div");
for (int i = 1; i <= PageModel.TotalPages; i++) {
TagBuilder tag = new TagBuilder("a");
tag.Attributes["href"J = urlHelper.Action(PageAction,
new { productPage = i )) ;
tag.InnerHtml.Append(i.ToString());
result.InnerHtml.AppendHtml(tag);
puЫic
output.Content.AppendHtml(result.InnerHtml);
Класс вспомогательной функции дескриптора
элемент
di v
PageLinkTagHelper
заполняет
элементами а, которые соответствуют страницам товаров. Сейчас мы
не будем вдаваться в какие-либо детали относительно вспомогательных функций де­
скрипторов: достаточно знать. что они предлагают один из наиболее удобных спосо­
бов помещения логики С# в представления.
Код для вспомогательной функции дескриптора может выглядеть запутанным, по­
тому что смешивать С# и
HTML
непросто. Но использование вспомогательных функ­
ций дескрипторов является предпочтительным способом включения блоков кода С# в
представление, поскольку вспомогательную функцию дескриптора можно легко под­
вергать модульному тестированию.
Большинство компонентов
MVC,
таких как контроллеры и представления, обнару­
живаются автоматически, но вспомогательные функции дескрипторов должны быть
зарегистрированы.
В листинге
ки
Views
8.25
приведено содержимое файла
Viewlmports. cshtml
с добавленным оператором, который сообщает
MVC
тельные функции дескрипторов следует искать в пространстве имен
Infrastructure.
Кроме того, добавлено также выражение
из пап­
о том, что вспомога­
@using.
SportsStore.
чтобы на 1U1ассы
моделей представлений можно было ссылаться в представлениях, не указывая про­
странство имен.
Глава 8. SportsStore: реальное nриложение
Листинг
8.25.
235
Регистрация вспомогательной функции дескриптора
в файле_Viewimports.
cshtml из папки Views/Home
@using SportsStore.Models
@using
SportsStore.Мodels.ViewМodels
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTaqHelper SportsStore.Infrastructure.*, SportsStore
Модульное тестирование: создание ссылок на страницы
Чтобы протестировать класс вспомогательной функции дескриптора
вызывается метод Р
r
TagHelperOutput,
о се
ss ()
с тестовыми данными и
PageLinkTagHelper,
предоставляется объект
который инспектируется на предмет сгенерированной
разметки. Тест определен в новом файле
PageLinkTagHelperTests. cs
НТМL­
внутри проекта
SportsStore.Tests:
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Moq;
using SportsStore.Infrastructure;
using SportsStore.Models.ViewModels;
using Xunit;
namespace SportsStore.Tests {
puЬlic
class PageLinkTagHelperTests
[Fact]
puЫic
11
void Can_Generate Page Links()
Организация
var urlHelper = new Mock<IUrlHelper>();
urlHelper.SetupSequence(x => x.Action(It.IsAny<UrlActionContext>()))
.Returns("Test/Pagel"J
.Returns("Test/Page2")
.Returns("Test/Page3");
var urlHelperFactory = new Mock<IUrlHelperFactory>();
urlHelperFactory.Setup(f =>
f.GetUrlHelper(It.IsAny<ActionContext>()))
.Returns(urlHelper.Object);
PageLinkTagHelper helper =
new PageLinkTagHelper(urlHelperFactory.Object)
PageModel = new Paginginfo {
CurrentPage = 2,
Totalitems = 28,
ItemsPerPage = 10
},
PageAction = "Test"
1;
TagHelperContext ctx = new TagHelperContext(
new TagHelperAttributeList(),
new Dictionary<object, object>(), "");
236
Часть 1. Введение в инфраструктуру ASP NET Саге MVC 2
var content = new Mock<TagHelperContent>();
TagHelperOutput output = new TagHelperOutput ( "div",
new TagHelperAttributeList(),
(cache, encoder) => Task.FromResult(content.Object));
11
Действие
helper.Process(ctx, output);
11
Утверждение
Assert.Equal(@"<a href=""Test/Pagel"">l</a>"
+ @"<а href=""Test/Page2"">2</a>"
+@"<а href=""Test/Page3"">3</a>",
output.Content.GetContent() );
Сложность теста связана с построением объектов, которые требуются для создания и
применения вспомогательной функции дескриптора. Вспомогательные функции дескрип­
торов используют объекты реализаций
IUrlHelperFactory
для генерации
URL, ко­
торые указывают на разные части приложения. Для создания реализаций интерфейса
IUrlHelperFactory
и связанного с ним
данные, используется инфраструктура
IUrlHelper,
предоставляющего тестовые
Moq.
Основная часть теста проверяет вывод вспомогательной функции дескриптора с приме­
нением литерального строкового значения, которое содержит двойные кавычки. Язык С#
позволяет работать с такими строками при условии, что строка предварена символом @, а
вместо одной двойной кавычки внутри строки используется набор из двух двойных кавычек
(" ").
Вы должны помнить о том, что разносить литеральную строку по нескольким строкам
файла нельзя, если только строка, с которой производится сравнение, не разнесена ана­
логичным образом. Например, литерал, применяемый в тестовом методе, был размещен в
нескольких строках из-за недостаточной ширины печатной страницы. Символ новой строки
не добавлялся, иначе тест не прошел бы.
Добавление данных модели представления
Мы пока не готовы использовать вспомогательную функцию дескриптора, т.к. пред­
ставление еще не снабжено экземпляром класса модели представления
Это можно было бы сделать с применением объекта
ViewBag,
Paginginfo.
но лучше поместить все
данные, подлежащие передаче из контроллера в представление, внутрь единствен­
ного класса модели представления. Добавим в папку
SportsStore
файл класса по имени
приведенным в листинге
Листинг
8.26.
Models/ViewModels проекта
ProductsListViewModel. cs с содержимым.
8.26.
Содержимое файла
из папки
ProductsListViewModel. cs
Models/ViewModels
using System.Collections.Generic;
using SportsStore.Models;
namespace SportsStore.Models.ViewModels
puЫic class ProductsListViewModel {
puЬlic IEnumeraЬle<Product> Products
get; set; )
puЫic Paginglnfo Paginglnfo { get; set; )
Глава 8. SportsStore: реальное приложение
237
List () класса ProductController, чтобы
ProductsListViewModel для снабжения представления сведе­
Далее можно обновить метод действия
он использовал класс
ниями о товарах, отображаемых на страницах, и информацией о разбиении на стра­
ницы (листинг
Листинг
8.27.
8.27).
Обновление метода действия
в файле
List ()
ProductController.cs из папки Controllers
using Microsoft.AspNetCore.Mvc;
using SportsStore.Models;
using System.Linq;
usinq
SportsStore.Models.ViewМodels;
namespace SportsStore.Controllers
class ProductController : Controller
private IProductRepository repository;
puЫic int PageSize = 4;
puЫic
ProductController(IProductRepository repo) {
repository = repo;
puЫic
puЫic
ViewResult List(int productPage
1)
=> View(new ProductsListViewМodel (
Products = repository.Products
.OrderВy(p => p.ProductID)
. Skip( (productPaqe - 1) * PaqeSize)
.Take(PaqeSize),
Paqinqinfo = new Paqinqinfo (
CurrentPaqe = productPaqe,
ItemsPerPaqe = PaqeSize,
Totalitems = repository.Products.Count()
}) ;
Внесенные
изменения
ProductsListViewModel
обеспечивают
передачу
представлению
объекта
как данных модели.
Модульное тестирование:
данные разбиения на страницы для модели представления
Нам необходимо удостовериться в том, что контроллер отправляет представлению коррек­
тную информацию о разбиении на страницы. Ниже показан модульный тест, добавленный в
класс ProductControllerTests внутри тестового проекта, который обеспечивает такую
проверку:
[Fact]
void Can Send Pag1nation View Model() {
11 Организаци;
Mock<IProductRepository> mock = new Mock<IProductRepository>();
mock.Setup(m => m.Products) .Returns( (new Product[] {
new Product (ProductID = 1, Name = "Pl"},
puЫic
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
238
new
new
new
new
})
11
Product
Product
Product
Product
{ProductID
{ProductID
{ProductID
{ProductID
.AsQueryaЬle<Product>()
2,
3,
4,
5,
);
Name
Name
Name
Name
"Р2"},
"Р3"},
"Р4"},
"Р5"}
Организация
ProductController controller
new ProductController(mock.Object) { PageSize
11
3 };
Действие
ProductsListViewModel result =
controller.List(2) .ViewData.Model as ProductsListViewModel;
11
Утверждение
Paginginfo pageinfo = result.Paginginfo;
Assert.Equal(2, pageinfo.CurrentPage);
Assert.Equal(3, pageinfo.ItemsPerPage);
Assert.Equal(5, pageinfo.Totalitems);
Assert.Equal(2, pageinfo.TotalPages);
Потребуется также модифицировать ранее созданный модульный тест для проверки раз­
биения на страницы, содержащийся в методе Can _ Pag ina te
действия
List
() . Он полагается на метод
ViewResul t, свойством Model которого явля­
Product, но мы поместили эти данные внутрь еще од­
(),возвращающий объект
ется последовательность объектов
ного типа модели представления. Вот переделанный тест:
[Fact]
puЫic
11
void Can_Paginate()
Организация
Mock<IProductRepository> mock = new Mock<IProductRepository>();
mock.Setup(m => m.Products) .Returns( (new Product[J
new Product {ProductID
1, Name
"Pl"},
new Product (ProductID
2, Name
"Р2"},
new Product {ProductID
3, Name
"Р3"},
new Product {ProductID
4, Name
"Р4"},
new Product {ProductID
5, Name
"Р5"}
}) .AsQueryaЫe<Product>() );
ProductController controller
controller.PageSize = 3;
11
Действие
ProductsListViewМodel
controller.List(2)
11
new ProductController(mock.Object);
resul t =
.ViewData.Мodel
as
ProductsListViewМodel;
Утверждение
Product [ J prodArray = resul t. Products. ToArray () ;
Assert.True(prodArray.Length == 2);
Assert. Equal ( "Р4", prodArray [О] . Name) ;
Assert.Equal ("Р5", prodArray[l] .Name);
Учитывая объем дублированного кода в двух тестовых методах, обычно следовало бы со­
здать общий метод начальной установки. Однако, поскольку модульные тесты описаны в
индивидуальных врезках вроде текущей, их код представлен по отдельности, чтобы с каж­
дым тестом можно было ознакомиться независимо от других.
Глава 8. SportsStore: реальное приложение
239
В настоящий момент представление ожидает последовательность объектов
Product,
ге
8.28,
Листинг
поэтому файл
List. cshtml
необходимо обновить, как показано в листин­
чтобы иметь дело с новым типом модели представления.
8.28.
Обновление файла
List. cshtml
из папки
Views/Product
@model ProductsListViewModel
@foreach (var р in Model.Products)
<div>
<h3>@p.Name</h3>
@p.Description
<h4>@p.Price.ToString("c" )</h4>
</div>
Выражение
@model
было изменено для указания механизму
перь мы работаем с другим типом данных. Цикл
ником данных стало свойство
Products
foreach
Razor
на то, что те­
бьm обновлен, чтобы источ­
объекта модели.
Отображение ссылок на страницы
Наконец-то все готово для добавления в представление
List
ссылок на страни­
цы. Мы создали модель представления, которая содержит информацию о разбиении
на страницы, обновили контроллер, чтобы эта информация передавалась представ­
лению, и модифицировали выражение
@model,
приведя его в соответствие с новым
типом модели представления. Осталось добавить НТМL-элемент, который вспомога­
тельная функция дескриптора будет обрабатывать для создания ссылок на страницы
(листинг
Листинг
8.29).
8.29.
Добавление ссылок на страницы в файле
из папки
List. cshtml
Views/Product
@model ProductsListViewModel
@foreach (var р in Model.Products)
<div>
<h3>@p.Name</h3>
@p.Description
<h4>@p.Price.ToString("c" )</h4>
</div>
<div page-model="@Model.Pagin ginfo" page-action="List"></div>
Запустив приложение, вы увидите новые ссьшки на страницы (рис.
8.9).
Стиль
отображения довольно примитивен, но далее в главе он будет улучшен. Пока важно
то, что ссылки позволяют перемещаться по страницам каталога и знакомиться с то­
варами, выставленными на продажу. Когда механизм
page-model
в элементе
div,
он обращается к классу
Razor
обнаруживает атрибут
PageLinkTagHelper
образования элемента, что и дает набор ссылок, показанных на рис.
8.9.
для пре­
240
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
D SponsStoro
Ka~·ak
А
boat for ono p<rsoi1
Gold-plat<d, diamond-studdod King
S275.00
S79,500.00
Sl,200.00
TЫnldng Сар
ш
Lifejacktt
S4S.9S
Slб.00
Socct1· BaU
Uusttady Cbair
ш
Рис.
8.9.
Отображение ссылок для навигации по страницам
Почему бы просто не воспользоваться элементом управления GridView?
Если вы ранее работали с ASP.NET, то вам может показаться, что такой большой объем ра­
боты привел к довольно скромным результатам. Пришлось написать немало кода лишь для
того, чтобы получить список товаров с разбиением на страницы. Если бы мы прибегли к
услугам инфраструктуры
Web Forms,
то такого же результата можно было бы достичь с при­
менением готового элемента управления
привязав его напрямую к таблице
GridView или Lis t View
Produc t s базы данных.
из
ASP.NET Web Forms,
То, что было сделано в главе, вы глядит не слишком впечатляюще, но серьезно отличается
от процесса перетаскивания какого-то элемента управления на поверхность визуального
проектирования. Во-первых, мы строим приложение с надежной и удобной в сопровожде­
нии архитектурой, которая обеспечивает надлежащее разделение обязанностей. В отличие
от простейшего использования
Li s t v i ew
мы не связываем напрямую пользовательский
интерфейс и базу данных, что представляет собой подход, который дает быстрые резуль­
таты, но вызовет проблемы и сложности в будущем . Во-вторых, в ходе дела мы создаем
модульные тесты, что позволяет проверять поведение приложения естественным образом,
который практически невозможен в случае применения сложного элемента управления
Web
Forms. В-третьих, не забывайте, что значительная часть главы посвящена созданию инфра­
структуры, на основе которой строится приложение. Например, определить и реализовать
хранилище нужно только один раз, и теперь, когда оно имеется в нашем распоряжении,
новые функциональные средства можно строить и тестировать легко и быстро, как будет
продемонстрировано в последующих главах.
Глава 8. SportsStore: реальное nриложение
241
Разумеется, ничто из сказанного отнюдь не умаляет значимость безотлагательных результа­
тов, которые способна предложить инфраструктура Web
Forms,
но, как объяснялось в главе
3,
такая безотлагательность имеет свою цену, которая может оказаться высокой и болезнен­
ной в крупных и сложных проектах.
Улучшение
URL
Ссьmки на страницы работают, но для передачи информации серверу они по-пре­
жнему используют строку запроса следующего вида:
http://localhost/?productPage=2
Чтобы получить более привлекательные
следует шаблону компонуемых
URL.
URL,
необходимо создать схему, которая
Компонуемым называется
URL.
имеющий смысл
для пользователя, такой как показанный ниже:
http://localhost/Page2
Инфраструктура МVС позволяет легко изменять схему
URL в
приложении, потому
что применяет средство маршрутизацииАSР.NЕТ, которое отвечает за обработку
URL
для выяснения, на какую часть приложения они указывают. Понадобится лишь доба­
вить новый маршрут при регистрации промежуточного программного обеспечения
MVC
в методе
Листинг
8.30.
Configure ()
Startup
(листинг
Добавление нового маршрута в файле
из папки
using
using
using
using
using
using
using
using
using
using
using
класса
8.30).
Startup. cs
SportsStore
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencylnjection;
SportsStore.Models;
Microsoft.Extensions.Configuration;
Microsoft.EntityFrameworkCore;
namespace SportsStore {
puЫic
class Startup {
Startup(IConfiguration configuration) =>
Configuration = configuration;
puЫic
puЫic
IConf iguration Configuration { get; }
void ConfigureServices(IServiceCollection services)
services.AddDbContext<ApplicationDbContext>(optio ns =>
options.UseSqlServer(
Configuration["Data:SportStoreProducts:Connectio nString"]) );
services.AddTransient<IProductRepository, EFProductRepository>();
services.AddMvc();
puЫic
void Configure(IApplicationBuilder
app.UseDeveloperExceptionPage();
puЫic
арр,
IHostingEnvironment env) {
242
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
a pp.U seSta tus Code Pages() ;
app.U seS t a ticFile s ( ) ;
app.UseM vc (routes => {
routes.MapRoute(
name: "pagination",
template: "Products/Page{productPage}" ,
defaults: new ( Controller = "Product", action = "List" }) ;
rou tes .MapRout e(
name: "default",
temp lat e: " { c o n tro ller=Pr o d uc t} / { act ion= List} / { i d ?} " ) ;
) ) ;
Se ed Da t a . Ensu r ePopulate d (app);
Новый маршрут важно поместить перед стандартным маршрутом (по имени
d e f au l t) . который
уже присутствует в методе . Как будет показано в главе
15.
систе ма
маршрутизации обрабатывает маршруты в порядке их перечисления. а нам нужно.
чтобы новый маршрут имел преимущество перед существующим маршрутом.
Это единственное изменение. которое требуется внести. чтобы изменить сх е му
URL
для разбиения на страницы списка товаров . Инфраструктура МVС тесно интег­
рирована с функцией маршрутизации . а потому приложение автоматически отраж ает
изменение подобного рода в
URL,
используемых приложением. включая
URL.
которые
генерируются вспомогательными функциями дескрипторов вроде применяемых для
генерации ссылок на страницы. Не беспокойтесь. если маршрутизация пока не осо­
бенно понятн а
-
она будет подробно ра с сматриваться в главах
15
и
16.
Запустив приложени е и щелкнув на ссылке для какой-нибудь страницы. можно
наблюдать новую схему
С
ф
URL
в действии (рис.
8 . 10).
localhost .53406/Product•/Page2
Stadium
Flat-pack~d 35 ,000-s~a1
stad1um
$79,500.00
Рис.
8.10.
Новая схема
URL, отображаемая
в браузере
Стилизация содержимого
Мы построили значительную часть инфраструктуры и базовые средства приложе ­
ния и готовы к сборке всего воедино, но пока совершенно не уделяли внимание его
внешнему виду . Хотя данная книга не посвящена веб-ди з айну или
CSS ,
внешний вид
приложения SpoгtsStore настолько примитивен , что умаляет его технические достоин с ­
тва . В настоящем разделе мы постараемся исправить ситуацию . Мы собираемся р еали­
зовать классическую компоновку. содержащую два столбца и заголовок (рис.
8 . 11 ).
Глава 8. SportsSto re: реальное приложение
Home
• Watersports
• Soccer
• Chess
243
• Product 1
• Product 2
•
(main body)
•
Рис.
Установка пакета
8.11.
Целевой дизайн для приложения
SportsStore
Bootstrap
Для предоставления стилей
CSS, которые будут применены к приложению, мы пла­
Bootstrap. При установке пакета Bootstrap мы будем пола­
гаться на встроенную в Visual Studio поддержку инструмента Bower. Выберем шаблон
элемента Bower Configuration File (Файл конфигурации Bower) из категории General в
диалоговом окне Add New ltem, чтобы создать в проекте SportsStore файл по име­
ни bower. j son. как демонстрировалось в главе 6. Добавим в раздел depe ndenci es
созданного файла пакет Bootstrap, как показано в листинге 8.31. Ранее уже упомина­
лось, что в примерах книги применяется предварительный выпуск Bootstrap.
нируем использовать пакет
Листинг
8.31. Добавление пакета Bootstrap в файле bower. j son
из папки
SportsStore
"name": "asp.net ",
"private": t r ue,
" de pendencies": {
"bootstrap": "4 . 0.0-alpha.6"
После сохранения изменений, внесенных в файл
bowe r . j son, с реда Visual Studio
Bower загрузит пакет Bootstrap в папку wwwroo t / 1 i Ь/
Bootstrap зависит от пaкeтajQuery, который также автоматически
с помощью инструмента
boo tstrap.
Пакет
добавится в проект.
Применение стилей
В главе
5
Bootstrap
к компоновке
объяснялось. как работают представления
Razor,
как они используются и
как встраивать в них компоновки. Файл запуска представления, добавленный в нача­
ле главы, указывает, что в качестве стандартной компоновки должен выступать файл
по имени
Layout .cshtml,
именно к нему (листинг
так что начальную стилизацию
8.32).
Bootstrap
мы применим
244
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Листинг
8.32.
Применение СSS-файла
из папки
Bootstrap
к фaйлy
_Layout.cshtml
Views/Shared
<IDOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet"
asp-href-include="/lib/bootstrap/dist/**/*.min.csв"
asp-href-exclude="**/*-reboot*,**/*-grid*" />
<title>SportsStore</title>
</head>
<body>
<div clasв="navbar navbar-inverвe bg-inverse" role="navigation">
<а class"'"navbar-brand" href"'"*">SPORTS STORE</a>
</div>
<div class="row m-1 р-1">
<div id="categories" clasв,,,"col-3">
Put something useful here later
</div>
<div class•"col-9">
@RenderBody ()
</div>
</div>
</body>
</html>
Элемент
link
имеет атрибуты
asp-href-include
и
asp-href-exclude,
которые
представляют собой пример использования встроенного класса вспомогательной фун­
кции дескриптора. В этом случае вспомогательная функция дескриптора просмат­
ривает значения атрибутов и генерирует элементы
указанному атрибутом
include,
link
для всех файлов по пути,
и по путям, указанным атрибутами
exclude.
Пути
в атрибутах могут содержать групповые символы, что делает такой прием удобным
способом обеспечения возможности добавления и удаления файлов из структуры па­
пок
wwwroot,
не нарушая работу приложения. Но, как объясняется в главе
25,
следует
проявлять внимание, чтобы из указанных путей выбирались только те файлы, кото­
рые ожидается получить.
Добавление в компоновку таблицы стилей
CSS
из
Bootstrap
означает. что опреде­
ленные в ней стили можно применять в любом представлении, которое полагается на
данную компоновку. В листинге
Листинг
8.33.
8.33
стилизация используется в файле
Стилизация содержимого в файле
из папки
List. cshtml.
List.cshtml
Views/Product
@model ProductsListViewModel
@foreach (var р in Model.Products)
<div claвs,,,"card card-outline-primary m-1 р-1">
<div class,,,"bg-faded р-1">
<h4>
@p.Name
<span class="badge badge-pill badge-primary" style,,,"float: right">
Глава 8. SportsStore: реальное приложение
245
<small>@p.Price.ToString("c" )</small>
</span>
</h4>
</div>
<di v class=" card-text р-1 ">@р. Description</ di v>
</div>
)
<div page-model="@Model.Paginginf o" page-action="List"
page-classes-enaЫed="true"
page-class="Ьtn" page-class-normal="Ьtn-secondary"
page-class-selected="Ьtn-primary"
class="Ьtn-group
pull-right m-1">
</div>
Нам необходимо стилизовать кнопки, которые генерирует класс
но жестко встраивать классы
Bootstrap
PageLinkTagHelper,
в код нежелательно, т.к. в итоге затруднит­
ся повторное использование вспомогательной функции дескриптора где-то в другом
месте приложения либо изменение внешнего вида кнопок. Взамен мы определяем в
элементе
di v
специальные атрибуты, которые указывают требуемые классы и соот­
ветствуют свойствам, добавленным во вспомогательную функцию дескриптора. Затем
они применяются для стилизации создаваемых элементов а (листинг
Листинг
8.34.
Добавление классов к генерируемым элементам в файле
PageLinkTagHelper. cs
using
using
using
using
using
using
8.34).
из папки
Infrastructure
Microsoft.AspNetCore.Mvc ;
Microsoft.AspNetCore.Mvc .Rendering;
Microsoft.AspNetCore.Mvc .Routing;
Microsoft.AspNetCore.Mvc .ViewFeatures;
Microsoft.AspNetCore.Raz or.TagHelpers;
SportsStore.Models.ViewM odels;
namespace SportsStore.Infrastructur e {
[HtmlTargetElement("div", Attributes = "page-model")]
class PageLinkTagHelper : TagHelper {
private IUrlHelperFactory urlHelperFactory;
puЬlic
PageLinkTagHelper(IUrlHe lperFactory helperFactory)
urlHelperFactory = helperFactory;
puЫic
[ViewContext]
[HtmlAttributeNotBound]
puЬlic ViewContext ViewContext { get; set;
puЫic
Paginginfo PageModel { get; set;
puЬlic
string PageAction { get; set;
puЫic
puЫic
puЫic
puЫic
= false;
bool PageClassesEnaЫed ( get; set;
string PageClass { get; set; }
string PageClassNormal { get; set; 1
string PageClassSelected ( get; set;
override void Process(TagHelperContext context,
TagHelperOutput output) {
IUrlHelper urlHelper = urlHelperFactory.GetUrlHe lper(ViewContext);
TagBuilder result = new TagBuilder("div");
puЫic
246
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
for (int i = 1; i <= PageModel.TotalPages; i++) {
TagBuilder tag = new TagBuilder("a");
tag.Attributes["href"] = urlHelper.Action(PageAction,
new { productPage = i }) ;
if (PageClassesEnaЬled) {
tag.AddCssClass(PageClass);
tag.AddCssClass(i == PageМodel.CurrentPage
? PageClassSelected : PageClassNormal) ;
tag.InnerHtml.Append(i.ToString());
result.InnerHtml.AppendHtml(t ag) ;
output.Content.AppendHtml(result.InnerHtml);
Значения атрибутов
автоматически используются для
установки значений
свойств во вспомогательной функции дескриптора, учитывая отображение между
форматом имени атрибута
С#
(PageClassNormal).
HTML (page-class-normal)
и форматом имени свойства
Это позволяет вспомогательным функциям дескрипторов по­
разному реагировать в зависимости от атрибутов НТМL-элемента, обеспечивая более
гибкий способ генерации содержимого в приложении МVС.
Запустив приложение, легко заметить , что его внешний вид улучшился
ком случае. в какой-то степени (рис.
-
во вся­
8. 12).
D SportsS1or~
useful here later
Bling-Bling
Кing
Gold-plated, diamond-studded King
~----
- - - - - - - - ----------- -
Рис.
8.12.
Приложение
-------~
SportsStore с улучшенным дизайном
Создание частичного представления
В качестве завершающего штриха в главе мы проведем рефакторинг приложения,
чтобы упростить представление
List. cshtml.
Мы создадим частичное представле­
ние, являющееся фрагментом содержимого, которое можно внедрять в другое пред­
ставление подобно шаблону. Частичные представления подробно рассматриваются
в главе
21.
Они помогают сократить дублирование, когда одно и то же содержимое
Глава 8. SportsStore: реальное приложение
247
должно появляться в разных местах приложения. Вместо того чтобы копировать и
вставлять одинаковую разметку
Razor
во множество представлений, можно опре­
делить ее единожды в частичном представлении. Чтобы создать частичное пред­
ставление, добавим в папку
ProductSummary. cshtml
Листинг
8.35.
Views/Shared
файл представления
Razor
по имени
и поместим в него разметку, показанную в листинге
Содержимое файла ProductSuпunary.
cshtml
из папки
8.35.
Views/Shared
@model Product
<div class="card card-outline-primary m-1 р-1">
<div class="bg-faded р-1">
<h4>
@Model.Name
<span class="badge badge-pill badge-primary" style="float:right">
<small>@Model.Price.ToString("c")</small>
</span>
</h4>
</div>
<div class="card-text p-l">@Model.Description</div>
</div>
Теперь необходимо модифицировать файл
List. cshtml
из папки
чтобы в нем применялось частичное представление (листинг
Листинг
8.36.
Использование частичного представления в файле
из папки
Views/Product,
8.36).
List.cshtml
Views/Product
@model ProductsListViewModel
@foreach (var
р
in Model.Products)
@Htm1.Partial("ProductSummary",
р)
<div page-model="@Model.Paginglnfo" page-action="List" page-classesenaЫed="true"
page-class="btn" page-class-normal="btn-secondary"
page-class-selected="btn-primary" class="btn-group pull-right m-1">
</div>
Мы взяли код разметки, который ранее размещался в цикле
представления
List. cshtml,
foreach
внутри
и перенесли его в новое частичное представление.
Обращение к частичному представлению производится с помощью вспомогательного
метода
Html. Partial (),
которому в аргументах передаются имя представления и
объект модели представления. Подобного рода переход на частичное представление
является рекомендуемым приемом, поскольку он позволяет вставлять одну и ту же
разметку в любое представление, которое нуждается в отображении сводки о товаре.
Как видно на рис.
8.13,
добавление частичного представления не изменяет внешний
вид приложения; оно просто меняет место, где механизм
Razor
находит содержимое,
которое применяет для генерации ответа, отправляемого браузеру.
248
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
useful here later
Kayak
А
boat for one person
Lifejacket
.,,,,.
Proteetive and fashionaЫe
Рис.
8.13.
Использование частичного представления
Резюме
В главе была построена основная инфраструктура для приложения
SportsStore.
Пока что она не содержит достаточного набора функциональных средств. чтобы их
можно было продемонстрировать пользователю прямо сейчас, но "за кулисами" уже
имеются зачатки модели предметной области с хранилищем товаров. которое подде­
рживается
SQL Server и Entity Framework Core. В приложении присутствует единс­
Product. который может создавать разбитые на страницы спис­
товаров. а также настроена ясная и дружественная к пользователям схема URL.
твенный контроллер
ки
В настоящей главе могло показаться. что для получения весьма незначительных
выгод пришлось провести излишне трудоемкую начальную подготовку. но в следу­
ющей главе равновесие будет восстановлено. Теперь , когда фундаментальная струк­
тура на месте. можно двигаться дальше и добавлять все средства, ориентированные
на пользователя: навигацию по категориям, корзину для покупок и начальную форму
для оплаты.
ГЛАВА
9
навигация
SportsStore:
в настоящей главе мы продолжим построение примера приложения SportsStore,
добавив к нему поддержку навигации по приложению и начав создавать корзи­
ну для покупок.
Добавление навигационных элементов управления
Приложение
SportsStore станет более удобным,
если пользователи получат возмож­
ность навигации среди товаров по категории. Мы будем решать задачу в три этапа.
•
Расширим метод действия
List () в классе ProductController,
Product в хранилище.
чтобы он был
способен фильтровать объекты
URL.
•
Пересмотрим и расширим схему
•
Создадим список категорий, который будет находиться в боковой панели сай­
подсвечивая
та,
текущую
категорию
и
предоставляя
ссылки
на
остальные
категории.
Фильтрация списка товаров
Мы начнем с расширения класса модели представления
который бьш добавлен в проект
SportsStore
ProductsListViewModel,
в предыдущей главе. Нам нужно обес­
печить взаимодействие текушей категории с представлением, чтобы визуализировать
боковую панель, и это хорошая отправная точка. В листинге
внесенные в файл
Листинг
9.1.
ProductsListViewModel. cs
Добавление свойства в файле
из папки
из папки
9.1
ProductsListViewModel. cs
Models/ViewModels
using System.Collections.Gener ic;
using SportsStore.Models;
namespace SportsStore.Models.ViewM odels
class ProductsListViewModel {
get; set; }
Products
puЫic Paginginfo Paginginfo { get; set;
puЫic string CurrentCategory { get; set; }
puЫic
puЫic IEnumeraЬle<Product>
}
показаны изменения,
Models/ViewModels.
250
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
В масс
ProductsListViewModel добавлено
свойство по имени
Следующий шаг замючается в обновлении масса
действия
List ()
фильтровал объекты
Product
CurrentCategory.
ProductController, чтобы метод
по категории и использовал только
что добавленное в модель представления свойство для указания категории, выбран­
ной в текущий момент. Изменения приведены в листинге
Листинг
9.2.
Добавление поддержки категорий к методу действия
в файле
using
using
using
using
9.2.
ProductController. cs
из папки
List ()
Controllers
Microsoft.AspNetCore.Mvc;
SportsStore.Models;
System.Linq;
SportsStore.Models.ViewModels;
namespace SportsStore.Controllers {
class ProductController : Controller
private IProductRepository repository;
puЫic int PageSize = 4;
puЬlic
ProductController(IProductRepository repo) {
repository = repo;
puЫic
ViewResult List(string category, int productPage = 1)
=> View(new ProductsListViewModel {
Products = repository.Products
.Where(p => category = null 11 p.Category = category)
.OrderBy(p => p.ProductID)
.Skip( (productPage - 1) * PageSize)
.Take(PageSize),
Paginginfo = new Paginginfo {
CurrentPage = productPage,
ItemsPerPage = PageSize,
Totalitems = repository.Products.Count()
puЬlic
)
,
CurrentCategory
= category
) ) ;
В метод действия
List () внесены три изменения. Первое - добавлен новый
category. Он применяется вторым изменением, которое свя­
зано с расширением запроса LINQ. Если значение category не равно null, тогда
выбираются только объекты Product с соответствующим значением в свойстве
Category. Последнее, третье, изменение касается установки значения свойства
CurrentCategory, которое было добавлено в класс ProductsListViewModel.
Однако в результате таких изменений значение Paginginfo. Totalitems вычисля­
параметр по имени
ется некорректно, потому что оно не принимает во внимание фильтр по категории.
Со временем мы все исправим.
Глава 9. SportsStore: навигация
251
Модульное тестирование: обновление существующих модульных тестов
Мы изменили сигнатуру метода действия
List (),
поэтому некоторые существующие ме­
тоды модульного тестирования перестали компилироваться. Чтобы решить возникшую про­
блему, в модульных тестах, которые работают с контроллером, методу действия
List ()
null. Например, раздел действия
ProductControllerTests. cs должен
понадобится передавать в первом параметре значение
тестового метода
Can_Paginate ()
в файле
выглядеть следующим образом:
[Fact]
puЫic
11
void Can_Paginate ()
Организация
Mock<IProductRepository> mock = new Mock<IProductRepository>();
mock.Setup(m => m.Products) .Returns( (new Product[]
"Pl"),
1, Name
new Product {ProductID
"Р2"),
2, Name
new Product IProductID
"Р3"),
3, Name
new Product {ProductID
"Р4"},
4, Name
new Product {ProductID
5, Name
"Р5"}
new Product {ProductID
}) .AsQueryaЬle<Product>());
ProductController controller
controller.PageSize = 3;
11
new ProductController(mock.Object);
Действие
ProductsListViewModel result
controller.List(null, 2) .ViewData.Model as
11
ProductsListViewМodel;
Утверждение
Product[] prodArray = result.Products.ToArray();
Assert.True(prodArray.Length == 2);
Assert. Equal ( "Р4", prodArray [О] • Name) ;
Assert.Equal("P5", prodArray[l] .Name);
Указывая
null
для
category,
мы получаем все объекты
Product,
которые конт­
роллер извлекает из хранилища, что воспроизводит ситуацию перед добавлением но­
вого
параметра. Такого же рода изменение необходимо внести
в тестовый метод
Can_Send_Pagination_View_Model():
[Fact]
puЫic
11
void Can_Send_Pagination_View_Model() 1
Организация
Mock<IProductRepository> mock = new Mock<IProductRepository>();
mock.Setup(m => rn.Products) .Returns( (new Product[] 1
"Pl"},
new Product {ProductID = 1, Narne
"Р2"},
2, Narne
new Product {ProductID
"Р3"},
3, Narne
new Product {ProductID
"Р4"},
4, Narne
new Product {ProductID
"Р5"}
5, Narne
new Product {ProductID
}) .AsQueryaЬle<Product>());
11
Организация
ProductController controller
new ProductController(rnock.Object) ( PageSize
3 };
252
11
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
Действие
ProductsListViewModel result =
controller.List(null, 2) .ViewData.Model as
11
ProductsListViewМodel;
Утверждение
Paginginfo pageinfo = result.Paginginfo;
Assert.Equal ( 2, pageinf o .CurrentPage ) ;
Assert.Equal(З, pageinfo.ItemsPerPage);
Assert.Equal(5, pageinfo.Totalitems);
Assert.Equal(2, pageinfo.TotalPages);
Когда вы примете образ мышления, ориентированный на тестирование, поддержание мо­
дульных тестов в синхронизированном состоянии с изменениями кода быстро станет вашей
второй натурой.
Чтобы увидеть результат фильтрации по категории. запустим приложение и выбе­
рем категорию с помощью показанной ниже строки запроса (замените номер порта
тем. который был назначен вашему проекту средой
том, что
Soccer
начинается с прописной буквы
Visual Studio).
позаботившись о
S:
http://localhost:60000/?category=Soccer
Отобразятся только товары из категории
Soccer
(рис.
9.1).
Вряд ли пользователи захотят переходить по категориям с применением
здесь было показано, что незначительные изменения в приложении
MVC
зывать крупное влияние, если базовая структура на месте.
Put something
useful here later
Soccer Ball
FIFA-approved size and weight
-
~
-
--
Corner Flags
Give your playing field а professional toucl1
-
--
Clll~·!·l·!·t
Stadium
Flat-packed 35.000-seat stadium
11
Рис.
9.1.
2
3
Использование строки запроса для фильтрации по категории
URL.
но
могут ок<~­
Глава 9. SportsStore: навигация
253
Модульное тестирование: фильтрация по категории
Нам необходим модульный тест для проверки функциональности фильтрации по кате­
гории, чтобы удостовериться в том, что фильтр может корректно генерировать сведения
о товарах указанной категории. Ниже приведен тестовый метод, добавленный в класс
ProductControllerTests:
[Fact]
void Can Filter Products() {
- создание имитированного хранилища
Mock<IProductRepository> mock = new Mock<IProductRepository>();
mock.Setup(m => m.Products) .Returns( (new Product[] {
"Pl", Category = "Catl"},
new Product {ProductID = 1, Name
"Р2", Category = "Cat2"},
new Product { ProductID = 2, Name
"РЗ", Category = "Catl"},
new Product {ProductID = 3, Name
"Р4", Category = "Cat2"},
new Product { ProductID = 4, Name
"PS", Category = "СаtЗ"}
new Product { ProductID = 5, Name
}) .AsQueryaЫe<Product>());
puЫic
11
Организация
11
11
Организация
-
создание контроллера и установка размера страницы
в три элемента
ProductController controller = new ProductController(mock.Object);
controller.PageSize = З;
11
Действие
Product[] result =
(controller.List("Cat2", 1) .ViewData.Model as ProductsListViewModel)
.Products.ToArray();
11
Утверждение
Assert.Equa1(2, result.Length);
"Р2" && result[O] .Category
Assert.True(result[O] .Name
Assert.True(result[l] .Name == "Р4" && result[l] .Category
Тест создает имитированное хранилище, содержащее объекты
"Cat2");
"Cat2"};
Product, которые отно­
List () запрашивается
сятся к диапазону категорий. С использованием метода действия
одна специфическая категория, и результаты проверяются на предмет наличия корректных
объектов в правильном порядке.
Улучшение схемы
URL
Мало кому понравится
/?category=Soccer.
маршрутизации в методе
ный набор
URL
видеть либо
иметь дело с неуклюжими
URL
вроде
Для решения этой проблемы мы намерены изменить схему
(листинг
Configure ()
класса
Startup,
чтобы создать более удоб­
9.3).
Внимание! Новые маршруты в листинге
9.3
важно добавлять в показанном порядке.
Маршруты применяются в порядке, в котором они определены, поэтому изменение по­
рядка может привести к нежелательным эффектам.
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
254
Листинг
9.3.
puЬlic
void Configure(IApplicationBuilder
Изменение схемы маршрутизации в файле
Startup. cs
арр,
из папки
SportsStore
IHostingEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
routes.мapRoute(
null,
tem.plate: 11 {cate9ory}/Pa9e{productPa9e:int} 11 ,
defaults: new { controller = "Product", action
naпie:
= "List"
}
) ;
routes.мapRoute(
naпie:
null,
11 Pa9e{productPa9e:int} 11 ,
defaults: new { controller = 11 Product",
action = 11 List", productPage = 1 }
teшplate:
) ;
routes.мapRoute(
null,
tem.plate: 11 {cate9ory} 11 ,
defaults: new { controller = 11 Product 11 ,
action = 11 List 11 , productPage = 1 }
naпie:
) ;
routes.мapRoute(
null,
: 11 11 ,
defaults: new { controller
productPage = 1 }) ;
nаше:
teшpla te
routes.мapRoute(naпie:
)) ;
null,
= "Product",
teшplate:
action
= "List",
"{controller}/{action}/{id?}");
SeedData.EnsurePopulated(app);
В табл.
9.1
описана схема
URL,
которую представляют определенные маршруты.
Система маршрутизации подробно объясняется в главах
Таблица
9.1.
15 и 16.
Сводка по маршрутам
URL
Что делает
!
Выводит первую страницу списка товаров всех категорий
/Page2
Выводит указанную страницу (в данном случае страницу
2),
отображая товары всех категорий
/Soccer
/Soccer/Page2
Выводит первую страницу товаров указанной категории
Выводит указанную страницу (страницу
категории
(Soccer)
2)
(Soccer)
товаров заданной
Глава 9. SportsStore: навигация
Система маршрутизации
ASP.NET
используется инфраструктурой
MVC
для обра­
ботки входящих запросов от пользователей, но также генерирует исходящие
которые соответствуют схеме
URL
255
URL,
и потому могут встраиваться в веб-страницы.
Применение системы маршрутизации для обработки входящих запросов и генерации
исходящих
URL позволяет
Интерфейс
URL.
гарантировать согласованность всех
IUrlHelper
URL в
приложении.
предоставляет доступ к функциональности генерации
Мы использовали этот интерфейс и определяемый им метод
Action ()
в классе
вспомогательной функции дескриптора. созданном в предыдущей главе. Теперь, когда
нужно генерировать более сложные
URL,
необходим способ получения дополнитель­
ной информации от представления, не добавляя дополнительные свойства к классу
вспомогательной функции дескриптора. К счастью, классы вспомогательных функ­
ций дескрипторов обладают удобным средством, которое позволяет получать в одной
коллекции все свойства с общим префиксом (листинг
Листинг
9.4.
Получение значений атрибутов, снабженных префиксом,
в файле
using
using
using
using
using
using
9.4).
PageLinkTagHelper. cs
из папки
Infrastructure
Microsoft.AspNetCore.Mvc;
Microsoft.AspNetCore.Mvc.Rendering;
Microsoft.AspNetCore.Mvc.Routing;
Microsoft.AspNetCore.Mvc.ViewFeatures;
Microsoft.AspNetCore.Razor.TagHelpers;
SportsStore.Models.ViewModels;
using System.Collections.Generic;
namespace SportsStore.Infrastructure
[HtmlTargetElement("div", Attributes = "page-model")]
class PageLinkTagHelper : TagHelper {
private IUrlHelperFactory urlHelperFactory;
puЬlic
PageLinkTagHelper(IUrlHelperFactory helperFactory)
urlHelperFactory = helperFactory;
puЫic
[ViewContext]
[HtmlAttributeNotBound]
puЫic ViewContext ViewContext { get; set;
puЫic Paginginfo PageModel { get; set; }
puЫic string PageAction { get; set; }
[HtmlAttributeName(DictionaryAttributePrefix = "page-url-")]
Dictionary<string, object> PageUrlValues { get; set; }
= new Dictionary<string, object>();
puЫic
puЫic
puЫic
puЫic
puЫic
bool PageClassesEnaЫed { get; set; }
string PageClass { get; set; }
string PageClassNormal { get; set; }
string PageClassSelected { get; set;
=
false;
override void Process(TagHelperContext context,
TagHelperOutput output) {
IUrlHelper urlHelper = urlHelperFactory.GetUrlHelper(ViewContext);
TagBuilder result = new TagBuilder("div");
for (int i = 1; i <= PageModel.TotalPages; i++) {
TagBuilder tag = new TagBuilder("a");
puЫic
PageUrlValues [ "productPage"] = i;
tag.AttriЬutes["href"] = urlHelper.Action(PageAction, PageUrlValues);
256
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
if (PageClassesEnaЫed) {
tag.AddCssClass(PageClass);
tag.AddCssClass(i == PageModel.CurrentPage
? PageClassSelected : PageClassNormal);
tag.InnerHtml.Append(i.ToString());
result.InnerHtml.AppendHtml(tag);
output.Content.AppendHtml(result.InnerHtml);
Декорирование свойства в классе вспомогательной функции дескриптора посредс­
твом атрибута
HtmlAttributeName
позволяет указывать префикс для имен атрибу­
тов элемента. которым в данном случае будет
page-ur 1-.
Значение любого атрибута,
чье имя начинается с такого префикса. будет добавлено в словарь, присваиваемый
свойству
PageUr l Val ues,
чтобы сгенерировать
URL
которое затем передается методу
для атрибута
href
IUr lHelper. Action () ,
элементов а, выпускаемых вспомога­
тельной функцией дескриптора.
В листинге
9.5
к элементу
di v
добавлен новый атрибут. который обрабатывается
вспомогательной функцией дескриптора и указывает категорию, применяемую для
генерации
URL.
В представление был добавлен только один атрибут, но в словарь до­
бавился бы любой атрибут с тем же самым префиксом.
Листинг
9.5.
Добавление нового атрибута в файле List. cshtml из папки Views/Home
@model ProductsListViewModel
@foreach (var р in Model.Products)
@Html.Partial("ProductSummary",
р)
<div page-model="@Model.Paginglnfo" page-action="List"
page-classes-enaЫed="true"
page-class="btn" page-class-normal="btn-secondary"
page-class-selected="btn-priшary" page-url-category="@Мodel.CurrentCategory"
class="btn-group pull-right m-1">
</div>
До внесения этого изменения ссьтки для перехода на страницы генерировались так:
http://<cepвep>:<пopт>/Pagel
Если пользователь щелкнет на страничной ссылке подобного типа, то фильтр по
категории утрачивается и приложение отобразит страницу. содержащую товары всех
категорий. За счет добавления текущей категории. получаемой из модели представле­
ния. взамен генерируются
URL следующего
вида:
http://<cepвep>:<пopт>/Chess/Pagel
Когда пользователь щелкает на ссылке такого рода. текущая категория передается
методу действия
List ()
ния можно посещать
и фильтрация сохраняется. После внесения данного измене­
URL
вроде
/Chess
или
/Soccer
и наблюдать, что страничные
ссылки, расположенные внизу. корректно включают категорию.
Глава 9. SportsStore: навигация
257
Построение меню навигации по категориям
Нам нужно предложить пользователям способ выбора категории, который не пре­
цусматривает ввод
URL,
что означает необходимость в отображении списка доступ­
ных категорий с отмеченной текущей категорией, если она есть. По мере построения
приложения список категорий будет задействован в нескольких контроллерах, а по­
тому он должен быть самодостаточным и многократно используемым.
В инфраструктуре
ASP.NET Core MVC
поддерживается концепция компонентов
представлений, которые идеально подходят для создания единиц вроде многократно
используемого навигационного элемента управления. Компонент представления
-
это класс С#, который предлагает небольшой объем многократно используемой при­
кладной логики с возможностью выбора и отображения частичных представлений
Razor.
Компоненты представлений подробно рассматриваются в главе
22.
В данном случае мы создадим компонент представления, который визуализиру­
ет навигационное меню и интегрирует его в приложение за счет обращения к этому
компоненту из разделяемой компоновки. Подход подобного рода дает нам обычный
класс С#, который может содержать любую необходимую прикладную логику и ко­
торый можно подвергать моцульному тестированию подобно любому другому классу.
Такой способ удобен при создании небольших сегментов приложения с одновремен­
ным сохранением общего подхода
MVC.
Создание навигационного компонента представления
Создадим папку по имени
Components,
которая по соглашению является местом
хранения компонентов представлений, и добавим в нее файл класса под названием
NavigationMenuViewComponent. cs
Листинг
9.6.
с определением, показанным в листинге
9.6.
Содержимое файла NavigationМenuViewComponent.cs
из папки
Componen ts
using Microsoft.AspNetCore.Mvc;
namespace SportsStore.Components
puЫic
class NavigationMenuViewComponent : ViewComponent
string Invoke() {
return "Hello from the Nav View Component";
puЬlic
Метод
Invoke ()
компонента представления вызывается, когда компонент приме­
няется в представлении
Razor,
а результат, возвращаемый методом
Invoke
(),встав­
ляется в НТМL-разметку, отправляемую браузеру. Мы начали с простого компонента
представления, который возвращает строку, но вскоре заменим его динамическим
НТМL-содержимым.
Список категорий должен присутствовать на всех страницах, поэтому мы собира­
емся использовать компонент представления в разделяемой компоновке, а не в отде­
льном представлении. Внутри компоновки компоненты представлений применяются
через выражение
@await Component. InvokeAsync
(),как показано в листинге
9.7.
258
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Листинг
9.7.
Использование компонента представления в фaйлe
из папки
_Layout.cshtml
Views/Shared
< 1 DOCTYPE h tm l >
<html>
<hea d >
<me ta n ame= " v iewp o r t " cont ent=" width=dev ice - width " />
<link r e l = " st ylesheet"
asp- href- includ e= " / liЬ/boo ts t ra p/dist/ * * / *.min.cs s "
asp- href -ex cl u de= "* */ *-reb oot• , ••/ •- grid* " />
<t i tl e >SportsS t o r e</tit l e>
</ h e ad >
<bo dy >
<div cla ss= "navbar n avba r-inver s e bg- inver se " ro le="na vigatio n">
<а cla ss= "n a vbar-br a nd" href = "#" >SP ORTS ST ORE</ a>
</ div>
<d i v c l ass= " row m-1 р - 1 " >
<d i v id= " catego r ies " c l a s s =" col - 3 " >
@awai t Componen t. InvokeAsync ( "Naviga tionМenu" )
</div>
<div c lass= "col-9" >
@RenderBody ()
</ d iv>
</div>
</b ody>
</ html>
Текст заполнителя зам ен е н вызовом метода
Component . I nvokeAsync (). Аргумен­
ViewComp onen t. т.е. с п о мо­
щью Navigat i onMenu ука з ывается клас с Navi gati onMen uView Component . Запу стив
приложение, можно заметить. что вывод и з метода InvokeAsync ( ) включен в НТМL ­
том метода является имя класса компон е нта без части
разметку. отправляемую браузеру (рис .
9.2).
SportsStore
Hello from the
Thinking
Nav View
Cornponeпt
Сар
lmprove brain efficieпcy Ьу 75%
Unsteady Chair
Рис.
9.2.
Применение компонента представления
•+4t•
Глава 9. SportsStore: навигация
259
Генерация списков категорий
Теперь можно возвратиться к навигационному компоненту представления и сге­
нерировать реальный набор категорий. Построить НТМL-разметку для категорий
можно было бы программно, как делалось во вспомогательной функции дескриптора
для страничных ссылок, но одно из преимуществ работы с компонентами представ­
лений заключается в том, что они могут визуализировать частичные представления
Razoг. Это означает, что компонент представления можно использовать для генерации
списка категорий и затем применить более выразительный синтаксис Razoг для ви­
зуализации НТМL-разметки, которая отобразит данный список. Первым делом нужно
обновить компонент представления, как показано в листинге
Листинг
9.8.
9.8.
Добавление категорий в файле NavigationМenuViewComponent. cs
из папки
Componen ts
using Microsoft.AspNetCore.Mvc ;
using System.Linq;
using SportsStore.Models;
namespace SportsStore.Components
class NavigationMenuViewCompon ent : ViewComponent {
private IProductRepository repository;
puЫic
puЫic NavigationМenuViewComponent(IProductRepository
repo)
repository = repo;
IViewComponentResul t Invoke ()
return View(repository.Products
.Select(x => x.Category)
. Distinct ()
.OrderBy(x => х));
puЬlic
Конструктор,
определенный
IProductRepository.
в листинге
Когда инфраструктуре
9.8, принимает аргумент типа
MVC необходимо создать экземпляр
класса компонента представления, она отметит потребность в предоставлении это­
го аргумента и просмотрит конфигурацию в классе
Startup,
чтобы выяснить, какой
объект реализации должен использоваться. Мы имеем дело с тем же самым средством
внедрения зависимостей, которое применялось в контроллере из главы
будет аналогичным
-
8,
и результат
обеспечение компоненту представления доступа к данным без
необходимости знать используемую реализацию хранилища, как описано в главе
В методе
Invoke ()
с помощью
LINQ
18.
выбирается и упорядочивается набор кате­
горий в хранилище, после чего он передается в качестве аргумента методу
View (),
который визуализирует стандартное частичное представление Razoг. Детали час­
тичного представления возвращаются из метода с применением объекта реализа­
ции
IViewComponentResul t
главе
22).
(данный процесс будет подробно рассматриваться в
260
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Модульное тестирование: генерация списка категорий
Модульный тест, предназначенный для проверки возможности генерации списка категорий,
относительно прост. Цель заключается в том, чтобы создать список, который отсортиро­
ван в алфавитном порядке и не содержит дубликатов. Проще всего это сделать, построив
тестовые данные, которые имеют дублированные категории и не отсортированы должным
образом, передав их вспомогательной функции дескриптора и установив утверждение, что
данные были соответствующим образом очищены. Вот модульный тест, который определя­
ется в новом файле класса по имени
NavigationMenuViewComponentTests. cs
SportsStore. Tests:
System.Collections.Generic;
System.Linq;
Microsoft.AspNetCore.Mvc.ViewComponents;
Moq;
SportsStore.Components;
SportsStore.Models;
Xunit;
внут­
ри проекта
using
using
using
using
using
using
using
namespace SportsStore.Tests
puЫic
class NavigationMenuViewComponentTests
[Fact]
void Can_Select Categories() {
11 Организация
Mock<IProductRepository> mock = new Mock<IProductRepository>();
mock.Setup(m => m.Products) .Returns( (new Product[J {
new Product {ProductID
1, Name
"Pl", Category = "Apples"),
new Product {ProductID
2, Name
"Р2", Category = "Apples"),
new Product {ProductID
3, Name
"РЗ", Category = "Plums"),
new Product {ProductID
4, Name
"Р4", Category = "Oranges"),
)) .AsQueryaЫe<Product>());
puЫic
NavigationMenuViewComponent target =
new NavigationMenuViewComponent(mock.Object);
11 Действие - получение набора категорий
string[J results = ( (IEnumeraЫe<string>) (target.Invoke()
as ViewViewComponentResult) .ViewData.Model) .ToArray();
11 Утверждение
Assert. True (EnumeraЬle. SequenceEqual (new string [ J { "Apples",
"Oranges", "Plums" ) , results));
Мы создаем имитированную реализацию хранилища, которая содержит повторяющиеся и
несортированные категории. Затем мы устанавливаем утверждение о том, что дубликаты
удалены и восстановлен алфавитный порядок.
Создание представления
При работе с представлениями, которые выбираются компонентами представле­
ний, механизм
Razor использует разнообразные соглашения.
И стандартное имя пред­
ставления, и местоположения, в которых ищется представление. отличаются от тех,
Глава 9. SportsStore : нави гация
которые приняты для контроллеров . Создадим папку
NavigationMenu
Vi ews/S hared /Components /
Defaul t . cshtml с
и добавим в нее файл представления по имени
содержимым, приведенным в листинге
Листинг
261
9.9.
9.9. Содержимое файла Default.cshtml из папки Views/Shared/
Components/NavigationМenu
@model
<а
IEnumeraЫe<string >
class= "btn btn-Ьlock btn-secondary "
asp-action=" Li st"
asp - controller="Pr oduct "
as p-route-category= "" >
Ноте
</а>
@f o rea ch (string c a teg ory in Mode 1) {
<а
c l ass =" Ьtn Ьt n - Ьl oc k Ьtn - se co nd ary "
asp - action= " Lis t "
asp-contro ller="Prod uc t "
a sp-route - c a t egory="@category "
asp-route-p rod uctPage = "l" >
@category
</а>
В представлении применяется один из встроенных классов вспомогательных фун1щий дескрипторов (описанных в главах
hre f
которых содержит
URL.
24
и
25)
для создания элементов а, атрибут
выбирающий определенную категорию товаров.
После запуска приложения появятся сс ылки на категории (рис .
9.3) .
Щелчок н а
какой-то категории приводит 1< тому . что с писо к товаров обновляется, отображая
только товары выбранной к атегор ии .
Home
Soccer Ball
Chess
FIFA-approved size and weigl1t
Рис. 9.З. Генерация ссылок на категории с помощью ком понента представления
262
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
подсветка текущей категории
В настоящий момент пользователь не располагает какой-нибудь визуальной под­
сказкой о выбранной категории. Хотя, основываясь на товарах в списке, можно вы­
двинуть предположение относительно категории, гораздо лучше предоставить более
наглядную визуальную обратную связь. Компоненты
ASP.NET Core MVC,
такие как
контроллеры и компоненты представлений, могут получать информацию о текушем
запросе, обращаясь к объекту контекста. Большую часть времени заботу о получении
объекта контекста можно поручить базовым классам, которые используются для со­
здания компонентов, подобно тому, как базовый класс
Controller
применяется для
создания контроллеров.
Базовый класс
ViewComponent
не является исключением и обеспечивает доступ
к объектам контекста через набор свойств. Одно из свойств называется
и предоставляет информацию о том, как
URL
RouteData
запроса был обработан системой
маршрутизации.
В листинге
9.10
свойство
RouteData
используется для доступа к данным запро­
са, чтобы получить значение выбранной в текущий момент категории. Значение ка­
тегории можно было бы передать представлению путем создания еще одного класса
модели представления (и так бы делалось в реальном проекте), но ради разнообразия
применим объект
Листинг
9.1 О.
ViewBag,
который был введен в главе
2.
Передача выбранной категории в файле
Naviga tionМenuViewComponen t. cs
из папки
Componen ts
using Microsoft.AspNetCore.Mvc;
using System.Linq;
using SportsStore.Models;
namespace SportsStore.Components
class NavigationMenuViewComponent : ViewComponent {
private IProductRepository repository;
puЬlic
NavigationMenuViewComponent(IProductRepository repo)
repository = repo;
puЬlic
puЬlic
IViewComponentResult Invoke() {
ViewBag.SelectedCategory
= RouteData?.Values["category"];
return View(repository.Products
.Select(x => x.Category)
. Distinct ()
.OrderBy(x => х));
Внутри метода
в объекте
ViewBag
Invoke ()
мы динамически создаем свойство
SelectedCategory
и устанавливаем его значение равным значению текушей катего­
рии, которое получаем через объект контекста, возвращенный свойством
Как объяснялось в главе
2, ViewBag
RouteData.
представляет собой динамический объект, кото­
рый позволяет определять новые свойства, просто присваивая им значения.
Глава 9. SportsStore: навигация
263
Модульное тестирование: сообщение о выбранной категории
Для выполнения проверки того, что компонент представления корректно добавил детали о
выбранной категории, в модульном тесте можно прочитать значение свойства
ViewBag,
ViewViewComponentResul t, описанный в главе 22. Ниже
показан модульный тест, добавленный в класс NavigatioMenuViewComponentTests:
которое доступно через класс
[F'act]
puЬlic
11
void Indicates Selected Category()
Организация
string categoryToSelect
"Apples";
Mock<IProductRepository> mock = new Mock<IProductRepository>();
mock.Setup(m => m.Products) .Returns( (new Product[]
new Product {ProductID = 1, Name
"Pl", Category
"Apples"},
"Oranges"},
new Product {ProductID = 4, Name = "Р2", Category
}) .AsQueryaЫe<Product>());
NavigationMenuViewComponent target =
new NavigationMenuViewComponent(mock.Object);
target.ViewComponentContext = new ViewComponentContext
ViewContext = new ViewContext {
RouteData = new RouteData()
}
};
target.RouteData.Values["category"] = categoryToSelect;
11
Действие
string result = (string) (target. Invoke () as
ViewViewComponentResul t). ViewData [ "SelectedCategory");
11
Утверждение
Assert.Equal(categoryToSelect, result);
Модульный тест снабжает компонент представления данными маршрутизации через свойс­
тво
ViewComponentContext,
посредством которого компоненты представлений полу­
чают все свои данные контекста. Свойство
ViewComponentContext обеспечивает до­
ступ к данным контекста, специфичным для представления, с помощью своего свойства
ViewContext,
которое в свою очередь обеспечивает доступ к информации о маршрути­
зации через собственное свойство
RouteData.
Большая часть кода в модульном тесте
связана с созданием объектов контекста, которые будут предоставлять выбранную катего­
рию таким же способом, как она бы предлагалась во время выполнения приложения, когда
данные контекста предоставляются инфраструктурой
ASP.NET Core MVC.
Теперь, когда доступна информация о том, какая категория выбрана, можно обно­
вить представление, выбираемое компонентом представления, чтобы задействовать
эту информацию, и изменить классы
CSS,
которые используются для стилизации
ссылок, сделав представление текущей категории отличающимся от остальных кате­
горий. В листинге
9.11 приведено изменение, внесенное в файл Defaul t. cshtml.
264
Часть 1. Введение в инфраструктуру ASP.NET Соге MVC 2
Листинг
9.11.
Подсветка текущей категории в файле
Defaul t. cshtml
из папки Views/Shared/Components/NavigationМenu
@model
<а
IEnum era Ыe<string >
c lass = "Ьt n btn-Ьlock Ьtn-secondary"
asp -a ction = "List"
asp-controller="Product"
asp-route- ca tegory="" >
Ноте
</а>
@foreach (stri ng category in Mode l) {
<а class="Ьtn btn-Ыock
@ (category
=
ViewВag.SelectedCategory?
"btn-primary": "btn-secondary")"
asp-action="List"
asp-controller="Product"
asp-route-category="@category"
asp-route-productPage="l">
@ca tegory
< /а>
С помощью выражения
Razoг внутри атрибута с
1а s s
мы применяем класс
Ьtn -prim ary к элементу, который представляет выбранную категорию. и класс
Ьtn -second ary к остальным элементам. Указанные классы применяют разные стили
Bootstrap
и делают активную кнопку визуально отличающейся (рис .
Home
Thinking
Chess
lmprove brain efficiency
Soccer
Unsteady Chair
fiQ.j,$
Сар
Ьу
75%
Secretly give your opponent а disadvantage
Watersports
Рис.
9.4.
9.4).
Подсвечивание выбранной категории
•*
Глава 9. SportsStore: навигация
265
Корректировка счетчика страниц
Мы должны скорректировать ссылки на страницы. чтобы они правильно работали ,
когда выбрана какая-то категория. В настоящий момент количество ссылок на стра­
ницы определяется общим числом товаров в хранилище, а не количеством товаров
выбранной категории. Это значит. что пользователь может щелкнуть на ссылке для
страницы
2
категории
Chess
и получить пустую страницу. поскольку товаров данной
категории не хватает для заполнения второй страницы . Проблема демонстрируется
на рис.
9.5.
Chess
Sоссег
Watersports
Рис.
9.5. Отображение некорректных ссылок на страницы , когда выбрана какая-то категория
Проблему можно устранить. модифицировав метод действия
ре
Product
мание (листинг
Листинг
9.12.
Создание данных о разбиении на страницы, учитывающих категории,
ProductController. cs
из папки
Controllers
Microsoft.As pNetCore.Mvc;
SportsS to re.Models;
System.Linq;
SportsStore .Models.Vi e wMode ls;
namespa ce SportsStore.Control ler s {
c lass ProductControl l er : Controll e r
private IPr oductReposit or y re pository;
puЫic int PageSize = 4;
puЫic
ProductController {IProductReposit ory re po) {
r epository = repo;
p u Ыic
в контролле­
9. 12).
в файле
us ing
usin g
us i ng
using
Li s t ()
так. чтобы при разбиении на страницы категории принимались во вни­
266
Часть 1. Введение в инфраструктуру ASP.NET Соге MVC 2
pu Ы ic
ViewResu l t List(s t r i ng category , i nt productPage
1)
=> Vi ew(ne w Prod uctsL i s tViewMode l (
Produ cts = rep o s i t o ry.Produ cts
. Wh e r e (р => c ategor y == n ul l 1 1 р. Ca tegor y
ca tegor y )
. Ord e r By( p => p . Prod u ct I D)
. S k ip( (p r oductPage - 1 ) * PageSize)
. Ta ke(P age S iz e) ,
Pag i ngl nfo = ne w Pa gingiп fo {
Cu rre ntPag e = productPag e ,
Ite ms Pe rPag e = Pa geSiz e ,
Total items = catego r y == null ?
r e po s i t o r y . Produc ts. Count()
re p o sit o ry. Pr odu cts.Where(e =>
e .Categ o ry = = categ ory) . Co unt()
}'
Current Catego r y
=
c ateg ory
}) ;
Если категория была выбрана, тогда возвращается количество позиций в ней. а
если нет, то общее число товаров. Теперь во время просмотра товаров в какой-либо
категории ссылки в нижн е й части страницы корректно отражают количество товаров
в этой категории (рис.
9.6) .
Home
Thinking
Chess
lmprove brain efficiency
Soccer
Unsteady Chair
Watersports
Secretly give
Сар
уоuг
Ьу
75%
opponent
а
disadvantage
Human Chess Board
А
fun game for the family
Bling-Bling
Кing
"
"
-
CjfJ.!Ф!.8
Gold-plated, diamond-studded King
-
11
Рис.
9.6.
Отображение ссылок на страницы с учетом выбранной категории
Глава 9. SportsStore: навигация
267
Модульное тестирование: счетчик товаров определенной категории
Протестировать возможность генерации корректных счетчиков товаров для различных
категорий очень просто. Мы создадим имитированное хранилище, которое содержит из­
вестные данные в определенном диапазоне категорий, и затем вызовем метод действия
List (), запрашивая каждую категорию
ProductControllerTests:
по очереди. Вот модульный тест, добавленный в
класс
[Fact]
puЬlic
11
void Generate Category_Specific_Product Count() {
Организация
Mock<IProductRepository> mock = new Mock<IProductRepository>();
mock.Setup(m => m.Products) .Returns( (new Product[] (
"Catl"},
"Pl", Category
1, Name
new Product (ProductID
"Cat2"},
"Р2", Category
new Product {ProductID = 2, Name
"Catl"},
"Р3", Category
new Product {ProductID = 3, Name
"Cat2"},
"Р4 ", Category
new Product {ProductID = 4, Name
"Cat3"}
"Р5", Category
new Product {ProductID = 5, Name
}) .AsQueryaЫe<Product>() );
ProductController target = new ProductController(mock.Object);
target.PageSize = 3;
Func<ViewResult, ProductsListViewModel> GetModel = result =>
result?.ViewData?.Model as ProductsListViewModel;
11
Действие
int?
int?
int?
int?
11
resl
res2 =
res3 =
resAll
GetModel(target.List("Catl") )?.Paginginfo.Totalitems;
GetModel(target.List("Cat2") )?.Paginginfo.Totalitems;
GetModel(target.List("Cat3")) ?.Paginginfo.Totalitems;
= GetModel(target.List(null) )?.Paginginfo.Totalitems;
Утверждение
Assert.Equal(2,
Assert.Equal(2,
Assert.Equal (1,
Assert.Equal(5,
resl);
res2);
res3);
resAll);
Обратите внимание, что в модульном тесте также вызывается метод
List ()
без указания
категории, чтобы удостовериться в правильности подсчета общего количества товаров.
Построение корзины для покупок
Приложение успешно расширяется, но продавать какие-либо товары до тех пор,
пока не будет реализована корзина для покупок, не удастся. В настоящем разделе мы
создадим корзину для покупок согласно иллюстрации на рис.
9.7.
Если вы приобрета­
ли что-нибудь в электронных магазинах, то она должна выглядеть знакомой.
268
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Товары
Ввод информации
Ваша корзина
о доставке
Футбольный мяч 1Добавить в корзину 1
1 х Стадион
$79,500.00
Угловые флажки 1Добавить в корзину 1
Всего
$79,500.00
Стадион
Добавить в корзину
...ит.д ".
Перейти к оплате
Продолжить покупку
Рис.
9.7.
Базовый поток корзины для покупок
Кнопка доб а вления в корзину
{Add to cart)
будет отображатьс я рядом с каждым
товаром в каталоге. Щелчок на ней будет приводить к выводу сводки по товарам. ко­
торые уже были выбраны пользователем , включая их общую стоимость. В этой точк е
пользователь может с помощью кнопки продолжения п о купки (Coпtiпue shoppiпg) воз­
вратиться в каталог товаров, а посредством кнопки перехода к оплате
{Checkout now)
сформировать заказ и завершить сеан с покупки .
Определение модели корзины
Начнем с добавления в папку
Mode ls файла класса по имени Cart. cs с определ е ­
ниями . показанными в листинге
Листинг
9.13.
Содержимое файла
9. 13.
Cart. cs
из папки
Models
u s ing Syst e m. Co llecti o n s.G eneric;
us i n g System.Linq;
na me s p ac e Spo rt sSto r e . Models {
p uЫ i c
c l ass Ca rt {
privat e Li s t <CartLi ne> lineColl ectio n = new Lis t<Cart Lin e> ( ) ;
рuЫ1с
virtual voi d Add item(Pr od u ct produ ct , i nt quan tity)
Car tLi n e l i n e = l ineCo l l e c t io n
. Wh e r e(p => p . Pr oduct . Pr oductI D == p roduc t . Pro d uctID)
. Fi rs t OrDe f au l t() ;
if (l i n e == null) {
line Co llection.Ad d(new CartL i n e {
Pr od u c t = pro duc t,
Quant i ty = quantity
)) ;
el se {
line .Qua ntity += qua ntity;
puЫ ic
vi r tual void Re move Li n e ( Product product) = >
li neCo l lectio n. RemoveAll (l => l . Product .Produ c t I D == produc t.P r oductI D) ;
puЬlic
v i r tual dec i ma l Comput e To t al Value () =>
lineColl ec tion.Sum( e = > e.Pr oduc t.Price * e.Q uantity ) ;
p u Ыi c
virt u a l voi d Cl ear() = > lineCo l l e cti o n . Cle a r ( ) ;
pu Ьl ic
vi r t ua l
IEn um e raЫe<Ca rt L i n e>
Line s = > l ineCo l le c tion ;
Глава 9. SportsStore: навигация
269
puЫic
class CartLine {
int CartLineID { get; set; )
puЬlic Product Product { get; set; )
puЬlic int Quantity { get; set; )
puЫic
Класс
Cart использует класс CartLine. который определен в том же самом файле
и представляет товар. выбранный пользователем, а также приобретаемое количество
товара. Мы определили методы для добавления элемента в корзину. удаления элемен­
та из корзины, вычисления общей стоимости элементов в корзине и очистки корзины
путем удаления всех элементов. Мы также предоставили свойство, которое позволяет
обратиться к содержимому корзины с применением IEnurneraЫe<CartLine>. Все
они легко реализуются с помощью кода С# и небольшой доли кода
LINQ.
Модульное тестирование: проверка корзины
Класс
Cart
относительно прост, но в нем присутствует ряд важных линий поведения, кото­
рые должны корректно работать. Неправильно функционирующая корзина нарушит работу
всего приложения
SportsStore.
Мы разобьем средства на части и протестируем их по отде­
льности. Для размещения тестов в проекте
имени
SportsStore. Tests
создан новый файл по
CartTests. cs.
Первая линия поведения относится к добавлению элемента в корзину. При первоначаль­
ном добавлении в корзину объекта
CartLine.
Product
должен быть добавлен новый экземпляр
Ниже представлен тестовый метод вместе с определением класса модульного
тестирования.
using System.Linq;
using SportsStore.Models;
using Xunit;
namespace SportsStore.Tests
puЬlic
class CartTests {
[Fact]
puЬlic
11
void Can_Add New Lines()
Организация
-
{
создание нескольких тестовых товаров
Product pl = new Product
ProductID
1, Name
"Pl" );
Product р2 = new Product { ProductID = 2, Name = "Р2" ) ;
11
Организация
-
создание новой корзины
Cart target = new Cart ();
11
Действие
target.Additem(pl, 1);
target.Additem(p2, 1);
CartLine[] results = target.Lines.ToArray();
11
Утверждение
Assert.Equal(2, results.Length);
Assert.Equal (pl, results [О] .Product);
Assert. Equal (р2, resul ts [ 1] . Product);
270
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Но если пользователь уже добавлял объект
Product в корзину, тогда необходимо увели­
CartLine, а не создавать новый. Вот мо­
чить количество в соответствующем экземпляре
дульный тест:
[Fact]
puЫic
void Can_Add Quantity For Existing Lines() {
- создание нескольких тестовых товаров
Product pl = new Product { ProductID
1, Name
"Pl" };
Product р2 = new Product { ProductID = 2, Name = "Р2" };
11
Организация
11 Организация - создание
Cart target = new Cart () ;
11
новой корзины
Действие
target.Addltem(pl, 1);
target.Addltem(p2, 1);
target.Additem(pl, 10);
CartLine[] results = target.Lines
.OrderBy(c => c.Product.ProductID) .ToArray();
11
Утверждение
Assert.Equal(2, results.Length);
Assert. Equal ( 11, resul ts [О] . Quanti ty);
Assert. Equal ( 1, resul ts [ 1] . Quanti ty);
Также понадобится проверить, что пользователи имеют возможность менять свое решение
и удалять товары из корзины. Эта линия поведения реализуется методом
RemoveLine () .
Модульный тест выглядит следующим образом:
[Fact]
puЫic
void Can_Remove Line() {
- создание нескольких
Product pl
new Product
ProductID
Product р2 = new Product
ProductID
Product р3 = new Product
ProductID
11
Организация
11 Организация - создание
Cart target = new Cart();
товаров
1 , Name
"Р 1 "
2, Name
"Р2
3, Name
"Р3"
11
);
} ;
};
новой корзины
11 Организация - добавление
target.Additem(pl, 1);
target.Additem(p2, 3);
target.Additem(p3, 5);
target.Addltem(p2, 1);
11
тестовых
некоторых товаров в корзину
Действие
target.RemoveLine(p2);
11
Утверждение
Assert.Equal(O, target.Lines.Where(c => c.Product
Assert.Equal(2, target.Lines.Count());
р2)
.Count ());
Далее проверяется линия поведения, связанная с возможностью вычисления общей стои­
мости элементов в корзине. Вот модульный тест:
Глава 9. SportsStore: навигация
271
[Fact]
void Calculate Cart Total() {
- создание нескольких тестовых товаров
"Pl", Price
1, Name
ProductID
Product pl = new Product
Product р2 = new Product { ProductID = 2, Name = "Р2", Price
puЫic
11
Организация
// Организация - создание
Cart target = new Cart();
11
lOOM );
);
50М
новой корзины
Действие
target.Additem(pl, 1);
target.Additem(p2, 1);
target.Additem(pl, 3);
decimal result = target.ComputeTotalValue ();
11
Утверждение
Assert.Equal(450M, result);
Последняя проверка очень проста. Мы должны удостовериться, что в результате очистки
корзины ее содержимое корректно удаляется. Ниже показан модульный тест.
[Fact]
void Can_Clear Contents() {
- создание нескольких тестовых
1, Name
ProductID
Product pl = new Product
Product р2 = new Product { ProductID = 2, Name
puЬlic
11
11
Организация
- создание
new Cart();
Организация
Cart target
=
=
"Pl", Price
"Р2", Price
lOOM );
50М ) ;
новой корзины
11 Организация - добавление
target.Additem(pl, 1);
target.Additem(p2, 1);
11 Действие - очистка
target.Clear();
товаров
нескольких элементов в корзину
корзины
//Утверждение
Assert.Equal(O, target.Lines.Count());
Временами, как в данном случае, код для тестирования функциональности класса получает­
ся намного длиннее и сложнее, чем код самого класса. Не допускайте, чтобы это приводило
к отказу от написания модульных тестов. Дефекты в простых классах, особенно в тех, кото­
рые играют настолько важную роль, как
Cart
в приложении
SportsStore,
могут оказывать
разрушительное воздействие.
Создание кнопок добавления в корзину
Нам необходимо модифицировать частичное представление Views/Shared/
ProductSummary. cshtml, добавив кнопки к спискам товаров. В качестве подготов­
ки добавим в папку Infrastructure файл класса по имени UrlExtensions. cs с
определением расширяющего метода. приведенного в листинге 9.14.
272
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Листинг
9.14.
Содержимое файла
UrlExtensions. cs
из папки
Infrastructure
using Microsoft.AspNetCore.Http;
namespace SportsStore.Infrastructure
puЫic
static class UrlExtensions {
puЫic
static string PathAndQuery(this HttpRequest request) =>
request.QueryString.HasValue
? $"{request.Path){request.QueryString)"
: request.Path.ToString();
Расширяющий метод
зуемый в
URL,
работает с классом
PathAndQuery ()
ASP.NET Core для
HttpRequest,
исполь­
описания НТГР-запроса. Расширяющий метод генерирует
по которому браузер будет возвращаться после обновления корзины, принимая
во внимание строку запроса, если она есть. В листинге
9.15
в файл импортирования
представлений добавляется пространство имен, которое содержит расширяющий ме­
тод, так что его можно применять в частичном представлении.
Листинг
9.15.
Добавление пространства имен в файле_Viewimports.
из папки
cshtml
Views
@using SportsStore.Models
@using SportsStore.Models.ViewModels
@using SportsStore.Infrastructure
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper SportsStore.Infrastructure.*, SportsStore
В листинге
9.16
показано частичное представление с описанием каждого товара,
которое обновлено для включения кнопки
Add
Листинг
Productswamary. cshtml
9.16.
Добавление кнопки в файле
из папки
То
Cart
(Добавить в корзину).
Views/Shared
@model Product
<div class="card card-outline-primary m-l р-1">
<div class="bg-faded р-1">
<h4>
@Model.Name
<span class="badge badge-pill badge-primary" style="float:right">
<small>@Model.Price.ToString("c")</small>
</span>
</h4>
</div>
id="@Model.ProductID" asp-action="AddToCart"
asp-controller="Cart" method="post">
<input type="hidden" asp-for="ProductID" />
<input type="hidden" name="returnUrl"
value="@ViewContext.HttpContext.Request.PathAndQu ery()" />
<span class="card-text р-1">
<forш
@Мodel.Description
<button
type="suЬmit"
class="Ьtn Ьtn-success Ьtn-sm
pull-right" style="float:right">
Глава 9. SportsStore: навигация
273
Add То Cart
</button>
</span>
</form>
</div>
forrn, содержащий скрытые элементы input, которые ус­
ProductID из модели представления и URL, куда браузер
после обновления корзины. Элемент forrn и один из элементов
Мы добавили элемент
танавливают значение
должен возвращаться
inpu t
конфигурируются с использованием встроенных вспомогательных функций де­
скрипторов, что является удобным способом генерирования форм, которые содержат
значения модели и нацелены на контроллеры и действия в приложении, как описано
в главе
24.
Во втором элементе
URL
для установки
input
применяется расширяющий метод, созданный
возврата. Кроме того, добавлен элемент
button,
который будет
отправлять форму приложению.
На заметку! Обратите внимание, что атрибут
rnethod
элемента
forrn
установлен в
post,
что инструктирует браузер относительно отправки данных формы с использованием
НТТР-метода POST. Вы можете это изменить и заставить форму применять НТТР-метод
GET, но должны соблюдать осторожность. Спецификация НТТР требует, чтобы запросы
GET были идемпотентными, т.е. они не должны приводить к изменениям, а добавление
товара в корзину определенно считается изменением. Более подробно такая тема будет
обсуждаться в главе 16, включая объяснение того, что может произойти, если проигнори­
ровать требование идемпотентности запросов
GET.
Включение поддержки сеансов
Мы собираемся сохранять детали корзины пользователя с использованием состоя­
ния сеанса, что представляет собой данные, которые хранятся на сервере и ассоции­
руются с последовательностью запросов, сделанных пользователем. Инфраструктура
ASP.NET
предлагает целый ряд разных способов хранения состояния сеанса, в том
числе хранение его в памяти, что мы и будем применять. Преимуществом такого под­
хода является простота, но данные сеанса будут утеряны, когда приложение останав­
ливается или перезапускается. Включение поддержки сеансов требует добавления в
класс
Startup
Листинг
9.17.
служб и промежуточного программного обеспечения (листинг
Включение поддержки сеансов в файле
из папки
using
using
using
using
using
using
using
using
using
using
using
Startup. cs
SportsStore
System;
Systern.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
SportsStore.Models;
Microsoft.Extensions.Configuration;
Microsoft.EntityFrameworkCore;
9.17).
274
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
namespace SportsStore {
class Startup {
Startup(IConfiguration configuration) =>
Configuration = configuration;
puЫic
puЬlic
IConfiguration Configuration { get; }
void ConfigureServices(IServiceCollection services)
services.AddDbContext<ApplicationDbContext>(optio ns =>
options.UseSqlServer(
Configuration["Data:SportStoreProducts:Connectio nString"]));
services.AddTransient<IProductRepository, EFProductRepository>();
services.AddMvc{);
puЫic
puЬlic
services.AddМemoryCache();
services.AddSession();
void Configure(IApplicationBuilder
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseSession();
app.UseMvc(routes => {
puЬlic
11 ... для
IHostingEnvironment env) {
арр,
краткости конфигурация маршрутизации не показана
...
) ) ;
SeedData.EnsurePopulated(app);
AddMemoryCache ( ) настраивает хранилище данных в памяти.
AddSession () регистрирует службы, используемые для доступа к данным се­
а метод UseSession () позволяет системе сеансов автоматически ассоцииро­
Вызов метода
Метод
анса,
вать запросы с сеансами, когда они поступают от клиента.
Реализация контроллера для корзины
Для обработки щелчков на кнопках
Добавим в папку
Controllers
лением, представленным в листинге
Листинг
using
using
using
using
using
9.18.
Add
То
Cart
понадобится создать контроллер.
файл класса по имени
Содержимое файла
CartController. cs
CartController. cs
из папки
System.Linq;
Microsoft.AspNetCore.Http;
Microsoft.AspNetCore.Mvc;
SportsStore.Infrastructure;
SportsStore.Models;
namespace SportsStore.Controllers
class CartController : Controller
private IProductRepository repository;
puЬlic
CartController(IProductRepository repo) {
repository = repo;
puЬlic
с опреде­
9.18.
Controllers
Глава 9. SportsStore: навигация
275
RedirectToActionResult AddToCart(int productld, string returnUrl) {
Product product = repository.Products
. FirstOrDefault (р => р. ProductID == productld);
i f (product ! = null) {
Cart cart = GetCart();
cart.Addltem(product, 1);
SaveCart(cart);
puЫic
return RedirectToAction("Index", new { returnUrl )) ;
RedirectToActionResult RemoveFromCart(int productld,
string returnUrl) {
Product product = repository.Products
.FirstOrDefault(p => p.ProductID == productld);
puЫic
i f (product ! = null) {
Cart cart = GetCart();
cart.RemoveLine(product);
SaveCart(cart);
return RedirectToAction("Index", new { returnUrl }) ;
private Cart GetCart() {
Cart cart = HttpContext.Session.GetJson<Cart>("Cart") ?? new Cart();
return cart;
private void SaveCart(Cart cart) {
HttpContext.Session.SetJson("Cart", cart);
CartController необходимо сделать несколько
Cart применяется средство состо­
взаимодействует метод GetCart () . Промежуточное
Относительно масса контроллера
замечаний. Для сохранения и извлечения объектов
яния сеанса
ASP.NET,
с которым и
программное обеспечение, зарегистрированное в предыдущем разделе, использу­
ет сооkiе-наборы или переписывание
URL,
чтобы ассоциировать вместе множество
запросов от определенного пользователя с целью формирования отдельного сеанса
просмотра. Связанным средством является состояние сеанса, которое ассоциирует
данные с сеансом. Ситуация идеально подходит для масса
Cart:
мы хотим, чтобы
каждый пользователь имел собственную корзину, и она сохранялась между запроса­
ми. Данные, связанные с сеансом, удаляются по истечении времени существования
сеанса (обычно из-за того, что пользователь не отправляет запрос какое-то время), т.е.
Cart не придется.
RemoveFromCart () применялись имена пара­
метров, которые соответствуют именам элементов input в НТМL-формах, созданных
в представлении ProductSummary. cshtml. Это позволяет инфраструктуре MVC ассо­
циировать входящие переменные НТГР-запроса POST формы с параметрами и означа­
управлять хранилищем или жизненным цимом объектов
В методах действий
AddToCart ()
и
ет, что делать что-то самостоятельно для обработки формы не нужно. Такой процесс
называется привязкой модели и с его помощью можно значительно упрощать классы
контроллеров, как будет объясняться в главе
26.
276
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Определение расширяющих методов состояния сеанса
Средство состояния сеанса в
и
ASP.NET Core
хранит только значения
Поскольку мы хотим сохранять объект
byte [].
ширяющие методы для интерфейса
ISession,
Cart,
int, st.ring
необходимо определить рас­
которые предоставят доступ к дан­
ным состояния сеанса с целью сериализации объектов
ратного преобразования. Добавим в папку
SessionExtensions. cs
листинге
Листинг
Cart в формат JSON и их об­
Infrastructure файл класса по имени
с определениями расширяющих методов, показанными в
9.19.
9.19.
Содержимое файла
из папки
SessionExtensions. св
Infrastructure
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
namespace SportsStore.Infrastructure
puЫic
static class SessionExtensions
puЫic
static void SetJson(this ISession session, string key,
obj ect val ue) {
session.SetString(key, JsonConvert.SerializeObject(value));
static Т GetJson<T> (this ISession session, string key) {
var sessionData = session.GetString(key);
return sessionData == null
? default (Т) : JsonConvert. DeserializeObject<T> (sessionData);
puЫic
При сериализации объектов в формат
ма обозначений для объектов
Json.NET,
JavaScript)
JSON (JavaScript Object Notation -
систе­
расширяющие методы полагаются на пакет
который вы снова встретите в главе
20.
Пакет
Json.NET
не требуется до­
бавлять к проекту, потому что он уже используется "за кулисами" инфраструктурой
МVС для поддержки средства заголовков
су
JSON.
https: / /www.newtonsoft.com/j son
с пакетом
которое описано в главе
21.
(По адре­
доступна информация о том. как работать
Json.NET напрямую.)
Расширяющие методы облегчают сохранение и извлечение объектов
бавления объекта
Cart
Cart.
Для до­
к состоянию сеанса в контроллере применяется следующий
вызов:
HttpContext.Session.SetJson("Cart", cart);
Свойство
HttpContext
определено в базовом классе
обычно унаследованы контроллеры, и возвращает объект
Controller,
HttpContext.
от которого
Этот объект
предоставляет данные контекста о запросе, который был получен. и ответе, находя­
щемся в процессе подготовки. Свойство
реализующий интерфейс
где мы определили метод
HttpContext. Session возвращает объект.
ISession. Данный интерфейс является именно тем типом,
SetJson (),принимающий аргументы. в которых указыва­
ются ключ и объект, подлежащий добавлению в состояние сеанса.
Глава 9. SportsStore: навигация
277
Расширяющий метод сериализирует объект и добавляет его в состояние сеанса.
используя функциональность, которая лежит в основе интерфейса
Для извлечения объекта
Cart
ISession.
применяется другой расширяющий метод, которому
передается тот же самый ключ:ы
Cart cart
=
HttpContext.Session.GetJson<Cart>("Cart");
Параметр типа позволяет указать тип объекта, который ожидается извлечь: этот
тип используется в процессе десериализации.
Отображение содержимого корзины
Финальное замечание о контроллере
и
RemoveFromCart ()
вызывают метод
Cart касается того, что методы AddToCart ()
RedirectToAction (). Результатом будет от­
правка клиентскому браузеру НТГР-инструкции перенаправления, которая заставит
URL. В данном случае браузер запросит URL, который вы­
Index () контроллера Cart.
Мы планируем реализовать метод Index () и применять его для отображения со­
держимого объекта Cart. Взглянув на рис. 9.7 еще раз, можно заметить, что это рабо­
браузер запросить новый
зывает метод действия
чий поток, инициируемый по щелчку пользователя на кнопке добавления в корзину.
Представлению, которое будет отображать содержимое корзины. необходимо пе­
Cart и URL для отображения в случае, если
Continue shopping (Продолжить покупку). Для такой
цели мы создадим простой класс модели представления. Добавим в папку Models/
ViewModels проекта SportsStore файл класса по имени CartindexViewModel. cs
с содержимым, приведенным в листинге 9.20.
редать две порции информации: объект
пользователь щелкнет на кнопке
Листинг
9.20.
Содержимое файла CartindexViewМodel.
cs
из папки Models/ViewМodels
using SportsStore.Models;
namespace SportsStore.Models.ViewModels
class CartindexViewModel
Cart Cart { get; set; }
puЫic string ReturnUrl { get; set;
puЬlic
puЬlic
Имея модель представления. можно реализовать метод действия
CartController
Листинг
9.21.
(листинг
Реализация метода действия
в файле
using
using
using
using
using
using
9.21).
CartController. cs
System.Linq;
Microsoft.AspNetCore.Http;
Microsoft.AspNetCore.Mvc;
SportsStore.Infrastructure;
SportsStore.Models;
SportsStore.Models.ViewМodels;
Index ()
из папки
Controllers
Index ()
в классе
278
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
namespace SportsStore.Controllers {
class CartController : Controller
private IProductRepository repository;
puЫic
CartController(IProductRepository repo) {
repository = repo;
puЬlic
ViewResult Index(string returnUrl)
return View(new CartindexViewModel {
Cart = GetCart () ,
ReturnUrl = returnUrl
puЬlic
}) ;
11 ... для
краткости другие методы не показаны ...
Метод действия
Index () извлекает объект Cart из состояния сеанса и использует
CartindexViewModel, который затем передается методу
его при создании объекта
View ()
для применения в качестве модели представления.
Последний шаг, относящийся к отображению содержимого корзины, предусмат­
ривает создание представления, которое будет визуализировать действие
Создадим папку
Index. csh tml
Листинг
9.22.
Views/Cart
Index.
и поместим в нее файл представления Razoг по имени
с разметкой, приведенной в листинге
Содержимое файла
Index. cshtml
9.22.
из папки
Views/Cart
@model CartindexViewModel
<h2>Your cart</h2>
<tаЫе
class="taЫe
taЫe-bordered taЫe-striped">
<thead>
<tr>
<th>Quantity</th>
<th>Item</th>
<th class="text-right">Price</th>
<th class="text-right">Subtotal</th>
</tr>
</thead>
<tbody>
@foreach (var line in Model.Cart.Lines) {
<tr>
<td class="text-center">@line.Quantity</td>
<td class="text-left">@line.Product.Name</td>
<td class="text-right">@line.Product.Price.ToString("c")</td>
<td class="text-right">
@( {line.Quantity * line.Product.Price) .ToString("c"))
</td>
</tr>
</tbody>
Глава 9. SportsStore : навигация
279
<tfoot>
<tr>
<td colspan="З" class=" text -right">Total:< / td>
<td class = "text-right">
@Model. Cart. ComputeTotal Val ue () . ToString ( "с")
</td>
</ tr>
</tfoot>
</tа Ые >
<div class="text-center">
<а class="btn Ьtn-primary" href="@Model.ReturnUr l " >Continue shopping< /a>
< / div>
Представление проходит по элементам в корзине и добавляет в НТМL-таблицу
строку для каждого элемента вместе со стоимостью и итоговой суммой по корзине .
Классы , назначенные элементам, соответствуют стилям
Bootstrap
для таблиц и вы­
равнивания текста.
В результате становится доступной базовая функциональность корзины для поку­
пок. Во-первых, товары выводятся вместе с кнопками
ну). как показано на рис.
То
Cart
(Добавить в корзи­
с;1;.1.•
Home
Thinking
Chess
lmprove brain efficiency Ьу 75%
Soccer
Unsteady Chair
Watersports
Add
9.8.
Сар
Secretly give your opponent
Рис.
9.8.
Кнопки
Add
Во-вторых, щелчок пользователя на кнопке
То
Add
а
iфjfiMI
disadvantage
Cart
То
Cart приводит
к добавлению со­
ответствующего товара в его корзину и отображению сводной информации по корзи­
не (рис.
9.9).
Щелчок на кнопке
Continue shopping
(Продолжить покупку) возвращает
пользователя на страницу товара. из которой произошел переход .
280
Часть 1. Введение в инфраструктуру ASP. NET Саге MVC 2
Your cart
Home
Chess
Quantity
Soccer
Watersports
ltem
Price
Subtotal
Unsteady Chair
529.95
$29.95
Thi11king Сар
$16.00
$16.00
Human Chess Board
575.00
$75.00
$275.00
$275.00
Total:
$395.95
Kayak
Continue shopping
Рис.
9.9.
Отображение содержимого корзины для покупок
Резюме
В главе мы начали расширять пользовательские части приложения
SportsStore. Мы
предоставили средства. с помощью которых пользователь может переходить по кате­
гориям, и создали базовые строительные блоки, позволяющие добавлять элементы в
корзину для покупок. В следующей главе разработка приложения будет продолжена.
ГЛАВА
10
SportsStore:
завершение построения
корзины для покупок
в настоящей главе мы продолжим строить пример приложения SportsStoгe. В пре­
дьщущей главе мы добавили базовую поддержку корзины для покупок, а теперь
собираемся усовершенствовать и завершить создание этой функциональности.
Усовершенствование модели
корзины с помощью службы
В предыдущей главе был определен класс модели
Cart
и продемонстрирована воз­
можность его сохранения с использованием средства состояния сеанса, что давало
возможность пользователю формировать набор товаров для покупки. Обязанность по
управлению постоянством класса
Cart
Cart.
Cart.
возлагалась на контроллер
явно определялись методы для получения и сохранения объектов
в котором
Проблема такого подхода в том. что нам пришлось дублировать код для получения
и сохранения объектов
Cart
во всех компонентах. которые его применяли. В теку­
щем разделе мы воспользуемся средством служб, которое находится в самой основе
инфраструктуры
ASP.NET Саге,
чтобы упростить способ управления объектами
освобождая индивидуальные компоненты. такие как контроллер
Cart.
Cart,
от необходи­
мости напрямую иметь дело с деталями.
Службы чаще всего применяются для сокрытия деталей реализации интерфейсов
от компонентов, которые от них зависят. Вы видели пример, когда создавалась служ­
ба для интерфейса
IProductRepository.
которая позволила гладко заменить фик­
тивный класс хранилища реальным хранилищем
Entity
Fгamewoгk Саге. Но службы
могут использоваться для решения множества других задач. а также применяться для
придания и изменения формы приложения, даже если работа производится с конк­
ретными классами наподобие
Cart.
Создание класса корзины, осведомленного о хранилище
Первым шагом по приведению в порядок способа использования класса
Cart
будет создание подкласса, который осведомлен о том. как сохранять самого себя с
применением состояния сеанса. Добавим в папку
SessioпCart.
cs
Mode 1 s
файл класса по имени
и поместим в него определение. показанное в листинге
10.1.
282
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
Листинг
using
using
using
using
using
10.1.
Содержимое файла
SessionCart. cs
из папки
Models
System;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
Newtonsoft.Json;
SportsStore.Infrastructure;
namespace SportsStore.Models {
puЫic class SessionCart: Cart {
puЫic static Cart GetCart ( IServiceProvider services) {
ISession session = services.GetRequiredService<IHttpContextAccessor>()?
.HttpContext.Session;
SessionCart cart = session?.GetJson<SessionCart>("Cart")
?? new SessionCart();
cart.Session = session;
return cart;
[Jsonignore]
ISession Session { get; set; )
puЫic override void Additem(Product product, int quantity) {
base.Additem(product, quantity);
Session.SetJson("Cart", this);
puЫic
override void RemoveLine(Product product) {
base.RemoveLine(product);
Session.SetJson("Cart", this);
puЬlic
puЫic
override void Clear ()
base.Clear();
Session.Remove("Cart");
Класс
тоды
SessionCart является
Additem (), RemoveLine ()
производным от класса
и
Clear
Cart
и переопределяет ме­
(),так что они вызывают базовые реали­
зации и затем сохраняют обновленное состояние в сеансе. используя расширяющие
методы интерфейса
тод
GetCart () -
ISession,
которые были определены в главе
это фабрика для создания объектов
объектом реализации
ISession,
9.
SessionCart
Статический ме­
и снабжения их
чтобы они могли себя сохранять.
Получение объекта реализации
лучить экземпляр службы
к объекту
HttpContext.
а
ISession несколько затруднено. Мы должны по­
IHttpContextAccessor. который предоставит доступ
тот в свою очередь - к объекту реализации ISession.
Такой непрямой подход требуется из-за того, что сеанс не предоставляется как обыч­
ная служба.
Регистрация службы
Следующий шаг заключается в создании службы для класса
чтобы удовлетворять запросы для объектов
Cart
Cart. Цель в том.
SessionCart.
выдачей объектов
которые будут сохранять себя самостоятельно. Создание службы иллюстрируется в
листинге
10.2.
Глава 1О. SportsStore: завершение построения корзины для покупок
Листинг
10.2.
283
Создание службы корзины в файле Startup. cs из папки SportsStore
puЬlic
void ConfigureServices(IServiceCollection services)
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration["Data:SportStoreProducts:ConnectionString"]));
services.AddTransient<IProductRepository, EFProductRepository>();
services.AddScoped<Cart>(sp => SessionCart.GetCart(sp));
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddMvc();
services.AddMemoryCache();
services.AddSession();
Метод
AddScoped () указывает, что для удовлетворения связанных запросов к
Cart должен применяться один и тот же объект. Способ связывания
земплярам
эк­
за­
просов может быть сконфигурирован, но по умолчанию это значит, что в ответ на
любой запрос экземпляра
со стороны компонентов, которые обрабатывают тот
Cart
же самый НТГР-запрос. будет выдаваться один и тот же объект.
Вместо предоставления методу
AddScoped ( )
отображения между типами, как
делалось для хранилища, указывается лямбда-выражение, которое будет выполнять­
ся для удовлетворения запросов к
Cart.
Лямбда-выражение получает коллекцию
служб. которые были зарегистрированы. и передает ее методу
SessionCart.
В результате запросы для службы
создания объектов
SessionCart,
Cart
GetCart ()
класса
будут обрабатываться путем
которые сериализируют сами себя как данные се­
анса, когда они модифицируются.
Мы также добавили службу с использованием метода
AddSingleton ().
который
указывает, что всегда должен применяться один и тот же объект. Созданная служба
сообщает инфраструктуре
MVC
IHttpContextAccessor,
необходимо использовать класс
о том, что когда требуются реализации интерфейса
HttpContextAccessor.
SessionCart можно получать доступ
10.1.
Данная служба обязательна. поэтому в классе
к текущему сеансу. как делалось в листинге
Упрощение контроллера
Cart
Преимущество создания службы такого вида связано с тем. что она позволит уп­
ростить контроллеры. в которых применяются объекты
ден переделанный класс
CartController,
Cart.
В листинге
Листинг 10.З. Использование службы Cart в файле CartController. cs
из папки
using
using
using
using
Controllers
System.Linq;
Microsoft.AspNetCore.Mvc;
SportsStore.Models;
SportsStore.Models.ViewModels;
namespace SportsStore.Controllers {
class CartController : Controller
private IProductRepository repository;
private Cart cart;
puЫic
10.3
где задействована новая служба.
приве­
284
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
puЫic
CartController(IProductRepository repo, Cart cartService) {
repository = repo;
cart = cartService;
puЫic
ViewResult Index(string returnUrl)
return View(new CartindexViewModel
Cart = cart,
ReturnUrl = returnUrl
) ) ;
puЫic
RedirectToActionResult AddToCart(int productid, string returnUrl)
{
Product product = repository.Products
.FirstOrDefault(p => p.ProductID == productld);
if (product != null) {
cart.Additem(product, 1);
return RedirectToAction("Index", new { returnUrl )) ;
puЬlic
RedirectToActionResult RemoveFromCart(int productid,
string returnUrl) {
Product product = repository.Products
.FirstOrDefault(p => p.ProductID == productid);
if (product != null) {
cart.RemoveLine(product);
return RedirectToAction("Index", new { returnUrl }) ;
Класс
CartController
указывает на то. что он нуждается в объекте
Cart.
за счет
объявления аргумента конструктора. Это позволяет удалить методы. которые читают
и записывают данные в сеанс, а также код. требующийся для записи обновлений.
Результатом оказывается контроллер. который не только проще. но и более сосредо­
точен на своей роли в приложении, не беспокоясь о том, как объекты
Cart
создаются
либо хранятся. И поскольку службы доступны по всему приложению, любой компо­
нент может получать корзину пользователя с применением одного и того же приема.
Завершение функциональности корзины
После ввода службы
Cart
наступило время завершить построение функциональ­
ности корзины. добавив два новых средства. Первое средство позволит пользователю
удалять элемент из корзины. Второе средство даст возможность отображать итоговую
информацию по корзине в верхней части страницы.
Удаление элементов из корзины
Мы уже определили и протестировали метод действия
троллере.
RemoveFromCart ()
в кон­
а потому для предоставления пользователям возможности удаления эле­
ментов достаточно лишь открыть доступ к данному методу в представлении. доба-
Глава 1О. SportsStore: завершение построения корзины для покупок
вив кнопки
Remove
(Удалить) ко всем строкам в итоговой информации по корзине.
Изменения, которые понадобится внести в файл
заны в листинге
Листинг
285
Views/Cart/Index.cshtml,
пока­
10.4.
10.4. Добавление кнопок Remove в файле Index.cshtml из папки Views/Cart
@model CartindexViewModel
<h2>Your cart</h2>
<tаЫе class="taЫe taЫe-bordered taЫe-striped">
<thead>
<tr>
<th>Quantity</th>
<th>Item</th>
<th class="text-right">Price</th>
<th class="text-right">SuЬtotal</th>
</tr>
</thead>
<tbody>
@foreach (var line in Model.Cart.Lines) {
<tr>
<td class="text-center">@line.Quantity</td>
<td class="text-left">@line.Product.Name</td>
<td class="text-right">@line.Product.Price.ToString(" c")</td>
<td class="text-right">
@( (line.Quantity * line.Product.Price) .ToString("c"))
</td>
<td>
<forш asp-action="ReшoveFromCart" method="post">
<input type="hidden" name="ProductID"
value="@line.Product.ProductID" />
<input type="hidden" name="returnUrl"
value="@Мodel.ReturnUrl" />
<Ьutton type="suЬmit"
class="Ьtn Ьtn-sm Ьtn-danger">
Remove
</button>
</form>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="З" class="text-right">Total:</td>
<td class="text-right">
@Model.Cart.ComputeTotalValue() .ToString("c")
</td>
</tr>
</tf oot>
</tаЫе>
<div class="text-center">
<а class="btn btn-primary" href="@Model.ReturnUrl">Continue shopping</a>
</div>
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
286
Мы добавили к каждой строке таблицы новый столбец, содержащий элемент
со скрытыми элементами
и
URL возврата.
input.
form
которые указывают товар. подлежащий удалению.
а также кнопку для отправки формы.
Чтобы увидеть кнопки
Remove
в работе, понадобится запустить приложение и до­
бавить несколько элементов в корзину для покупок . Поскольку корзина уже обладает
функциональностью удаления элементов, ее можно протестировать. щелкнув на од­
ной из новых кнопок (рис.
Chess
Quantlty
Soccer
Watersports
10.1).
Subtotal
Lifejacket
$48.95
$48.95
1111
Soccer Ball
S19.50
S19.50
Total:
$68.45
"
;gщщч.щ;м
1
~rt
Price
ltem
l
ltem
Price
Subto tal
Total:
S48.95
НфМ.!.Щфi
'-------~
Рис.
10.1.
Удаление элемента из корзины для покупок
Добавление виджета с итоговой информацией по корзине
Мы имеем функционирующую корзину, но еще должны определить способ ее
встраивания в пользовательский интерфейс. Пользователи могут выяснять. что на­
ходится в их корзинах, только за счет просмотра экрана со сводкой по корзине. Но
попасть на этот экран можно только путем добавления нового элемента в корзину.
Для решения упомянутой задачи мы создадим виджет (графический элемент) с
итоговой информацией по содержимому корзины. щелчок на котором будет приво­
дить к отображению товаров. находящихся в корзине . и сделаем е го доступным в
любом месте приложения. Он будет реализован почти так же, как виджет для нави­
гации
-
в виде компонента представления, вывод которого может включаться в раз­
деляемую компоновку Razoг.
Добавление пакета
Font Awesome
В качестве части итоговой информации по корзине мы будем отображать кнопку.
которая позволит пользователю перейти к оплате. Вместо отображения каких -либо
слов на кнопке мы хотим использовать символ корзины. При отсутствии художест­
венных навыков можно применить пакет с открытым кодом
Font Awesome, предлагаю­
щий великолепный набор значков, которые допускается интегрировать в приложения
как шрифты, где каждый символ шрифта представляет собой отдельное изображение.
Получить дополнительные сведения о пакете
держащиеся в нем значки. можно по адресу
Font Awesome.
а также просмотреть со­
ht tps : / / fo nt awesome. сот / .
Глава 1О. SportsStore: завершение построения корзины для nокуnок
Выберем проект
SportsStore
элементы) в верхней части окна
Добавим пакет
Font Awesome
и щелкнем на кнопке
Solution Explorer,
в раздел
(Показать все
Show All ltems
чтобы отобразить файл
dependencies
(листинг
287
bower. j son.
10.5).
Листинг 10.5. Добавление пакета Foпt Awesome в файле bower. j son
из папки
"name":
asp.net 11
11
SportsStore
,
"private": true,
"dependencies": {
"bootstrap": "4.0.0-alpha.6",
"fontawesome": "4. 7. 0"
После сохранения файла
мента
Bower
bower. j son
загрузит и установит пакет
Visual Studio с помощью инстру­
Font Awesome в папку wwwroot/liЬ/
среда
fontawesome.
Создание класса компонента представления и представления
Добавим в папку
Components файл класса по имени CartSummaryViewComponent. cs
и определим в нем компонент представления, показанный в листинге
Листинг
10.6.
10.6. Содержимое файла CartSummaryViewComponen t. cs
из папки Componen ts
using Microsoft.AspNetCore.Mvc ;
using SportsStore.Models;
namespace SportsStore.Components
class CartSummaryViewComponent : ViewComponent
private Cart cart;
puЫic
puЫic
cart
CartSummaryViewComponen t(Cart cartService) (
cartService;
=
IViewComponentResult Invoke() (
return View(cart);
puЫic
Компонент представления
CartSummaryViewComponen t
способен задействовать в
своих интересах службу, созданную ранее в главе для получения объекта
Cart,
при­
нимая ее как аргумент конструктора. Результатом оказывается простой компонент
представления, который передает объект
Cart
методу
View
(),чтобы сгенерировать
фрагмент НТМL-разметки для включения в компоновку. Чтобы создать компонов­
Views/Shared/Components/ CartSummary, добавим в нее файл
Razoг по имени Defaul t. cshtml и поместим в него разметку, приве­
ку, создадим папку
представления
денную в листинге
10.7.
288
Часть 1. Введение в инфраструктуру ASP.NET Соге MVC 2
Листинг 1О.7. Содержимое файла
Defaul t. csh tml из папки
Views/Shared/Components/CartSummary
@model Cart
<div class="">
@if (Model.Lines.Count() > 0)
<small class="navbar-text">
<b>Your cart:</b>
@Model.Lines.Sum(x => x.Quantity) item(s)
@Model.ComputeTotalValue() .ToString("c")
</small>
<а
class="btn btn-sm btn-secondary navbar-btn"
asp-controller="Cart" asp-action="Index"
sp-route-returnurl="@ViewContext.HttpContext.Reques t.PathAndQuery()">
<i class="fa fa-shopping-cart"></i>
</а>
</div>
Представление отображает кнопку со значком корзины
Font Awesome
и когда в
корзине присутствуют товары. предоставляет снимок. который сообщает количес­
тво элементов и общую сумму. Теперь, имея компонент представления и представ­
ление, можно модифицировать разделяемую компоновку, чтобы итоговая инфор­
мация по корзине включалась в ответы, генерируемые контроллерами приложения
(листинг
Листинг
10.8).
10.8. Добавление итоговой информации по корзине
в файле _Layout. cshtml из папки Views/Shared
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet"
asp-href-include="/liЬ/bootstrap/dist/**/*.min.css"
asp-href-exclude="**/*-reboot*,**/*-grid*" />
<link rel="stylesheet" asp-href-include="/lib/fontawesome/css/*.css" />
<title>SportsStore</title>
</head>
<body>
<div class="navbar navbar-inverse bg-inverse" role="navigation">
<div class="row">
<а class="col navbar-brand" href="#">SPORTS STORE</a>
<div class="col-4 text-right">
@awai t Component. InvokeAsync ( "CartSuшmary")
</div>
</div>
</div>
<div class="row m-1 р-1">
<div id="categories" class="col-3">
@awai t Component. InvokeAsync ( "NavigationMenu")
</div>
Глава 1О. SportsStore: завершение построения корзины для покупок
289
<div class="col -9" >
@RenderBody ()
</div>
</div>
</ body>
</ html>
Запустив приложение, можно увидеть итоговую информацию по корзине. Когда
корзина пуста, отображается только кнопка
Continue shopping
(Продолжить покупки).
По мере добавления элементов в корзину выводится их количество и общая стоимость
(рис.
10.2).
Благодаря такому дополнению пользователь знает, какие товары находят­
ся в корзине, и получает очевидный путь для перехода к оплате покупок.
Рис.
10.2.
Отображение итоговой информации по корзине
Отправка заказов
Итак, мы добрались до финального пользовательского средства приложения
SportsStore:
возможности перехода к оплате и оформлению заказа. В последующих
разделах мы расширим модель предметной области. чтобы обеспечить поддержку по­
лучения от пользователя подробной информации о доставке. и добавим в приложение
обработку этой информации .
Создание класса модели
Добавим в папку
Mo dels
Order . cs и приведем его содер ­
Order будет использоваться для пред­
файл класса по имени
жимое в соответствие с листингом
10.9.
Класс
ставления информации о доставке пользователю .
Листинг
10.9. Содержимое файла Order. cs из папки Models
using System.Collections.Generi c;
using System.Compo nentModel.Dat aAnn otations;
using Microsoft.AspNetCore.Mvc .ModelBinding;
namespace SportsStore.Models
puЬlic
c la ss Order {
290
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
[BindNever]
int OrderID { get; set; )
[BindNever]
puЬlic ICollection<CartLine> Lines { get; set; )
[Required(ErrorMessage = "Please enter а name")]
puЫic
//Введите имя
string Name { get; set; )
[Required(ErrorMessage = "Please enter the first address line")]
puЫic
//Введите первую строку адреса
puЫic
string Linel
get; set; )
puЬlic string Line2
get; set; )
puЬlic string LineЗ
get; set; )
[Required(ErrorMessage = "Please enter
а
city name")]
//Введите название города
puЬlic
string City { get; set; )
[Required(ErrorMessage = "Please enter
а
state name")]
//Введите название штата
puЫic
string State { get; set; )
string Zip { get; set; )
[Required(ErrorMessage = "Please enter а country name")]
// Введите название страны
puЫic string Country { get; set; )
puЫic bool GiftWrap { get; set; )
puЫic
Здесь применяются атрибуты проверки достоверности из пространства имен
System. ComponentModel. DataAnnotations,
стоверности подробно рассматривается в главе
Кроме того, в классе
Order
как мы делали в главе
2.
Проверка до­
27.
используется атрибут
BindNever,
который предо­
твращает предоставление пользователем значений для снабженных этим атрибутом
свойств в НТТР-запросе. Такая возможность системы привязки моделей, описанная
в главе
26,
останавливает применение инфраструктурой
MVC
значений из НТТР­
запроса для заполнения конфиденциальных или важных свойств модели.
Добавление реализации процесса оплаты
Наша цель заключается в том, чтобы обеспечить пользователям возможность
ввода информации о доставке и отправки заказа. Для начала потребуется добавить
к представлению итоговой информации по корзине кнопку
лате). Изменения, которые необходимо внести в файл
показаны в листинге
Листинг
10.10.
Checkout
(Перейти к оп­
Views/Cart/Index.cshtml,
10.10.
Добавление кнопки
из папки
Checkout
в файле Index. cshtml
Views/Cart
<div class="text-center">
<а class="btn btn-primary" href="@Model.ReturnUrl">Continue shopping</a>
<а class="Ьtn Ьtn-primary" asp-action="Checkout"
asp-controller="Order">
Checkout
</а>
</div>
Глава 1О. SportsStore: завершение построения корзины для покупок
291
Изменения обеспечивают генерацию ссылки. стилизованной в виде кнопки, щел­
чок на которой приводит к вызову метода действия
создаваемого в следующем разделе. На рис.
10.3
Checkout ()
контроллера
Order,
показано, как выглядит кнопка
Checkout.
Your cart
Ноте
Chess
Quantity
Price
Subtotal
$275.00
$275.00
Total:
$275.00
Jtem
Soccer
Kayak
Watersports
i@,\j.il
11;1
Рис.
10.3.
Кнопка
Теперь понадобится определить контроллер
файл класса по имени
ге
OrderContro l l er. cs
Checkout
Order.
Добавим в папку
Controllers
с определением, приведенным в листин­
10.11.
Листинг
10.11.
Содержимое файла
OrderController.cs
из папки
Controllers
us ing Mi c rosoft .As pNe t Co r e .Mvc ;
using SportsSto re.Models;
namespace Sport s St ore.Controlle r s
puЫic
class Or d erController : Controller
puЫic
ViewRe s ul t Checkout ( ) => Vi e w(n e w Order ());
Checkout ( ) возвращает стандартное представление и передает новый объ­
ShippingDetails в качестве модели представления. Чтобы создать представле­
ние. создадим папку Vi ew s /О rde r и поместим в нее файл представления Razor по
имени Chec ko ut. cshtml с разметкой, показанной в листинге 10.12.
Метод
ект
292
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Листинг
10.12.
Содержимое файла
Checkout. cshtml
из папки
Views/Order
@model Order
<h2>Check out now</h2>
<p>Please enter your details, and we'll ship your goods right away!</p>
<form asp-action="Checkout" method="post">
<hЗ>Ship to</hЗ>
<div class="form-group">
<label>Name:</label><input asp-for="Name" class="form-control" />
</div>
<h3>Address</h3>
<div class="form-group">
<label>Line l:</label><input asp-for="Linel" class="form-control" />
</div>
<div class="form-group">
<label>Line 2:</label><input asp-for="Line2" class="form-control" />
</div>
<div class="form-group">
<label>Line 3:</label><input asp-for="LineЗ" class="form-control" />
</div>
<div class="form-group">
<label>City:</label><input asp-for="City" class="form-control" />
</div>
<div class="form-group">
<label>State:</label><input asp-for="State" class="form-control" />
</div>
<div class="form-group">
<label>Zip:</label><input asp-for="Zip" class="form-control" />
</div>
<div class="form-group">
<label>Country:</label><input asp-for="Country" class="form-control" />
</div>
<hЗ>Options</hЗ>
<div class="checkbox">
<label>
<input asp-for="GiftWrap" /> Gift wrap these items
</label>
</div>
<div class="text-center">
<input class="Ьtn Ьtn-primary" type="submit" value="Complete Order" />
</div>
</form>
Для каждого свойства в модели мы создали элементы
вательского ввода. сформатированные с помощью
ментах
inpu t
label и input для пользо­
Bootstrap. Атрибут asp-for в эле­
обрабатывается встроенной вспомогательной функцией дескриптора.
которая генерирует атрибуты
type, id, name
модели, как объясняется в главе
и
value
на основе указанного свойства
24.
Чтобы увидеть результат добавления нового метода действия и представления
(рис.
10.4).
запустим приложение. щелкнем на кнопке со значком корзины в верхней
части страницы и затем на кнопке
ние можно также, запросив
URL
Checkout
вида
(Оплата). Попасть на данное представле­
/Order /Checkout.
Глава 1О. SportsStore: завершение nостроения корзины для nокуnок
Home
Chess
Soccer
293
Check out now
Please enter your details, and we'll ship your goods right away!
Ship to
Name:
Waterspor1s
Address
Line 1:
Line 2:
Line 3:
City:
State:
Zip:
Country:
Options
i:. Gift wrop these items
•111к.mm•.
•м•м•.1м•м•
Рис.
10.4.
Форма для сбора деталей о доставке
Реализация обработки заказов
Мы будем обрабатывать заказы. записывая их в базу данных . Разумеется, боль ­
шинство сайтов электронной коммерции на этом не останавливаются , но мы не ста­
нем заниматься обработкой кредитных карт или других форм оплаты. Чтобы сосре­
доточиться на инфраструктуре
данных.
MVC.
вполне достаточно простого сохранения в базе
294
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Расширение базы данных
Когда основной связующий код. созданный в главе
8,
на месте, добавить в базу
данных новый вид модели легко. Добавим в класс контекста базы данных новое свойс­
тво, как показано в листинге
Листинг
10.13.
Добавление свойства в фaйneApplicationDbContext.cs
из папки
using
using
using
using
10.13.
Models
Microsoft.EntityFrameworkCore;
Microsoft.EntityFrameworkCore.Design;
Microsoft.EntityFrameworkCore.Infrastructure;
Microsoft.Extensions.Dependencyinjection;
namespace SportsStore.Models (
puЬlic class ApplicationDbContext : DbContext (
puЫic ApplicationDbContext(DbContextOptions<ApplicationDbCo ntext> options)
: base ( options) ( )
puЬlic DbSet<Product> Products ( get; set;
puЫic DbSet<Order> Orders { get; set;
)
Такого изменения достаточно для того, чтобы инфраструктура
Core создала миграцию базы данных,
которая позволит объектам
Entity Framework
Order сохраняться в
базе данных. Для создания миграции откроем окно командной строки или
перейдем в папку проекта
SportsStore
(где находится файл
PowerShell,
Startup. cs)
и введем
следующую команду:
dotnet ef migrations add Orders
Эта команда сообщает
Entity Framework Core
о необходимости получить новый
снимок модели данных приложения, выяснить его отличия от предыдущей версии,
хранящейся в базе данных, и сгенерировать новую миграцию под названием
Orders.
Новая миграция будет автоматически применяться при запуске приложения, пото­
му что в классе
SeedData
вызывается метод
Migrate ().
предоставляемый
Entity
Framework Core.
Переустановка базы данных
Если в модель приходится часто вносить изменения, то может возникнуть ситуация, когда миг­
рации и схема базы данных перестают быть синхронизированными. Самое простое, что можно
сделать - удалить базу данных и начать заново. Однако, естественно, такой подход приемлем
только на стадии разработки, потому что все сохраненные данные будут утрачены.
Чтобы удалить базу данных, необходимо ввести приведенную ниже команду, находясь в пап­
SportsStore:
dotnet ef database drop --force
ке проекта
После того,
как база данных удалена,
SportsStore
ввод следующей
команды в папке проекта
приводит к построению базы данных заново и применению созданных ра­
нее миграций:
dotnet ef database update
В результате база данных переустанавливается, точно отражая модель, что дает возмож­
ность возвратиться к разработке приложения.
295
Глава 1О. SportsStore: завершение построения корзины для покупок
Создание хранилища заказов
Чтобы предоставить доступ к объектам
Order,
мы последуем тому же самому шаб­
лону. который использовался для хранилища товаров. Добавим в папку
файл по имени
листинге
Листинг
IOrderReposi tory. cs
Models
новый
и определим в нем интерфейс, показанный в
10.14.
10.14.
Содержимое файла
IOrderReposi tory. cs
из папки
Models
using System.Linq;
namespace SportsStore.Models
puЫic
interface IOrderRepository
IQueryaЫe<Order> Orders { get;
void SaveOrder(Order order);
Для реализации интерфейса хранилища заказов добавим в папку
класса по имени
тинге
EFOrderReposi tory. cs
Models
файл
с определением, представленным в лис­
10.15.
Листинг
10.15.
Содержимое файла
EFOrderReposi tory. cs
из папки
Models
using Microsoft.EntityFrameworkCore;
using System.Linq;
namespace SportsStore.Models {
class EFOrderRepository : IOrderRepository {
private ApplicationDbContext context;
puЫic
EFOrderRepository(ApplicationDbContext ctx)
context = ctx;
puЫic
Orders => context.Orders
.Include(o => o.Lines)
.Then!nclude(l => l.Product);
puЫic IQueryaЬle<Order>
void SaveOrder(Order order)
context.AttachRange(order.Lines.Select(l => l.Product));
i f (order.OrderID == 0) {
context.Orders.Add(order);
puЫic
context.SaveChanges();
EFOrderRepos i tory реализует интерфейс IOrderReposi tory с примене­
Entity Framework Соге, позволяя извлекать набор сохраненных объектов Order
Класс
нием
и создавать либо изменять заказы.
296
Часть 1. Введение в инфраструктуру ASP.NET Соге MVC 2
Особенности хранилища заказов
Реализация хранилища для заказов в листинге
10.15
требует небольшой дополнительной
работы. Инфраструктуру Eпtity Framework Core необходимо проинструктировать о загрузке
связанных данных, если они охватывают несколько таблиц. В листинге
тодов
Include ()
и
Theninclude ()
указано, что когда объект
10.15 с
Order
помощью ме­
читается из базы
данных, то также должна загружаться коллекция, ассоциированная со свойством
наряду с объектами
Product,
Lines,
которые связаны с элементами коллекции:
IQueryaЫe<Order> Orders => context.Orders
.Include(o => o.Lines)
.Theninclude(l => l.Product);
puЫic
Такой подход гарантирует получение всех нужных объектов данных, не выполняя запросы и
не собирая данные напрямую.
Дополнительный шаг требуется и при сохранении объекта
Order
в базе данных. Когда дан­
ные корзины пользователя десериализируются из состояния сеанса, пакет
новые объекты, не известные инфраструктуре Eпtity
ется записать все объекты в базу данных. В случае
инфраструктура Eпtity
Framework Core
JSON создает
Framework Core, которая затем пыта­
объектов Product это означает, что
попытается записать объекты, которые уже были со­
хранены, что приведет к ошибке. Во избежание проблемы мы уведомляем Eпtity
Core
Framework
о том, что объекты существуют и не должны сохраняться в базе данных до тех пор,
пока они не будут модифицированы:
context.AttachRange(order.Lines.Select(l => l.Product));
В результате инфраструктура Eпtity
лизированные объекты
Product,
Framework Core
В листинге 10.16 хранилище заказов
ConfigureServices () класса Startup.
Листинг
10.16.
Order.
регистрируется как служба внутри метода
Регистрация службы хранилища заказов в файле Startup. св
из папки
puЫic
не будет пытаться записывать десериа­
которые ассоциированы с объектом
SportsStore
void ConfigureServices(IServiceCollection services)
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration["Data:SportStoreProducts:ConnectionString")));
services.AddTransient<IProductRepository, EFProductRepository>();
services.AddScoped<Cart>(sp => SessionCart.GetCart(sp));
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IOrderRepository, EFOrderRepository>();
services.AddMvc();
services.AddMemoryCache();
services.AddSession();
Глава 1О. SportsStore: завершение построения корзины для nокуnок
Завершение построения контроллера
Для завершения класса
297
Order
OrderController
понадобится модифицировать конс­
труктор так. чтобы он получал службы, требующиеся ему для обработки заказа, и
добавить новый метод действия. который будет обрабатывать НТТР-запрос
формы. когда пользователь щелкает на кнопке
10.17.
Завершение контроллера в файле
из папки
POST
(Завершить заказ). Оба
10.17.
изменения показаны в листинге
Листинг
Complete order
OrderController. cs
Controllers
using Microsoft.AspNetCore.Mvc;
using SportsStore.Models;
usinq System.Linq;
namespace SportsStore.Controllers
puЬlic class OrderController : Controller
private IOrderRepository repository;
private Cart cart;
puЫic OrderController(IOrderRepository repoService, Cart cartService) {
repository = repoService;
cart = cartService;
ViewResult Checkout() => View(new Order() );
[HttpPost]
puЫic IActionResult Checkout(Order order) {
if (cart.Lines.Count() =О) {
ModelState.AddМodelError("", "Sorry, your cart is empty! ");
puЫic
11
Корзина пуста!
if (ModelState.IsValid) {
order.Lines = cart.Lines.ToArray();
repository.SaveOrder(order);
return RedirectToAction(nameof(Completed));
else {
return View(order);
ViewResult Completed()
cart. Clear () ;
return View () ;
puЫic
Метод действия
зываться для
Checkout ()
запроса POST - в
декорирован атрибутом
HttpPost,
т.е. он будет вы­
этом случае, когда пользователь отправляет форму.
Мы снова полагаемся на систему привязки моделей. так что можно получить объект
Order.
дополнить его данными из объекта
Cart
и сохранить в хранилище.
Инфраструктура МVС контролирует ограничения проверки достоверности, кото­
рые бьmи применены к классу
через свойство
ModelState
Order
посредством атрибутов аннотаций данных, и
сообщает методу действия о любых проблемах. Чтобы вы­
яснить. есть ли проблемы. мы проверяем свойство
ModelState. IsValid.
Мы вызы-
298
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
ваем метод
ModelState.AddModelError ()
для регистрации сообщения об ошибке.
если в корзине нет элементов. Вскоре мы объясним. как отображать такие сообщения
об ошибках. а более подробное описание привязки моделей и проверки достоверности
будет представлено в главах
27
и
28.
Модульное тестирование: обработка заказа
Чтобы выполнить модульное тестирование класса
рить поведение версии
POST
метода
Checkout
тым, использование привязки моделей
MVC
OrderController,
необходимо прове­
().Хотя метод выглядит коротким и прос­
означает наличие многих вещей, происходящих
"за кулисами'', которые должны быть протестированы.
Мы хотим обрабатывать заказ, только если в корзине присутствуют элементы и поль­
зователь предоставил достоверные детали о доставке. При любых других обстоятельс­
твах пользователю должно быть сообщено об ошибке. Вот первый тестовый метод, кото­
рый определен в файле класса по имени
OrderControllerTests. cs
внутри проекта
SportsStore. Tests:
using Microsoft.AspNetCore.Mvc;
using Moq;
using SportsStore.Controllers;
using SportsStore.Models;
using Xunit;
namespace SportsStore.Tests
puЬlic class OrderControllerTests
[Fact]
puЫic void Cannot_Checkout_Empty Cart() {
11 Организация - создание имитированного хранилища заказов
Mock<IOrderRepository> mock = new Mock<IOrderRepository>();
11 Организация - создание пустой корзины
Cart cart = new Cart();
11 Организация - создание заказа
Order order = new Order();
11 Организация - создание экземпляра контроллера
OrderController target = new OrderController(mock.Object, cart);
11
Действие
ViewResult result = target.Checkout(order) as ViewResult;
11 Утверждение - проверка, что заказ не был сохранен
mock.Verify(m => m.SaveOrder(It.IsAny<Order>()), Times.Never);
11 Утверждение - проверка, что метод возвращает стандартное представление
Assert.True(string.IsNullOrEmpty(result.ViewName) );
11 Утверждение - проверка, что представлению передана
11
недопустимая модель
Assert.False(result.ViewData.ModelState.IsValid);
Тест проверяет отсутствие возможности перехода к оплате при пустой корзине. Мы удосто­
веряемся, что метод
SaveOrder ()
имитированной реализации
IOrderReposi tory
ни­
когда не вызывается, что метод возвращает стандартное представление (которое повторно
отобразит введенные пользователем данные, давая ему шанс откорректировать их) и что
состояние модели, передаваемое представлению, помечено как недопустимое. Это может
выглядеть как излишне ограничивающий набор утверждений, но для проверки правильности
Глава 1О. SportsStore: завершение построения корзины для покупок
299
поведения нужны все три утверждения. Следующий тестовый метод работает в основном
так же, но внедряет в модель представления ошибку, эмулирующую проблему, о которой
сообщает средство привязки модели (что должно происходить в производственной среде,
когда пользователь вводит некорректные данные о доставке):
[Fact]
puЫic
void Cannot Checkout Invalid_ShippingDetails() {
- создание имитированного хранилища заказов
Mock<IOrderRepository> mock = new Mock<IOrderRepository>();
// Организация - создание корзины с одним элементом
Cart cart = new Cart();
cart.Additem(new Product(), 1);
//Организация - создание экземпляра контроллера
OrderController target = new OrderController(mock.Object, cart);
//Организация - добавление ошибки в модель
target.ModelState.AddModelError("error", "error");
// Действие - попытка перехода к оплате
ViewResult result = target.Checkout(new Order()) as ViewResult;
// Утверждение - проверка, что заказ не был сохранен
mock.Verify(m => m.SaveOrder(It.IsAny<Order>()), Times.Never);
//Утверждение - проверка, что метод возвращает стандартное представление
Assert.True(string.IsNullOrEmpty(result.ViewName));
//Утверждение - проверка, что представлению передается недопустимая модель
Assert.False(result.ViewData.ModelState.IsValid);
//Организация
Удостоверившись в том, что пустая корзина или некорректные данные о доставке предо­
твращают сохранение заказа, необходимо проверить, что нормальные заказы сохраняются
должным образом. Ниже приведен тест.
[Fact]
puЫic
void Can Checkout_And Submit Order() {
- создание имитированного хранилища заказов
Mock<IOrderRepository> mock = new Mock<IOrderRepository>();
/ / Организация - создание корзины с одним элементом
Cart cart = new Cart();
cart.Addltem(new Product(), 1);
//Организация - создание экземпляра контроллера
OrderCoпtroller target = new OrderController(mock.Object, cart);
// Действие - попытка перехода к оплате
RedirectToActionResult result =
target.Checkout(new Order()) as RedirectToActionResult;
// Утверждение - проверка, что заказ был сохранен
mock.Verify(m => m.SaveOrder(It.IsAny<Order>()), Times.Once);
//Утверждение - проверка, что метод перенаправляется на действие Completed
Assert.Equal("Completed", result.ActionName);
//Организация
Тестировать возможность идентификации допустимых сведений о доставке не нужно. Это
автоматически обрабатывается средством привязки моделей с использованием атрибутов,
примененных к свойствам класса
Order.
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
300
Отображение сообщений об ошибках проверки достоверности
Для проверки пользовательских данных инфраструктура МVС будет использовать
атрибуты проверки достоверности. примененные к классу
Order.
Тем не менее. чтобы
отобразить сообщения о проблемах, понадобится внести небольшое изменение. Здесь
задействована еще одна встроенная вспомогательная функция дескриптора . которая
инспектирует состояние проверки достоверности данных. предоставленных пользова­
телем. и добавляет предупреждающие сообщения для каждой обнаруженной проблемы.
В листинге
HTML,
10.18
демонстрируется добавление в файл
Ch e ckout . cs h tm l
элемента
который будет обрабатываться этой вспомогательной функцией дескриптора.
Листинг 10.18. Добавление области с итогами проверки достоверности в файле
Checkout.cshtml
из папки
Views/Order
@model Or d er
<h2>Check out n ow</ h 2>
<p>Please enter your details, an d we'll s h ip yo u r goods ri gh t away ! </ p >
<div asp-validation-summary="All" class="text-danger"></div>
<form asp-actio n="Checkout" metho d="post" >
< hЗ>Ship to </ h З>
Благодаря такому про стому изм ен е нию пользо вателю отображаются сообщения
об ошибках проверки достоверности. Чтобы увидеть резул ьтат, понадобится посетить
URL
вида
и попробовать перейти к оплате, не выбрав ни одного
/Order / Ch eckout
товара или не указав сведения о доставке (рис.
10.5).
Вспомогательная функция де­
скриптора, генерирующая такие сообщения. является частью системы проверки до­
стоверности моделей, которая подробно рассматривается в главе
Horne
Chess
Soccer
Watersports
27.
Check out now
Please enter your details, and l'>'e'll ship your goods right away!
•
•
•
•
•
•
Please enter а name
Please enter the first address line
Please erнer а city riame
Please enter а state nan1e
Please enter а country name
Sorry, your cart is en1pty!
Ship to
Рис. 1О.5. Отображение сообщений об ошибках проверки достоверности
Глава 1О. SportsStore: завершение построения корзины для покупок
301
Совет. Данные, отправленные пользователем, перед проверкой посылаются серверу, что
известно как проверка достоверности на стороне сервера , для которой инфраструктура
MVC предлагает великолепную подцержку. Проблема с проверкой достоверности на сто­
роне сервера заключается в том , что пользователю сообщается об ошибках лишь после
того, как данные отправлены серверу и обработаны, а также сгенерирована результирую­
щая страница
-
на занятом сервере все это может занять несколько секунд . По указан­
ной причине проверка достоверности на стороне сервера обычно дополняется провер­
кой достоверности на стороне клиента, при которой введенные пользователем значения
проверяются с помощью кода
JavaScript
до отправки серверу данных формы . Проверка
достоверности на стороне клиента будет описана в главе
27.
Отображение итоговой страницы
Чтобы завершить реализацию процесса оплаты , необходимо создать представ­
ление. которое будет отображаться, когда браузер перенаправляется на де йствие
Comp l eted контроллера Orde r . Добавим в папку Vi ew s / Order файл представления
Razor по имени Compl et ed. c sh t ml и поместим в него разметку. приведенную в лис­
тинге 10.19.
Листинг
10.19. Содержимое файла Completed. cshtml из папки Views/Order
<h 2> Thanks 1 </h2>
<p>T h a n ks for pla c ing your order . </p>
<p >We 'll ship you r g oods a s s oon a s p os s i Ы e.< / p>
Для интеграции представления
Completed
в приложение никаких изменений в
код вносить не придется, поскольку требуемые операторы уже были добавлены при
определении метода действия
Comp le t ed ()
в листинге
10.17.
Теперь пользователь
может проходить через весь процесс, начин ая с выбора товаров и заканчивая пере­
ходом к оплате. При условии. что пол ьзов атель пр едоставил корр ектные сведения о
доставке (и в корзине есть какие-то товары), после щелчка на кнопке
он увидит итоговую страницу (рис .
10.6).
Thanks!
Home
Thanks for placing your order.
Chess
We'JJ sl1ip your goods as soon as
possiЬJe.
Soccer
Watersports
Рис.
10.6.
Итоговая страница завершенного заказа
Complete order
302
Часть 1. Введение в инфраструктуру ASP.NEТ Core MVC 2
Резюме
Мы завершили все основные части приложения
SportsStore.
отвечающие за вза­
имодействие с пользователями. Конечно, до сайта Amazoп приложению далеко, но в
нем имеется каталог товаров с возможностью просмотра по категориям и страницам,
аккуратная корзина для покупок и простой процесс оплаты.
Архитектура с хорошим разделением означает, что мы можем легко изменять
поведение любой порции приложения. не беспокоясь о возникновении проблем или
несовместимости где-либо в приложении. Например. мы могли бы изменить способ
сохранения заказов, что никак бы не повлияло на корзину для покупок. каталог това­
ров или любую другую область приложения. В следующей главе мы добавим средства.
необходимые для администрирования приложения
SportsStore.
ГЛАВА
11
SportsStore:
администрирование
в настоящей главе мы продолжим строить приложение SportsStore. чтобы предо­
ставить администратору сайта способ управления заказами и товарами.
Управление заказами
В предыдущей главе была добавлена поддержка для получения заказов от пользо­
вателей и сохранения их в базе данных. В этой главе мы собираемся создать простой
инструмент администрирования. который позволит просматривать полученные зака­
зы и помечать их как отгруженные.
Расширение модели
Первым изменением, которое необходимо внести, является расширение модели,
чтобы можно было фиксировать, какие заказы были отгружены. В листинге
показано добавление нового свойства в класс
Order. cs
Листинг
внутри папки
11.1.
Models.
Добавление свойства в файле
Order. cs
из папки
Models
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace SportsStore.Models
puЫic
11. l
Order, который определен в файле
class Order {
[BindNever]
puЫic int OrderID { get; set; )
[ BindNever]
puЫic ICollection<CartLine> Lines { get; set; )
[BindNever]
bool Shipped { get; set; }
puЬlic
[Required(ErrorMessage = "Please enter
/ / Введите имя
puЫic string Name { get; set; )
а
name")]
[Required(ErrorMessage = "Please enter the first address line")]
11 Введите первую строку адреса
304
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
puЫic
puЫic
puЫic
string Linel
string Line2
string LineЗ
get; set;
get; set;
get; set;
[Required(ErrorMessage
=
"Please enter
11
puЫic
=
"Please enter
11
puЫic
=
"Please enter
11
puЫic
а
state narne")]
Введите название штата
string State { get; set; 1
string Zip { get; set; 1
[Required(ErrorMessage
puЫic
city narne")J
string City { get; set; 1
[Required(ErrorMessage
puЫic
а
Введите название города
а
country narne")]
Введите название страны
string Country { get; set; )
bool GiftWrap { get; set; 1
Такой итеративный подход к расширению и адаптации модели с целью поддержки
различных средств типичен при разработке приложений
MVC.
В идеальном мире у
нас была бы возможность полностью определить классы моделей в начале проекта и
просто строить приложение на их основе, но так случается только в простейших про­
ектах, а на практике следует ожидать итеративную разработку по мере понимания
того, что требуется разрабатывать и развивать.
Миграции
Entity Framework Core
облегчают этот процесс, поскольку нам не при··
ходится вручную удерживать схему базы данных в синхронизированном состоянии
с классами моделей, создавая и запуская команды
бы отразить добавление свойства
строки или
PowerShell,
Shipped
SQL.
Обновим базу данных, что­
в класс
перейдя в папку проекта
Order, открыв окно командной
SportsStore (ту, что содержит файл
и выполнив следующую команду:
Startup. cs)
dotnet ef rnigrations add ShippedOrders
Миграция будет применена автоматически, когда приложение запустится и в клас­
се
произойдет обращение к методу
SeedData
структурой
Migrate
(),предоставленному инфра­
Entity Framework Core.
Добавление действий и представления
Функциональность, требуемая для отображения и обновления набора заказов в
базе данных, относительно проста, потому что она строится на основе средств и ин­
фраструктуры, которые были созданы в предшествующих главах. В листинге
контроллеру
Листинг
Order
11.2.
Добавление двух методов действий в файле
из папки
Controllers
using Microsoft.AspNetCore.Mvc;
using SportsStore.Models;
using Systern.Linq;
narnespace SportsStore.Controllers
class OrderController : Controller
private IOrderRepository repository;
puЫic
11.2
добавляются два метода действий.
OrderController. cs
к
Глава 11. SportsStore: администрирование
305
private Cart cart;
OrderController(IOrderRepo sitory repoService, Cart cartService) {
repository = repoService;
cart = cartService;
puЫic
ViewResul t List () =>
View(repository.Orders.Wh ere(o => !o.Shipped));
puЫic
[HttpPost]
IActionResult мarkShipped(int orderID) (
Order order = repository.Orders
orderID);
.FirstOrDefault(o => o.OrderID
if {order != null) (
order.Shipped = true;
repository.SaveOrder(orde r);
puЬlic
==
return RedirectToAction(nameof( List));
puЫic
ViewResult Checkout() => View(new Order());
(HttpPost]
IActionResult Checkout(Order order) {
if (cart.Lines.Count() == 0) {
ModelState.AddModelError ("", "Sorry, your cart is empty!");
puЫic
if (ModelState. IsValid) {
order.Lines = cart.Lines.ToArray();
repository.SaveOrder(ord er);
return RedirectToAction(nameof(C ompleted));
else {
return View(order);
ViewResult Completed() {
cart.Clear();
return View ();
puЬlic
Метод
List ()
выбирает все объекты
рых имеет значение
твия
List ()
false,
Order
в хранилище. свойство
Shipped
кото­
и передает их стандартному представлению. Метод дейс­
будет использоваться для отображения администратору списка неотгру­
женных заказов.
MarkShipped () будет получать запрос POST, указывающий идентифика­
Order
из хранилища, чтобы установить его свойство Shipped в true и сохранить.
Для отображения списка неотгруженных заказов добавим в папку Views/Order
файл представления Razor по имени List. cshtrnl и поместим в него разметку из
Метод
тор заказа, который применяется для извлечения соответствующего объекта
листинга
11.3.
Элемент tаЫе используется для отображения ряда деталей, включая
сведения о приобретенных товарах.
306
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Листинг
11.3.
@model
IEnumeraЫe<Order>
Содержимое файла
List.cshtml
из папки
Views/Order
@{
ViewBag.Title = "Orders";
Layout = "_AdminLayout";
@if (Model.Count() > 0) {
<tаЫе class="taЫe taЫe-bordered taЫe-striped">
<tr><th>Name</th><th>Zip</th><th colspan="2">Details</th><th></th></tr>
@foreach (Order о in Model) {
<tr>
<td>@o.Name</td><td>@o.Zip</td><th>Product</th>< th>Quantity</th>
<td>
<form asp-action="MarkShipped" method="post">
<input type="hidden" name="orderid" value="@o.OrderID" />
<button type="submit" class="btn Ьtn-sm Ьtn-danger">
Ship
</button>
</form>
</td>
</tr>
@foreach (CartLine line in o.Lines) {
<tr>
<td colspan="2"></td>
<td>@line.Product.Name</td><td>@line.Quantity</td >
<td></td>
</tr>
</tаЫе>
else {
<div class="text-center">No Unshipped Orders</div>
Каждый заказ отображается с кнопкой
му методу действия
List
MarkShipped
Ship
(Отгрузить), которая отправляет фор­
().С помощью свойства
Layout
для представления
указана другая компоновка, которая переопределяет компоновку. заданную в
файле_ ViewStart.
cshtml.
Для добавления компоновки создадим в папке
AdminLayout. cshtml
Views/Shared
с применением шаблона элемента
файл по имени
MVC View Layout Page
(Страница компоновки представления МVС) и поместим в него разметку, показанную
в листинге
Листинг
11.4.
11.4.
Содержимое файла
_AdminLayout.cshtml
из папки
Views/Shared
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.min.css" />
<title>@ViewBag.Title</title>
</head>
Глава 11. SportsStore: администрирование
307
<body c l as s = "m- 1 р -1" >
<div c l ass ="bg-info p- 2 " >< h4 >@Vi ewBag.Titl e< / h 4 ></ div>
@RenderBody ( )
</body>
</ html>
Чтобы просматривать и управлять заказами в приложении, запустим приложение ,
выберем некоторые товары и перейдем к оплате. Затем посетим
L i s t.
Появится сводка по созданным заказам (рис.
11.1).
URL
вида
/O r de r /
Щелкнем на кнопке
Ship;
база данных обновится. а список ожидающих заказов будет пуст.
С)
Ord«s
Orders
Name
Zlp
Details
Alice
10036
Product
Quantity
Lifejacket
Soccer Ball
Рис.
11.1.
Управление заказами
На заметку! В настоящий момент ничто не может воспрепятствовать запросу пользователем
URL
вида
/ Order / Li s t
и администрированию своего заказа. В главе
12
объясняется,
как ограничить доступ к методам действий.
Добавление средств управления каталогом
Соглаш ение для управления более сложными коллекциями элементов предусмат ­
ривает предоставление пользователю страниц двух типов : страницы списка и страни ­
цы редактирования (рис.
11.2).
Edit ltem: Kayak
List Screen
\tem
Actions
Kayak
Edlt 1Delete
Description: \
А boat for one ре".
Life)acket
Edit 1Oelete
Cзtegory:
Watersports
Soccer Ьа11
ШI .12..е.!Ш
Price ($):
275.00
Add New ltem
Рис.
11.2.
\ Kayak
Name:
::::::====~
Save
Эскиз пользовательского интерфейса
CRUD
Cancel
для каталога товаров
308
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Вместе эти страницы позволяют пользователю создавать, читать, обновлять и
удалять
(create, read, update. delete - CRUD)
называются onepat~uямu
CRUD
CRUD.
элементы в коллекции. Такие действия
Разработчики нуждаются в реализации операций
настолько часто, что средство формирования шаблонов в
гает сценарии для создания контроллеров
CRUD
Visual Stud!o
предла­
с заранее определенными методами
действий (включение средства формирования шаблонов рассматривалось в главе
Но, как и со всеми шаблонами
NET Core MVC
Visual Studio,
я считаю, что изучать возможности
8).
ASP.
лучше напрямую.
Создание контроллера
CRUD
Начнем с создания отдельного контроллера для управления каталогом товаров.
Добавим в папку
Controllers
приведенным в листинге
Листинг
11.5.
файл класса по имени
AdminController. cs
с кодом,
11.5.
Содержимое файла
AdminController. cs
из папки
Controllers
using Microsoft.AspNetCore.Mvc;
using SportsStore.Models;
namespace SportsStore.Controllers
puЫic
class AdminController : Controller
private IProductRepository repository;
puЫic
AdminController(IProductRepository repo)
repository = repo;
puЫic
В
(
ViewResult Index() => View(repository.Products);
конструкторе
контроллера
IProductRepository,
объявлена
зависимость
В классе контроллера определен единственный метод действия
вызывает метод
View
от
интерфейса
которая будет распознаваться при создании экземпляров.
Index (),
который
(),чтобы выбрать стандартное представление для действия, и
передает ему в качестве модели представления набор товаров из базы данных.
Модульное тестирование: метод действия
Нас интересует поведение метода действия
Index ()
Index () в контроллере Admin, которое за­
Product из хранилища. Протестировать
ключается в корректном возвращении объектов
его можно за счет создания имитированной реализации хранилища и сравнения тестовых
данных с данными, которые возвращает метод действия. Ниже показан код модульного
теста, помещенный в новый файл по имени
SportsStore.Tests.
using
using
using
using
using
using
using
System.Collections.Generic;
System.Linq;
Microsoft.AspNetCore.Mvc;
Moq;
SportsStore.Controllers;
SportsStore.Models;
Xunit;
AdminControllerTests. cs
внутри проекта
Глава 11. SportsStore: администрирование
309
namespace SportsStore.Tests {
class AdminControllerTests
[ Fact]
puЬlic void Index_Contains_All_Products()
//Организация - создание имитированного хранилища
Mock<IProductRepository> mock = new Mock<IProductRepository>();
mock.Setup(m => m.Products) .Returns( (new Product[] {
"Pl"),
1, Name
new Product {ProductID
"Р2") ,
new Product { Product I D = 2, Name
"РЗ"),
new Product {ProductID = 3, Name
)) .AsQueryaЫe<Product>() );
//Организация - создание контроллера
AdminController target = new AdminController(mock.Object);
puЫic
11
Действие
Product[] result
= GetViewModel<IEnumeraЫe<Product>>(target.Index() )?.ToArray();
//Утверждение
result.Length);
Assert.Equal ("Pl", resu.lt[O] .Name);
Assert.Equal("P2", result[l].Name);
Assert.Equal ("РЗ", result[2] .Name);
Assert.Equal(З,
private Т GetViewModel<T>(IActionResu.lt resu.lt) where
return (result as ViewResult)?.ViewData.Model as Т;
В тест был добавлен метод
GetViewModel ()
Т
c.lass {
для распаковки результата, возвращаемого
методом действия, и получения данных модели представления. Далее в главе будут реали­
зованы дополнительные тесты, которые используют этот метод.
Реализация представления списка
Следующим шагом будет добавление представления для метода действия
контроллера
ния
Razor
Admin.
по имени
Создадим папку
Index. cshtml
Содержимое файла
Листинг
11.6.
@mode.l
IEnumeraЬle<Product>
Views/Admin
с содержимым, приведенным в листинге
Index. cshtml
Index ()
и добавим в нее файл представле­
из папки
Views/Admin
@{
ViewBag.Title = "A.ll Products";
Layout = "_AdmiпLayout";
<tab.le c.lass="taЫe taЬle-striped tab.le-bordered tab.le-sm">
<tr>
<th c.lass="text-right">ID</th>
<th>Name</th>
<th c.lass="text-right">Price</th>
<th class="text-center">Actions</th>
</tr>
11.6.
310
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
@foreach (var item in Model) {
<tr>
<td class = "text-right">@item.ProductID</td >
<td>@item.Name</td >
<td class="text-right">@item.Price.ToString("c")</td>
< td class= "text-center">
<form asp-action="Delete" method="post">
<а asp-action = "Edit" class = "Ьtn Ьtn-sm Ьtn-warning"
asp -route-productid="@item.ProductID">
Edit
</а>
<input type="h idde n" name = "Produc tID" value= "@item.P roductl D" />
<button type = "submit" clas s= "Ьtn Ьtn -danger Ьtn-sm" >
Delete
</button>
</form >
</td>
</tr>
< /tаЫе>
<di v class = "text-center" >
<а asp-a ctio n = "Create" class="Ьtn
</d iv>
Ьtn-primary">Add
Produ ct</a>
Представление содержит таблицу, в которой для каждого товара предусмотрена
строка с ячейками, содержащими наименование и цену товара. Кроме того. в каждой
строке присутствуют кнопки.
которые позволят редактировать сведения о товаре и
удалять его, отправляя запросы к действиям
имеется кнопка
Add Product
добавим действия
Edi t
и
Delete .
В дополнение к таблице
(Добавить товар). нацеленная на действие
Edi t, Dele te
и
Creat e
Create.
посмотреть, как отображаются товары. запустив приложение и запросив
/Admin/Index
(рис.
11.3).
All Products
10 NAme
Price
1 K•yak
S27S.OO
Шejaclc:e1
148.95
3 Socct>r Ball
S1950
2
б
Thinicing
Act1ons
134.95
4 Corner Flags
S Stadium
579.\0000
Сар
\16.00
7 Unsteady Chair
129.95
8 Hum.an Chбs 8oard
9 Bling·Blirig King
Мы
в последующих разделах, а пока можно
175.00
11.200.00
if!'i*
Рис. 11.З. Отображение списка товаров
URL
вида
Глава 11. SportsStore: администрирование
Совет. В листинге
11.6 кнопка Edit
(Редактировать) находится внутри элемента
две кнопки располагаются рядом благодаря интервалу, примененному
Edit будет
посылать серверу НТТР-эапрос
это не требует элемента
forrn.
forrn,
Bootstrap.
так что
Кнопка
GET для получения текущих сведений о товаре;
Однако поскольку кнопка
Delete
(Удалить) будет вносить
изменение в состояние приложения, необходимо использовать НТТР-эапрос
рый требует элемента
311
POST,
кото­
forrn.
Редактирование сведений о товарах
Чтобы предоставить средства создания и обновления, мы добавим страницу ре­
дактирования сведений о товаре, подобную показанной на рис.
1 1.2.
Задача состоит
из двух частей:
•
отобразить страницу, которая позволит администратору изменять значения для
свойств товара;
•
добавить метод действия, который обработает внесенные изменения, когда они
будут отправлены.
Создание метода действия
В листинге
Adrnin.
11. 7
Edi t ()
приведен код метода действия
Edi t ( ) , добавленного в контроллер
который будет получать НТГР-запрос, отправляемый браузером, когда пользо­
ватель щелкает на кнопке
Листинг
11.7.
Edit.
Добавление метода действия
из папки
Edi t ()
в файле
AdminController. cs
Controllers
using Microsoft.AspNetCore.Mvc;
using SportsStore.Models;
using System.Linq;
namespace SportsStore.Controllers
puЫic class AdminController : Controller
private IProductRepository repository;
puЫic AdminController(IProductRepository repo) {
repository = repo;
puЫic
ViewResult Index() => View(repository.Products);
ViewResul t Edi t (int productid) =>
View(repository.Products
productid));
.FirstOrDefault(p => p.ProductID
puЬlic
==
Этот простой метод ищет товар с идентификатором, соответствующим значе­
нию параметра
View ().
productld,
и передает его как объект модели представления методу
312
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Модульное тестирование: метод действия Edi t О
В методе действия
Edi t ()
нам необходимо протестировать две линии поведения. Первая
заключается в том, что мы получаем запрашиваемый товар, когда предоставляем допусти­
мое значение идентификатора, чтобы удостовериться в редактировании ожидаемого това­
ра. Вторая проверяемая линия поведения связана с тем, что мы не должны получать товар
при запросе значения идентификатора, отсутствующего в хранилище. Ниже показаны тес­
товые методы, добавленные в файл класса
AdminControllerTests. cs.
[Fact]
void Can_Edit Product() {
- создание имитированного хранилища
Mock<IProductRepository> mock = new Mock<IProductRepository>();
mock.Setup(m => m.Products) .Returns( (new Product[] {
new Product {ProductID
1, Name
"Pl"),
new Product {ProductID = 2, Name
"Р2"),
new Product {ProductID = 3, Name
"Р3"),
)) .AsQueryaЬle<Product>());
11 Организация - создание контроллера
AdminController target = new AdminController(mock.Object);
puЫic
11
Организация
11
Действие
Product pl = GetViewModel<Product>(target.Edit(l));
Product р2 = GetViewModel<Product>(target.Edit(2));
Product р3 = GetViewModel<Product>(target.Edit(3));
11
Утверждение
Assert.Equal(l, pl.ProductID);
Assert.Equal(2, p2.ProductID);
Assert.Equal(3, p3.ProductID);
[Fact]
puЫic
11
void Cannot_Edit_Nonexistent Product() {
Организация
- создание имитированного хранилища
Mock<IProductRepository> mock = new Mock<IProductRepository>();
mock.Setup(m => m.Products) .Returns( (new Product[] {
new Product {ProductID = 1, Name
"Pl"),
new Product {ProductID = 2, Name
"Р2"),
new Product {ProductID = 3, Name
"Р3"),
)) .AsQueryaЬle<Product>());
11 Организация - создание контроллера
AdminController target = new AdminController(mock.Object);
11
Действие
Product result
11
=
GetViewModel<Product>(target.Edit(4));
Утверждение
Assert.Null(result);
Глава 11. SportsStore: администрирование
313
Создание представления редактирования
Теперь, располагая методом действия, можно создать представление для отображе­
ния. Добавим в папку
Views/Admin
Листинг
11.8.
Содержимое файла
Razor по имени Edi t. cshtml
11.8.
файл представления
и поместим в него разметку, приведенную в листинге
Edit.cshtml
из папки
Views/Admin
@model Product
@{
ViewBag.Title = "Edit Product";
Layout = "_AdmiпLayout";
method="post">
asp-for="ProductID" />
<div class="form-group">
<label asp-for="Name"></label>
<iпput asp-for="Name" class="form-coпtrol" />
</div>
<div class="form-group">
<label asp-for="Descriptioп"></label>
<textarea asp-for="Descriptioп" class="form-coпtrol"></textarea>
</div>
<div class="form-group">
<label asp-for="Category"></label>
<iпput asp-for="Category" class="form-coпtrol" />
</div>
<div class="form-group">
<label asp-for="Price"></label>
<iпput asp-for="Price" class="form-coпtrol" />
</div>
<div class="text-ceпter">
<form
asp-actioп="Edit"
<iпput type="hiddeп"
<buttoп class="btп btп-primary" type="submit">Save</buttoп>
<а asp-actioп="Iпdex"
class="btп btп-secoпdary">Cancel</a>
</div>
</form>
В представлении имеется форма
HTML,
большая часть содержимого которой гене­
рируется посредством вспомогательных функций дескрипторов, включая установку
целей для элементов
рибутов
name, id
и
form
value
и а, установку содержимого элементов
для элементов
input
и
label
и выдачу ат­
textarea.
Чтобы увидеть НТМL-разметку. генерируемую представлением, запустим прило­
жение, перейдем на
товаров (рис.
URL типа / Admin/ Index
Совет. Скрытый элемент
Значение
и щелкнем на кнопке
Edit для одного из
11.4).
ProductID
inpu t
для свойства Р roduc t
I D применяется
ради простоты.
генерируется базой данных как первичный ключ, когда новый объ­
ект сохраняется инфраструктурой Eпtity
Framework Core,
и его безопасное изменение
может оказаться сложным процессом. Для большинства приложений проще всего предо­
твратить изменение значения со стороны пользователя.
314
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
D Edrt Product
С
Х
ф localhost
Edit Product
Name
1 Soccer Ball
Description
FIFA-approved s1ze and we1ght
Category
Soccer
Price
19 50
-
Рис.
11.4.
Cancel
Отображение сведений о товаре для редактирования
Обновление хранилища товаров
Прежде чем можно будет о брабат ыват ь р езул ьтаты р еда ктирова ния . хр а нили ­
ще товар ов п о надобится рас ширить. добавив возм ожность сохран ения из м ен е ний .
Первым делом необходимо добавить к интерфейсу
тод (листинг
Листинг
I Produ ctRe p o s i t o ry
новый ме­
11.9).
11.9. Добавление метода в файл IProductResposi tory. cs
из папки Models
us i ng Sys tem.Linq;
na mespace Spo rt sSto re.Model s
p u Ьlic
interfa c e I Produc t Re po s ito ry
IQu e ryaЫe< P r od uct >
Pr od uc ts { ge t;
void SaveProduct(Product product);
З атем можно добавить новый метод к реализ а ции хранилища с помощью
Entity Framework
(листинг 11.10).
С о ге, которая определена в фай л е
EFProd uct Repo s i to r y . c s
315
Глава 11. SportsStore: администрирование
Листинг
11.1 О.
Реализация нового метода в файле
из папки
EFProductReposi tory. cs
Model s
using System.Collections.Generic;
using System.Linq;
namespace SportsStore.Models {
class EFProductRepository : IProductRepository {
private ApplicationDbContext context;
puЫic
EFProductRepository(ApplicationDbContext ctx) {
context = ctx;
puЬlic
IQueryaЫe<Product> Products => context.Products;
void SaveProduct (Product product)
if (product. ProductID == 0) {
context.Products.Add(product);
else {
Product dЬEntry = context.Products
.FirstOrDefault(p => p.ProductID == product.ProductID);
if (dЬEntry != null) (
dЬEntry.Name = product.Name;
dЬEntry.Description = product.Description;
dЬEntry.Price = product.Price;
dЬEntry.Category = product.Category;
puЫic
puЫic
context.SaveChanges();
Реализация метода
ProductID
SaveChanges ()
добавляет товар в хранилище, если значение
равно О; в противном случае применяются изменения к существующей
записи в базе данных.
Мы не хотим здесь вдаваться в детали инфраструктуры
Entity Framework Core,
пос­
кольку. как упоминалось ранее, это отдельная крупная тема. к тому же она не являет­
ся частью ASP.NEТ
Core MVC.
Тем не менее, кое-какой код в методе
SaveProduct ()
оказывает влияние на проектное решение. положенное в основу приложения МVС.
Нам известно. что обновление должно выполняться. когда получен параметр
Product,
который имеет ненулевое значение
влечения из хранилища объекта
Product
ProductID.
Задача решается путем из­
с тем же самым значением
ProductID
и
обновления всех его свойств, чтобы они соответствовали значениям свойств объекта,
переданного в качестве параметра.
Причина таких действий в том, что инфраструктура
Entity Framework Core
отсле­
живает объекты. которые она создает из базы данных. Объект. переданный методу
SaveChanges ().
создается системой привязки моделей
Entity Framework Core
ничего не знает о новом объекте
менять обновление к базе данных, коrда объект
Product
MVC,
т.е. инфраструктура
Product,
и она не будет при­
модифицирован. Существует
множество способов решения указанной проблемы. но мы принимаем самый простой
из них. предполагающий поиск соответствующего объекта, о котором известно инф­
раструктуре
Entity Framework Core,
и его явное обновление.
316
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Добавление нового метода в интерфейс
класса имитированного хранилища
в главе
8.
IProductRepository нарушает
FakeProductReposi tory, который был
работу
создан
Имитированное хранилище использовалось в целях быстрого старта про­
цесса разработки и демонстрации возможности применения служб для гладкой за­
мены реализаций интерфейса, не изменяя компоненты. которые на них опираются.
Имитированное хранилище больше не понадобится. В листинге
терфейс
IProductReposi tory
11. 11
видно, что ин­
удален из объявления класса. поэтому продолжать мо­
дификацию класса по мере добавления функций хранилища не придется.
Листинг
11.11.
Удаление интерфейса в файле
из папки
FakeProductReposi tory. cs
Models
using System.Collections.Generic;
using System.Linq;
namespace SportsStore.Models {
puЫic сlавв
FakeProductRepository /* : IProductRepository */
Products => new List<Product>
new Product { Name
"Football", Price = 25 ),
new Product { Name = "Surf board", Price = 179 ) ,
new Product { Name = "Running shoes", Price = 95 )
puЬlic
IQueryaЫe<Product>
).AsQueryaЫe<Product>{);
Обработка запросов POST в методе действия Edi t ()
К настоящему моменту все готово для реализации в контроллере
женной версии метода действия
Edi t (),
инициируемые по щелчку администратором на кнопке
метода приведен в листинге
Листинг
11.12.
Save
AdminController. cs
Controllers
namespace SportsStore.Controllers
class AdminController : Controller
private IProductRepository repository;
puЫic
AdminController(IProductRepository repo) {
repository = repo;
puЫic
ViewResult Index() => View(repository.Products);
ViewResult Edit(int productld) =>
View(repository.Products
.FirstOrDefault(p => p.ProductID == productld));
puЬlic
[HttpPost]
POST.
(Сохранить). Код нового
using Microsoft.AspNetCore.Mvc;
using SportsStore.Models;
using System.Linq;
puЫic
перегру­
11.12.
Определение метода действия в файле
из папки
Admin
которая будет обрабатывать запросы
Глава 11. SportsStore: администрирование
317
IActionResul t Edi t (Product product)
if (ModelState.IsValid) {
repository.SaveProduct(product);
TempData["message"] = $ 11 {product.Name} has been saved";
return RedirectToAction("Index");
else {
puЫic
//Что-то не так со значениями данных
return View(product);
Мы выясняем. смог ли процесс привязки модели проверить достоверность
отправленных
пользователем
ModelState. IsValid.
для
данных,
чего
читаем
значение
свойства
Если здесь все в порядке, тогда мы сохраняем изменения в
хранилище и направляем пользователя на действие
Index,
так что он увидит моди­
фицированный список товаров. При наличии какой-нибудь проблемы с данными мы
снова визуализируем стандартное представление, чтобы пользователь получил воз­
можность внести корректировки.
После сохранения изменений в хранилище сообщение сохраняется с использо­
ванием средства TempDa ta. которое является частью средства состояния сеанса
ASP.NET
Саге. Это словарь пар "ключ/значение", похожий на применяемые ранее
средства данных сеанса и
ViewBag.
Основное отличие объекта
TempData
от данных
сеанса в том, что он хранится до тех пор. пока не будет прочитан.
В такой ситуации использовать
ViewBag
невозможно. потому что объект
ViewBag
передает данные между контроллером и представлением, и он не может удерживать
данные дольше, чем длится текущий НТТР-запрос. Когда редактирование успешно,
браузер перенаправляется на новый
URL,
поэтому данные
ViewBag
утрачиваются.
Мы могли бы прибегнуть к средству данных сеанса. но тогда сообщение хранилось
бы вплоть до его явного удаления, чего делать бы не хотелось.
Таким образом. объект
TempDa ta
подходит как нельзя лучше. Данные ограничи­
ваются сеансом одного пользователя (пользователи не видят объекты
TempData
друг
друга) и хранятся достаточно долго, чтобы быть прочитанными. Мы будем читать
данные в представлении, которое визуализируется методом действия, куда перена­
правлялся пользователь, и определяется в следующем разделе.
Модульное тестирование:
метод действия Edi t ( ) , обрабатывающий запросы POST
В методе действия
Edi t () ,
обрабатывающем запросы
POST,
мы должны удостоверить­
ся, что хранилищу товаров для сохранения передаются допустимые обновления объекта
Product,
полученного в качестве аргумента метода. Кроме того, необходимо проверить,
что недопустимые обновления (т.е. содержащие ошибки проверки достоверности модели)
в хранилище не передаются. Ниже приведены тестовые методы, которые добавлены в файл
AdminControJlerTests. cs.
[Fact]
puЫic
11
void
Сап
Организация
Save Valid Changes() {
-
создание имитированного хранилища
Mock<IProductRepository> mock
=
new Mock<IProductRepository>();
318
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
11 Организация - создание имитированных временных данных
Mock<ITempDataDictionary> tempData = new Mock<ITempDataDictionary>();
//Организация - создание контроллера
AdminController target = new AdminController (mock.Object) {
TempData = tempData.Object
) ;
11
Организация - создание товара
Product product = new Product { Name = "Test" } ;
11
Действие - попытка сохранить товар
IActionResult result = target.Edit(product);
//Утверждение - проверка того, что к хранилищу быnо произведено обращение
mock.Verify(m => m.SaveProduct(product));
11 Утверждение - проверка, что типом результата является nеренаправление
Assert.IsType<RedirectToActionResult>(result);
Assert.Equal ("Index", (result as RedirectToActionResult) .ActionName);
[Fact]
puЫic
void Cannot_Save_Invalid_Changes() {
- создание имитированного хранилища
Mock<IProductRepository> mock = new Mock<IProductRepository>();
//Организация - создание контроллера
AdminController target = new AdminController(mock.Object);
11 Организация - создание товара
Product product = new Product { Name = "Test" );
// Организация - добавление ошибки в состояние модели
target .ModelState .AddModelError ( "error", "error");
11
Организация
11
Действие
-
IActioпResult
попытка сохранить товар
result = target.Edit(product);
11 Утверждение - проверка того, что к хранилищу быnо произведено обращение
mock.Verify(m => m.SaveProduct(It.IsAny<Product>()), Times.Never());
11 Утверждение - проверка типа результата метода
Assert.IsType<ViewResult>(result);
Отображение подтверждающего сообщения
Мы будем иметь дело с сообщением, сохраненным с помощью
компоновки
_AdminLayout. cshtml
(листинг
11.13).
TempDa ta, в файле
За счет обработки сообщения в
компоновке мы можем создавать сообщения в любом представлении. которое применяет
данную компоновку. без необходимости в создании дополнительных выражений
Листинг
11.1 З.
Razor.
Обработка сообщения
из папки
TempDa ta в файле _AdminLayou t. csh tml
Views/Shared
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.min.css" />
<title>@ViewBag.Title</title>
</head>
Глава 11. SportsStore: администрирование
319
<bod y c l as s= "m- 1 р - 1 " >
<div c la ss= "bg - i nf o p- 2 " ><h 4 >@View8ag . Title< /h4 ></div>
@if (TempData["message"] != null) {
<div class="alert alert-success">@TempData [ "message" ]</div>
@RenderBody ()
</bo dy>
</ht ml >
Совет. Преимущество работы с сообщением внутри компоновки заключается в том , что поль­
зователи будут видеть его на любой странице, визуализированной после сохранения из­
менений. В данный момент мы возвращаем его списку товаров, но рабочий поток можно
изменить с целью визуализации какого-то другого представления, и пользователи будут
по-прежнему видеть сообщение (при условии, что следующее представление использует
ту же самую компоновку).
Теперь мы располагаем всеми фрагментами для редактирования сведений о това­
рах. Чтобы увидеть. как все они работают, запустим приложение. перейдем на
вида
/Adrnin/Index.
кнопке
Save.
щелкнем на кнопке
Edit
URL
и внесем и з менение . Затем щелкнем на
Произойдет перенаправле ние на
/Adrn in /Index
и отобразится сообще­
TempDa ta (ри с . 11 .5) . Если перезагруз ить страницу со списком товаров. то со ­
общение исчезнет. поскольку после чтения объект TempDat a удаляется . Подход очень
ни е из
удобен. т.к. не приходится иметь дело со старыми сообщениями.
Х
~ EdtPJodi.кt
Edit Product
Name
Green Kayak
Pri"
2 lif•j•<k•t
S•B.95
3 Soccer B•ll
S19.50
S34.95
4 Corn<r Flags
5 Stadium
S79,500.00
Th1nking Сар
S16.00
7 Unste•dy Chaa
S299S
8 Humon Choss 8cord
S7S.OO
б
$1.200.00
9 Bling · Bling King
6
Рис.
11.5.
дction1
$275.00
1 Grten Kayak
Gil*
Редактирование сведений о товаре и отображение сообщения из TempDa ta
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
320
Добавление проверки достоверности модели
Мы добрались до точки, когда к илассам модели необходимо добавить правила
проверки достоверности. Пока что администратор может вводить отрицательные
значения для цен или оставлять описания пустыми
и приложение SportsStoгe бла­
-
гополучно сохранит эти данные в базе. Смогут ли недопустимые данные успешно
сохраниться, зависит от того, удовлетворяют ли они ограничениям в таблицах
созданных инфраструктурой
Entity
SQL,
Fгamewoгk Соге, и для большинства приложений
таких мер безопасности будет недостаточно. Чтобы защититься от недопустимых
значений данных, свойства иласса
делалось в илассе
Листинг
11.14.
Order
Product
10
декорируются с помощью атрибутов, как
(листинг
11.14).
Применение атрибутов проверки достоверности в файле Product. cs
из папки
using
using
из главы
Models
System.Componentмodel.DataAnnotations;
Мicrosoft.AspNetCore.Мvc.ModelBinding;
namespace SportsStore.Models {
puЫic
class Product {
int ProductID { get; set; }
puЫic
[Required(ErrorMessage = "Please enter
11
puЫic
11
а
douЫe.мa.xValue,
ErrorМessage
= "Please
11
enter
а
positive price")]
Введите положительное значение для цены
decimal Price { get; set; }
[Required(ErrorMessage = "Please specify
11
puЬlic
В главе
description")]
Введите описание
string Description { get; set; }
[Required]
[Range(0.01,
puЫic
product name")]
string Name { get; set; }
[Required(ErrorMessage = "Please enter
puЫic
а
Введите наименование товара
а
category")]
Укажите категорию
string Category { get; set; }
1О
использовалась вспомогательная функция дескриптора для отображе­
ния сводки по ошибкам проверки достоверности в верхней части формы. Здесь мы
применим похожий подход, но будем отображать сообщения об ошибках рядом с эле­
ментами формы в представлении
Листинг
11.15.
Edi t
(листинг
11. 15).
Добавление элементов для отображения ошибок проверки
достоверности в файле
@model Product
@{
ViewBag.Title = "Edit Product";
Layout = "_AdminLayout";
Edi t. cshtml из папки Views/ Admin
Глава 11. SportsStore: администрирование
321
<form asp-action="Edit" method="post">
<input type="hidden" asp-for="ProductID" />
<div class="form-group">
<label asp-for="Name"></label>
<div><span asp-validation-for="Name" class="text-danger"></span ></div>
<input asp-for="Name" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Description"></l abel>
<div><span asp-validation-for="Descri ption" class="text-danger"></span >
</div>
<textarea asp-for="Description" class="form-control"></te xtarea>
</div>
<div class="form-group">
<label asp-for="Category"></labe l>
<div><span asp-validation-for="Categ ory" class="text-danger"></spa n>
</div>
<input asp-for="Category" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Price"></label>
<div><span asp-validation-for="Price " class="text-danger"></spa n>
</div>
<input asp-for="Price" class="form-control" />
</div>
<div class="text-center">
<button class="btn btn-primary" type="submit">Save</butto n>
<а asp-action="Index" class="Ьtn Ьtn-secondary">Cancel</a>
</div>
</form>
Когда атрибут
asp-validation-for
применяется к элементу
span,
он использу­
ет вспомогательную функцию дескриптора, которая добавляет сообщение об ошибке
проверки достоверности для указанного свойства, если при проверке возникли какие­
то проблемы.
Вспомогательные функции дескрипторов будут вставлять сообщение об ошибке в эле­
мент
span
и добавлять элемент в класс
легко применять стили
Листинг
11.16.
CSS
Добавление стиля
из папки
input-validation-error,
который позволит
к элементам с сообщениями об ошибках (листинг
CSS
в файле
11.16).
_AdminLayout.cshtml
Views/Shared
< 1 DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-wid th" />
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.min.css" />
<title>@ViewBag.Title</t itle>
<style>
.input-validation-error { border-color: red; background-color: #fee
</style>
</head>
322
Часть 1. Введение в инфраструктуру ASP NET Core MVC 2
<body c l ass= "m-1 р-1" >
<d iv cl a s s= "bg-info p - 2 " ><h4>@V ie wBa g.Tit le</h 4 >< /d i v >
@if (TempData ["mess age "] 1 = null) (
<div c l ass= "alert alert -succe ss mt -1 ">@TempDa t a [ "mes sage " ] </ div>
@Re nd e r Body ()
</body>
</ html>
Определенный здесь стиль
са
CSS выбирает элементы ,
i npu t- validation - error ,
которые являютс я членами кла с­
и устанавливает для них границу красного цвета и фон .
Совет. Явная установка стилей, когда используется библиотека
CSS наподобие Bootstrap ,
27
может привести к несоответствиям в случае применения тем содержимого. В главе
будет продемонстрирован альтернативный подход, при котором для применения классов
Bootstrap к элементам с сообщениями об ошибках проверки
JavaScript, сохраняя все в согласованном состоянии.
достоверности используется
код
Вспомогательные функции дескрипторов для сообще ний проверки достоверно с ти
можно применять где угодно в представлении, но по соглашению (что вполне разумно)
принято размещать их поближе к пробл е мному элементу , чтобы вв ести пользователя
в курс дела. На рис. 11 .6 показано, как выглядят сообщения об ошибках пров е рки
достоверности и отобража е мые подсказки. для чего нужно запустить приложение. от ­
редактировать сведения о товаре и отправить недопустимые данны е.
PIP-~
st> '°nter а product 11ame
D~script ion
Please e11ter
а destпptюn
J
1
Category
1llease spe,if) о cakgory
11
Price
PI dse ~n\er а pos111ve pr<ce
1
-200
L
Рис.
11.6.
-
Cancel
Проверка достоверности данных при редактировании сведений о товаре
Глава 11. SportsStore: администрирование
323
Включение проверки достоверности на стороне клиента
В текущий момент проверка достоверности данных применяется, только когда
пользователь-администратор
отправляет
результаты
редактирования
серверу.
но
большинство пользователей ожидают немедленного отклика при наличии проблем с
введенными данными. Именно потому разработчики часто предпочитают выполнять
проверку достоверности на стороне клиента, при которой данные проверяются в
браузере с использованием
JavaScript.
Приложения МVС могут выполнять провер­
ку достоверности на стороне клиента на основе аннотаций данных, применяемых к
классу модели предметной области.
Прежде всего, понадобится добавить библиотеки
JavaScript,
которые предоставят
приложению средство проверки достоверности на стороне клиента,
файле
bower. j son
Листинг
11.17.
(листинг
11.17).
Добавление пакетов
из папки
что делается в
JavaScript
SportsStore
в файле
bower. j son
"name 11 : "asp.net",
"private": true,
"dependencies" : {
"bootstrap": "4.0.0-alpha.6",
"fontawesome": "4.7.0",
"jquery": "3.2.1",
"jquery-validation": "1.17. 0",
"jquery-validation-unobtr usive": "3.2.6"
Проверка достоверности на стороне клиента построена на основе популярной
библиотеки
jQuery,
которая упрощает работу с АРI-интерфейсом
Следующий шаг связан с добавлением файлов
JavaScript в
DOM
гружались, когда используются средства администрирования приложения
(листинг
Листинг
браузера.
компоновку. чтобы они за­
SportsStore
11. 18).
11. 18.
Добавление библиотек проверки достоверности
в файле
_Ad.minLayout. cshtml
из папки
Views/Shared
< 1 DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-wid th" />
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.min.css" />
<title>@ViewBag.Title</t itle>
<style>
.input-validation-error { border-color: red; background-color: #fee
</style>
<script src="/lib/jquery/dist/jque ry.min.js"></script>
<script src="/lib/jquery-validati on/dist/jquery.validate.m in.js">
</script>
<script
src="/liЬ/jquery-validation-unobtrusive/jquery.validate.unoЬtrusive.min.js">
</script>
</head>
324
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
<body class="m-l р-1">
<div class="bg-info p-2"><h4>@ViewBag.Title</h4></div>
@if (TempData["message"] != null) {
<div class="alert alert-success mt-1 ">@TempDat.a [ "message"] </div>
@RenderBody ()
</body>
</html>
Включение проверки достоверности на стороне клиента не приводит к каким-то
визуальным изменениям. Но ограничения. которые указаны с помощью атрибутов.
примененных к классу модели С#, вступают в силу на уровне браузера, предотвращая
отправку пользователем формы с недопустимыми данными и обеспечивая немедлен­
ный отклик при наличии проблемы. За дополнительными сведениями обращайтесь
в главу
27.
Создание новых товаров
Далее мы реализуем метод действия
Product
Crea te (),
который указан для кнопки
Add
на главной странице со списком товаров. Он позволит администратору добав­
лять новые элементы в каталог товаров. Добавление возможности создания новых това­
ров требует одного небольшого дополнения в приложении, что является великолепной
демонстрацией мощи и гибкости хорошо структурированного приложения
чала добавим в контроллер
Листинг
11.19.
Adrnin
метод
Crea te () ,
Добавление метода действия
в файле
Crea te ()
AdminController. cs из папки Controllers
using Microsoft.AspNetCore.Mvc;
using SportsStore.Models;
using System.Linq;
namespace SportsStore.Controllers
puЫic class AdminController : Controller
private IProductRepository repository;
puЬlic AdminController(IProductRepository repo) {
repository = repo;
puЬlic
ViewResult Index() => View(repository.Products);
ViewResult Edit(int productid) =>
View(repository.Products
.FirstOrDefault(p => p.ProductID == productid));
puЫic
[HttpPost]
IActionResult Edit(Product product)
if (ModelState. IsValid) {
repository.SaveProduct(product);
TempData["message"J = $"{product.Namef has been saved";
return RedirectToAction("Index");
else {
puЬlic
11
Что-то не так со значениями данных
return View(product);
MVC. Для
11.19.
как показано в листинге
на­
Глава 11. SportsStore: администрирование
puЬlic
=> View ( "Edi t", new Product ()) ;
ViewResul t Create ()
Crea te ( )
Метод
325
не визуализирует свое стандартное представление . Взамен он
указывает, что должно использоваться представление
Edi t.
Применение в методе
действия представления. которое обычно связано с другим методом действия, впол­
не допустимо . В рассматриваемом случае мы указываем в качестве модели представ ­
ления новый объект
Product.
так что представление
заполняется пустыми
Edi t
полями.
На заметку! Модульный тест для метода действия
Crea t
е
()
не добавляется. Он позволил
бы проверить только способность обработки инфраструктурой
тата, возвращаемого методом действия
-
ASP.NET Core MVC
резуль­
то, что мы считаем само собой разумеющим­
ся. (Обычно тесты для функциональных средств инфраструктуры не пишутся, если только
нет подозрения о наличии дефекта.)
Это единственное изменение. которое потребовалось внести. поскольку метод
действия
Edi t ()
уже настроен на получение объектов
Product
от системы привязки
моделей и на их сохранение в базе данных. Чтобы протестировать имеющуюся фун ­
кциональность. запустим приложение , перейдем на
нем на кнопке
Add Product,
URL
вида
/ Adrni n/Index,
щелк­
заполним форму и отправим ее. Информация, введенная
на форме, будет использоваться для создания нового товара в базе данных, который
затем появится в списке (рис.
11. 7).
Description
Breгithe
10 Nam•
underwater
Price
S275.00
1 Green Kayak
1 Cate901y
Wate1'Sports
Price
2
Lifejack~t
З
Soccer Ball
S19.SO
4 Corner Ffags
SЗ4.95
S Stadium
10 50
S48.95
579.500.00
6 Thi11kin9 Сар
"
1
$16.00
Unsteьdy Cha1r
52995
8 Human Ch~s Шrd
9 Bling·Bling King
S7S.OO
SJ.200.00
10 Snorkel
51050
*0111
Рис.
11. 7.
Добавление нового товара в каталог
Actio.ns
326
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
Удаление товаров
Обеспечить поддержку удаления элементов из каталога довольно просто. Для на­
чала добавим в интерфейс
тинге
IProductRepository
новый метод, как показано в лис­
11.20.
Листинг
11.20.
Добавление метода для удаления товаров в файле
IProductReposi tory. cs
из папки
Models
using System.Linq;
namespace SportsStore.Models
puЫic
interface IProductRepository
IQueryaЬle<Product>
Products { get;
void SaveProduct(Product product);
Product DeleteProduct (int productID) ;
Затем реализуем метод
Core,
DeleteProduct () в классе
EFProductRepository (листинг 11.21).
т.е.
Листинг
11.21.
хранилища
Реализация поддержки удаления в файле
из папки
Entity Framework
EFProductReposi tory. cs
Model s
using System.Collections.Generic;
using System.Linq;
namespace SportsStore.Models (
class EFProductRepository : IProductRepository (
private ApplicationDbContext context;
puЫic
EFProductRepository(ApplicationDbContext ctx) (
context = ctx;
puЬlic
puЬlic
IQueryaЫe<Product>
Products => context.Products;
void SaveProduct(Product product)
i f ( product. Product I D == О) (
context.Products.Add(product);
else (
Product dbEntry = context.Products
.FirstOrDefault(p => p.ProductID
product.ProductID);
if (dbEntry != null) {
dbEntry.Name = product.Name;
dbEntry.Description = product.Description;
dbEntry.Price = product.Price;
dbEntry.Category = product.Category;
puЫic
context.SaveChanges();
Глава 11. SportsStore: администрирование
327
Product DeleteProduct(int productID) (
Product dЬEntry = context. Products
.FirstOrDefault(p => p.ProductID == productID);
puЫic
(dЬEntry
if
!= null) (
context.Products.Reшove(dЬEntry);
context.SaveChanges();
return
dЬEntry;
Финальный шаг связан с реализацией метода действия
Admin.
Метод действия
Delete ()
Delete ()
в контроллере
должен поддерживать только запросы
POST,
пото­
му что удаление объектов не является идемпотентной операцией. Как будет показа­
но в главе
16,
браузеры и кеши вольны выдавать запросы
GET
без явного согласия
пользователя, поэтому мы должны проявить осторожность, чтобы избежать внесе­
ния изменений как следствия запросов
листинге
Листинг
GET.
Код нового метода действия приведен в
11.22.
11.22. Добавление метода действия Delete () в файле
AdminController.cs
из папки
Controllers
using Microsoft.AspNetCore.Mvc;
using SportsStore.Models;
using System.Linq;
namespace SportsStore.Controllers
puЫic
class AdminController : Controller
private IProductRepository repository;
AdminController(IProductRepository repo) {
repository = repo;
puЫic
puЫic
ViewResult Index() => View(repository.Products);
ViewResult Edit(int productid) =>
View(repository.Products
.FirstOrDefault(p => p.ProductID == productid));
puЫic
[HttpPost]
IActionResult Edit(Product product)
puЫic
if (ModelState. IsValid) {
repository.SaveProduct(product);
TempData["message"] = $"{product.Name) has been saved";
retur·n RedirectToAction ( "Index");
else {
11
Что-то не так со значениями данных
return View(product);
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
328
puЫic
IActionResult Create() => View("Edit", new Product());
[HttpPost]
IActionResult Delete (int productid)
Product deletedProduct = repository.DeleteProduct(productid);
if (deletedProduct != null) {
TempData["message"] = $ 11 {deletedProduct.Name} was deleted";
puЫic
return RedirectToAction (" Index") ;
Модульное тестирование: удаление товаров
Нам нужно протестировать основное поведение метода действия
Delete (),
которое за­
ключается в том, что при передаче в качестве параметра допустимого идентификатора
ProductID
метод действия должен вызвать метод
редать ему корректное значение Р roduct I
файл
DeleteProduct () хранилища и пе­
D удаляемого товара. Вот тест, добавленный в
AdminControllerTests. cs:
[ Fact]
void Can Delete Valid Products() (
11 Организация - создание объекта Product
Product prod = new Product { ProductID = 2, Name = "Test" } ;
puЫic
11 Организация - создание имитированного хранилища
Mock<IProductRepository> mock = new Mock<IProductRepository>();
mock.Setup(m => m.Products) .Returns( (new Product[) {
1, Name
"Pl"},
new Product (ProductID
prod,
"РЗ"},
new Product (ProductID
3, Name
)) .AsQueryaЫe<Product>());
//Организация
-
создание контроллера
AdminController target = new AdminController(mock.Object);
11 Действие - удаление товара
target.Delete(prod.ProductID);
//
Утверждение
-
проверка того,
что был вызван метод удаления
11 в хранилище с корректным объектом Product
mock.Verify(m => m.DeleteProduct(prod.ProductID));
Чтобы увидеть средство удаления в работе, запустим приложение, перейдем на
URL
вида
/Admin/ Index
и щелкнем на одной из кнопок
ницы со списком товаров (рис.
ременной
TempData
l l .8).
Delete
(Удалить) внутри стра­
На рисунке можно заметить, что с помощью пе­
отображается сообщение об удалении товара из каталога.
Глава 11 . SportsStore : администрирование
~ All Produc11
329
х
All Products
10
Nomo
Snon:et ""as deletcd
S275.00
1 Green l<ayak
2 Lilejacket
$48.95
3 Soccer Boll
S19.50
Prict
Actlons
5275.00
1 Green К.уаk
S34.95
4 Corner Flags
2 Lifej•cket
$4895
3 Soccer в.11
S19.50
$79 50000
5 Su.dium
6 Th1nk1n9 Сар
S16.00
7 Unsteady Choir
S29.95
534.95
4 Corner Flags
S79.S00.00
S7S.OO
8 Human Chess Bcard
S16.00
Sl.200.00
9 Bl1ng·Bling King
52995
$10.50
10 Snorkel
$75.00
8 Human Chess 8oard
•111u
$1,lOO.OO
9 Bkng·Bling K1ng
*ФО#*
Рис.
11.8.
Удаление товара из каталога
На заметку! При попытке удалить товар , для которого ранее был создан заказ, возникнет
ошибка. Когда объект
Or de r
сохраняется в базе данных , он превращается в запись внут­
ри таблицы базы данных, которая содержит ссылку на связанный с ним объект
Produ ct ,
что известно как отношение внешнего ключа. Это означает, что по умолчанию база дан ­
ных не позволит удалить объект
Pro d uc t,
если для него был создан объект
Order,
поскольку такое действие внесло бы несогласованность в базу данных . Существует не­
Ord e r при
Pr odu ct , к которому они относятся , либо изменение отношения между
Prod uct и Order. Подробные сведения можно найти в документации Eпtity
сколько способов решения указанной проблемы, включая удаление объектов
удалении объекта
объектами
Framework Core.
Резюме
В главе были введены средства администрирования и показано. как реализовать
операции
CRUD.
которы е позволяют администратору со здавать. читать , обновлять
и удалять т ова ры из хранил ища и пом ечать заказы ка к отгруже нны е . В следующей
главе мы продем он с трируе м с п о соб з а щиты админи ст р ативных функций. чтобы
они не были доступными абсолютно всем пользовател ям, и разве рн ем приложение
SportsStore
в производственной среде.
ГЛАВА
12
SportsStore:
защита и развертывание
в предыдущей главе мы добавили в приложение SportsStoгe поддержку админис­
трирования. и от вашего внимания, вероятно, не ускользнул тот факт, что если
развернуть приложение в том виде как есть, то модифицировать каталог товаров смо­
жет любой пользователь. Для этого ему лишь нужно знать. что средства администри­
рования доступны через
URL вида /Admin/ Index
и
/Order /List.
В настоящей главе
мы покажем, как предотвратить использование административных функций случай­
ными посетителями, защитив их паролем. После добавления возможности защиты
мы объясним, каким образом подготовить и развернуть приложение SpoгtsStoгe в
производственной среде.
Защита средств администрирования
ASP.NET Саге Jdeпtity.
ASP.NET Саге, так и в приложения
Аутентификация и авторизация предоставляются системой
которая аккуратно интегрируется как в платформу
MVC.
В последующих разделах мы создадим базовую настройку защиты, которая поз­
волит одному пользователю по имени
Admin
проходить аутентификацию и получать
доступ к административным функциям в приложении. Система
ASP.NET Саге
Ideпtity
предлагает множество других средств для аутентификации пользователей, а также
авторизации доступа к функциям и данным приложения. Более подробные сведения
вы найдете в главах
28-30,
где будет показано, как создавать и управлять пользова­
тельскими учетными записями, каким образом применять роли и политики и как
поддерживать аутентификацию от третьих сторон вроде
и
Twitter.
Однако цель текущей главы
-
Microsoft, Google, Facebook
создать лишь столько функциональности.
сколько достаточно для предотвращения доступа пользователей к чувствительным
частям приложения
SportsStore,
что будет содействовать пониманию того, каким об­
разом аутентификация и авторизация вписываются в приложение
Создание базы данных
Система
ASP.NET Core
MVC.
Iden ti ty
Jdeпtity чрезвычайно конфигурируема и расширяема, под­
держивая многочисленные варианты хранения данных о пользователях. Мы собира­
емся использовать наиболее распространенный вариант, который предусматривает
хранение данных с применением
Eпtity
Framework Core.
Microsoft SQL Server
и доступ к ним с помощью
331
Глава 12. SportsStore: защита и развертывание
Создание класса контекста
Нам необходимо создать файл контекста базы данных, который будет действовать
в качестве шлюза между базой данных и объектами моделей базы данных
предоставляющими к ней доступ. Добавим в папку
Appldenti tyDbContext. cs
Models
Identi ty,
файл класса по имени
с определением, приведенным в листинге
12. l.
На заметку! Вы могли привыкнуть к тому, что для получения дополнительных функциональных
средств вроде защиты к проекту необходимо добавлять пакеты. Но с выпуском
Core 2 пакеты NuGet,
пакет, который был добавлен в файл
Листинг
12.1.
ASP.NET
требующиеся для системы ldeпtity, уже включены в проект через мета­
Содержимое файла
SportsStore. csproj
как часть шаблона проекта.
AppidentityDbContext.cs
из папки
Models
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore ;
using Microsoft.EntityFrameworkCore;
namespace SportsStore.Models {
puЫic class AppidentityDbContext : IdentityDbContext<IdentityUser> {
puЫic AppidentityDbContext(DbContextOptions<AppidentityDbCo ntext> options)
: base(options) { )
Класс
Appldenti tyDbContext является производным от класса Identi tyDbContext,
который предлагает связанные с
раметре типа используется
ldentity
средства для
Identi tyuser -
представления пользователей. В главе
28
Entity Framework Core.
В па­
встроенный класс, применяемый для
будет продемонстрировано использование
специального класса, который можно расширять с целью добавления дополнительной
информации о пользователях приложения.
Определение строки подключения
Далее определяется строка подключения, предназначенная для базы данных. В лис­
тинге
12.2
показано добавление в файл
appsettings. j son
проекта
SportsStore,
которое следует тому же самому формату. что и строка подключения, определенная
для базы данных товаров в главе
Листинг
12.2.
8.
Определение строки подключения в файле
из папки
appsettings. json
SportsStore
"Data": {
"SportStoreProducts":
"ConnectionString":
"Server=(localdb)\\MSSQLLocalDB;Database=SportsS tore;
Trusted_Connection=True;MultipleActiveResultSets =true"
)
'
"SportStoreidenti ty" : {
"ConnectionStrinq": "Server=(localdЬ)\\МSSQLLocalDB;DataЬase=Identity;
Trusted_Connection=True;МultipleActiveResultSets=true"
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
332
Вспомните, что строка подключения должна определяться в файле
j son
appsettings.
как единственная неразрывная строка кода. а в листинге она выглядит так из-за
ограниченной ширины печатной страницы. Добавленная разметка определяет строку
подключения по имени
Loca!DB
под названием
SportsStoreidenti ty,
Identi ty.
в которой указывается база данных
Конфигурирование приложения
Подобно другим средствам
ASP.NET Core система ldentity конфигурируется в
Start. В листинге 12.3 приведены добавления для настройки Identity в про­
SportsStore с применением ранее определенного класса контекста и строки
классе
екте
подключения.
Листинг 12.З. Конфигурирование средства ldeпtity в файле
из папки
using
using
using
using
using
using
using
using
using
using
using
using
Startup. cs
SportsStore
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
SportsStore.Models;
Microsoft.Extensions.Configuration;
Microsoft.EntityframeworkCore;
Мicrosoft.AspNetCore.Identity;
namespace SportsStore {
puЬlic
class Startup {
Startup(IConfiguration configuration) =>
Configuration = configuration;
puЬlic
puЫic
IConfiguration Configuration { get; )
void ConfigureServices(IServiceCollection services)
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration["Data:SportStoreProducts:ConnectionString"J));
puЫic
services.AddDbContext<AppidentityDbContext>(options =>
options.UseSqlServer(
Configuration["Data:SportStoreidentity:ConnectionString"]));
services.Addidentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<AppidentityDbContext>()
.AddDefaultTokenProviders();
services.AddTransient<IProductRepository, EfProductRepository>();
services.AddScoped<Cart>(sp => SessionCart.GetCart(sp));
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IOrderRepository, EfOrderRepository>();
services.AddMvc();
services.AddMemoryCache();
services.AddSession();
333
Глава 12. SportsStore: защита и развертывание
void Configure(IApplicationBuilder
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseSession();
app.UseAuthentication();
app.UseMvc(routes => {
puЫic
11 ... для
арр,
IHostingEnvironment env) {
краткости маршруты не показаны ...
) ) ;
SeedData.EnsurePopulated(app);
В методе
конфигурация
ConfigureServices ()
Entity
для регистрации класса контекста и с помощью метода
вает службы
Identity.
Fгamewoгk Соге расширена
Addidenti ty ()
устанавли­
используя встроенные классы для представления пользователей
и ролей. Внутри метода
Configure ()
вызывается метод
UseAuthentication ()
для
установки компонентов, которые будут перехватывать запросы и ответы для внедре­
ния политики безопасности.
Соэдание и применение миграции базы данных
Основная конфигурация на месте, и самое время воспользоваться средством миг­
раций
Entlty
Fгamewoгk Соге для определения схемы и ее применения к базе данных.
Откроем окно командной строки или PoweгShell и введем показанную ниже коман­
д.У в папке проекта
SportsStore,
чтобы создать новую миграцию для базы данных
Identity:
dotnet ef migrations add Initial --context AppidentityDbContext
Существенное отличие от предшествующих команд базы данных связано с
тем. что здесь используется аргумент
--context
текста, ассоциированного с базой данных,
AppidentityDbContext.
для указания имени класса кон­
с которой необходимо работать
-
При наличии множества баз данных в приложении важно
гарантировать работу с правильным классом контекста.
После того как инфраструктура
Entlty
Fгamewoгk Соге сгенерировала начальную
миграцию, выполним следующую команду. чтобы создать базу данных и запустить
команды миграции:
dotnet ef database update --context AppidentityDbContext
Результатом будет новая база данных
LocalDB
по имени
но просмотреть с применением проводника объектов
Ехрlогег) среды
SQL
Identity,
Serveг
которую мож­
(SQL
Serveг
Object
Visual Studio.
Определение начальных данных
Мы планируем явно создать пользователя
Admin,
наполняя базу данных началь­
ными данными при запуске приложения. Добавим в папку
имени
IdentitySeedData. cs
Models
файл класса по
и определим в нем статический класс (листинг
12.4).
334
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Листинг
12.4.
Содержимое файла
Iden ti tySeedDa ta. cs
из папки
Model s
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Dependencyinjection;
namespace SportsStore.Models {
puЫic
static class IdentitySeedData
private const string adminUser = "Admin";
private const string adminPassword = "Secret123$";
puЫic
static async void EnsurePopulated(IApplicationBuilder
арр)
UserManager<IdentityUser> userManager = app.ApplicationServices
.GetRequiredService<UserManager<IdentityUser>>();
IdentityUser user = await userManager.FindByidAsync(adminUser);
if (user == null) {
user = new IdentityUser("Admin");
await userManager.CreateAsync(user, adminPassword);
В коде используется класс
UserManager<T>.
который система ASP.NET Core
Identity
предоставляет в виде службы для управления пользователями, как обсуждается в гла­
ве
28.
В базе данных производится поиск учетной записи пользователя
рая в случае ее отсутствия создается (с паролем
Secret123$).
закодированный пароль в этом примере, поскольку система
Admin,
кото­
Не изменяйте жестко
Identity
имеет политику
проверки достоверности, которая требует, чтобы пароли содержали цифры и диапа­
зон символов. Способ изменения настроек. относящихся к проверке достоверности.
описан в главе
28.
Внимание! Жесткое кодирование деталей учетной записи администратора часто требуется
для того, чтобы можно было войти в приложение после его развертывания и начать ад­
министрирование. Поступая так, вы должны помнить о необходимости изменения пароля
для учетной записи, которую создали. В главе
28 приведены детали того, как изменять
пароли, используя ldeпtity.
Чтобы обеспечить начальное заполнение базы данных
приложения. добавим в метод
в листинге
Листинг
класса
I den t i t у во время запуска
Startup оператор. как показано
12.5.
12.5.
Начальное заполнение базы данных
из папки
puЫic
Configure ()
Identi ty
в файле
Startup. cs
SportsStore
void Configure(IApplicationBuilder
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseSession();
арр,
IHostingEnvironment env) {
Глава 12. SportsStore: защита и развертывание
335
app.UseAuthentication();
app.UseMvc(routes => (
11 ... для
краткости маршруты не показаны ...
) ) ;
SeedData.EnsurePopulated (app);
IdentitySeedData.EnsurePo pulated(app);
Применение базовой политики авторизации
Теперь, когда система
ASP.NET Core Identity
установлена и сконфигурирована,
можно применить политику авторизации к тем частям приложения. которые необхо­
димо защитить. Мы собираемся использовать самую базовую политику авторизации.
которая предусматривает разрешение доступа любому пользователю, прошедшему
аутентификацию. Хотя она может оказаться полезной политикой также и в реаль­
ном приложении, существуют возможности для создания более детализированных
элементов управления авторизацией (как описано в главах
в приложении
SportsStore
28-30),
но из-за того, что
имеется только один пользователь, вполне достаточно про­
вести различие между анонимными и аутентифицированными запросами.
Атрибут
в листинге
Authorize применяется для ограничения доступа к методам действий, и
12.6 видно,
что этот атрибут используется для защиты доступа к админис­
тративным действиям в контроллере
Order.
Листинг 12.б. Ограничение доступа в файле
из папки
OrderController. cs
Controllers
using Microsoft.AspNetCore.Mvc ;
using SportsStore.Models;
using System.Linq;
using Мicrosoft.AspNetCore.Authorization;
namespace SportsStore.Controllers (
class OrderController : Controller
private IOrderRepository repository;
private Cart cart;
puЫic
OrderController(IOrderRe pository repoService,
Cart cartService) (
repository = repoService;
cart = cartService;
puЫic
[Authorize]
ViewResult List() =>
View(repository.Orders.W here(o =>
puЫic
1 o.Shipped)
[HttpPost]
[Authorize]
IActionResult MarkShipped(int orderID) (
Order order = repository.Orders
.FirstOrDefault(o => o.OrderID == orderID);
puЫic
);
336
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
if (order ! = null) {
order.Shipped = true;
repository.SaveOrder(order);
return RedirectToAction(nameof(List));
puЫic
ViewResult Checkout() => View(new Order());
[HttpPost]
puЫic IActionResult Checkout(Order order) {
if (cart.Lines.Count() == 0) {
ModelState.AddModelError("", "Sorry, your cart is empty!");
if (ModelState. IsValid) {
order.Lines = cart.Lines.ToArray();
repository.SaveOrder(order);
return RedirectToAction(nameof(Completed));
else {
return View(order);
puЫic
ViewResult Completed() {
cart.Clear();
return View();
Мы не хотим препятствовать доступу пользователей, не прошедших аутенти­
фикацию, к остальным методам действий в контроллере
Authorize
применен только к методам
тить все методы действий. определяемые
счет применения атрибута
Authorize
Order, так что атрибут
MarkShipped ().Нам нужно защи­
контроллером Admin, чего можно достичь за
List ()
и
к самому классу контроллера, что приведет к
применению политики авторизации ко всем содержащимся в нем методам действий
(листинг
Листинг
12.7).
12. 7.
Ограничение доступа в файле
из папки
AdminCon troller. cs
Controllers
using Microsoft.AspNetCore.Mvc;
using SportsStore.Models;
using System.Linq;
using
Мicrosoft.AspNetCore.Authorization;
namespace SportsStore.Controllers {
[Authorize]
puЫic
class AdminController : Controller
private IProductRepository repository;
puЫic
AdminController(IProductRepository repo) {
repository = repo;
Глава 12. SportsStore: защита и развертывание
puЬlic
337
ViewResult Index() => View(repository.Products);
ViewResult Edit(int productid) =>
View(repository.Products
. FirstOrDefaul t (р => р. ProductID == productid) ) ;
puЫic
[HttpPost]
IActionResult Edit(Product product)
i f (ModelState. IsValid) (
repository.SaveProduct(product);
TempData["message"] = $"(product.Name} has been saved";
return RedirectToAction("Index");
else (
puЬlic
//Что-то не так со значениями данных
return View(product);
puЬlic
ViewResult Create() => View("Edit", new Product());
[HttpPost]
puЬlic IActionResult Delete(int productid)
Product deletedProduct = repository.DeleteProduct(productid);
if (deletedProduct != null) (
TempData["message"] = $"(deletedProduct.Name} was deleted";
return RedirectToAction("Index");
Создание контроллера
Accoun t
и представлений
Когда пользователь, не прошедший аутентификацию, посылает запрос, который
требует авторизации, он перенаправляется на
URL вида /Account/Login,
где прило­
жение может предложить пользователю ввести свои учетные данные. В качестве под­
готовки создадим модель представления для учетных данных пользователя, добавив
в папку
Models/ViewModels
показанным в листинге
Листинг
12.8.
файл класса по имени
LoginModel. cs
Содержимое файла LoginМodel.
cs
из папки
using System.ComponentModel.DataAnnotations;
namespace SportsStore.Models.ViewModels
puЫic
class LoginModel (
[Required]
puЫic string Name ( get; set; }
[Required]
[UIHint("password")]
puЫic string Password ( get; set; }
puЫic
с определением,
12.8.
string ReturnUrl ( get; set; }
"/";
Models/ViewModels
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
338
Свойства
Name
и
Password
декорированы атрибугом
Required,
который исполь­
зует проверку достоверности модели для обеспечения того, что значения были пре­
доставлены. Свойство
применения атрибута
Password декорировано атрибутом UIHint, поэтому в случае
asp-for внутри элемента input представления Razoг, пред­
назначенного для входа. вспомогательная функция дескриптора установит атрибут
type
в
password;
таким образом, вводимый пользователем текст не будет виден на
экране. Использование атрибуга
Далее добавим в папку
UIHint описано в главе 24.
Controllers файл класса по имени AccountController. cs
с определением контроллера. приведенным в листинге
дет отвечать на запросы к
Листинг
using
using
using
using
using
using
12.9.
12.9.
Данный контроллер бу­
URL вида /Account/Login.
Содержимое файла
AccountController. cs
из папки
Controllers
System.Threading.Tasks;
Microsoft.AspNetCore.Authorization;
Microsoft.AspNetCore.Identity;
Microsoft.AspNetCore.Identity.EntityFrameworkCore;
Microsoft.AspNetCore.Mvc;
SportsStore.Models.ViewModels;
namespace SportsStore.Controllers
[Authorize]
class AccountController : Controller
private UserManager<IdentityUser> userManager;
private SigninManager<IdentityUser> signinManager;
puЬlic
AccountController(UserManager<IdentityUser> userMgr,
SigninManager<IdentityUser> signinMgr) {
userManager = userMgr;
signinManager = signinMgr;
puЫic
[AllowAnonymous)
ViewResult Login(string returnUrl) {
return View(new LoginModel
ReturnUrl = returnUrl
puЫic
} ) ;
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken)
puЫic async Task<IActionResult> Login(LoginModel loginModel) {
if (ModelState. IsValid) {
IdentityUser user =
await userManager.FindByNameAsync(loginModel.Name);
if (user '= null) {
await signinManager.SignOutAsync();
if ( (await signinManager.PasswordSigninAsync(user,
loginModel.Password, false, false)) .Succeeded)
return Redirect(loginModel?.ReturnUrl ?? "/Admin/Index");
Глава 12. SportsStore: защита и развертывание
339
ModelState.AddModelError ("", "Invalid name or password");
11
Неправильное имя или пароль
return View(loginModel);
"/") {
async Task<RedirectResult> Logout(string returnUrl
await signinManager.SignOutAsy nc();
return Redirect(returnUrl);
puЫic
Когда пользователь перенаправляется на
метода действия
Login ()
URL
вида
/Account/Login,
версия
GET
визуализирует стандартное представление для страницы и
создает объект модели представления, включающий
URL,
на который браузер должен
быть перенаправлен, если запрос на аутентификацию завершился успешно.
Учетные данные аутентификации отправляются версии
РО S т
метода дейс­
Login (), которая применяет службы UserManager<Identi tyUser> и
SigninManager<Identi tyUser>, полученные через конструктор класса контрол­
твия
лера, для аутентификации пользователя и его входа в систему. Работа упомянутых
классов объясняется в главах 28-30, а пока достаточно знать, что в случае отказа в
аутентификации создается ошибка проверки достоверности модели и визуализиру­
ется стандартное представление. Если же аутентификация прошла успешно, тогда
пользователь перенаправляется на
URL,
к которому он хотел получить доступ перед
тем, как ему было предложено ввести свои учетные данные.
Внимание! В целом использование проверки достоверности данных на стороне клиента яв­
ляется хорошей практикой. Она освобождает от определенной работы сервер и обеспе­
чивает пользователям немедленный отклик о предоставленных ими данных. Тем не менее,
не поддавайтесь искушению выполнять на стороне клиента аутентификацию, поскольку
обычно это предусматривает передачу клиенту допустимых учетных данных, которые бу­
дут применяться при проверке вводимых имени пользователя и пароля, или, по меньшей
мере, наличие доверия сообщению клиента о том, что аутентификация завершилась ус­
пешно. Аутентификация должна всегда выполняться на сервере.
Чтобы снабдить метод
Views/Account
Login ()
представлением для визуализации, создадим папку
и поместим в нее файл представления Razoг по имени
с содержимым, показанным в листинге
Листинг
12.10.
Содержимое файла
Login. cshtml
12.10.
Login.cshtm.l
из папки
Views/Account
@model LoginModel
@{
ViewBag.Title = "Log In";
Layout =" AdminLayout";
<div class="text-danger" asp-validation-summary="A ll"></div>
<form asp-action="Login" asp-controller="Account" method="post">
<input type="hidden" asp-for="ReturnUrl" />
<div class="form-group">
<label asp-for="Name"></label>
<div><span asp-validation-for="Name" class="text-danger"></span ></div>
<input asp-for="Name" class="form-control" />
</div>
340
Часть 1. Введение в инфраструктуру ASP.NET Соге MVC 2
<div class="form-group">
<label asp-for="Password"></label>
<div><span asp-validation-for="Password" class="text-danger"></span>
</div>
<input asp-for="Password" class="form-control" />
</div>
<button class="btn btn-primary" type="submit">Log In</button>
</form>
Финальный шаг связан с изменением разделяемой компоновки для админис­
трирования, чтобы добавить кнопку
Log Out
(Выход), которая позволит текущему
пользователю выходить из приложения за счет отправки запроса действию
(листинг
12.11).
Logou t
Это удобное средство, облегчающее тестирование приложения, без
которого пришлось бы очищать сооkiе-наборы браузера, чтобы возвращаться в состо­
яние, когда аутентификация еще не прошла.
Листинг
12.11.
Добавление кнопки
из папки
Log Out
Views/Shared
в файле_AdminLayout.
cshtml
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.min.css" />
<title>@ViewBag.Title</title>
<style>
.input-validation-error {
border-color: red;
background-color: #fee;
</style>
<script src="/liЬ/jquery/dist/jquery.min.js"></script>
<script src="/liЬ/jquery-validation/dist/jquery.validate.min.js">
</script>
<script
src=
"/liЬ/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js">
</script>
</head>
<body class="m-1 р-1">
<div class="bg-inf'o р-2 row">
<div class="col">
<h4>@ViewBag.Title</h4>
</div>
<div class="col-2">
<а class="btn btn-sm Ьtn-primary"
asp-action="Logout" asp-controller="Account">Log Out</a>
</div>
</div>
@if (TempData["message"] != null)
<div class="a1ert alert-success mt-l">@TempData["message"]</div>
@RenderBody ()
</body>
</html>
Глава 12. SportsStore : защита и развертывание
341
Тестирование политики безопасности
Теперь можно протестировать политику безопасности, запустив приложение и
запросив
URL
вида
/ Admin/Index .
Поскольку в настоящий момент мы еще не про­
шли аутентификацию и пытаемся обратиться к действию, которое требует автори­
зации, браузер будет перенаправлен на
и
Secret123$
URL
вида
/ Account/Login.
Введем
в качестве имени и пароля и отправим форму. Контроллер
Admin
Account
сравнит предоставленные учетные данные с начальными данными . добавленными в
базу данных
I den t i t у,
и (при условии ввода правильных сведений) аутентифицирует
нас. после чего перенаправит обратно на
ступ. Процесс проиллюстрирован tia рис.
/ Accoun t /I,og in.
12.1.
$275.00
1 Green Kayak
2 Lifejacket
$48.95
3 5occer Ball
$19.50
4 Comer Fl•gs
J
Рис.
12.1.
куда теперь имеется до­
$34.95
5 Stadium
б
$79.500.00
.,.,
1hinking Сар
S16.00
,,. ~
Процесс аутентификации/авторизации для административных функций
Развертывание приложения
Все средства и функциональность приложения
SportsStore
на месте . так что на­
ступил момент для его подготовки и развертывания в производственной среде. В от­
ASP.NET Саге MVC доступно множество вариантов размещения .
- платформа Microsoft Azure. Она вы­
брана из-за того. что поступает от Microsoft и предлагает бесплатные учетные записи ,
т.е. вы можете полностью проработать пример SportsStore. даже если не хотите при­
менять Azure в собственных проектах.
ношении приложений
и в настоящей главе используется один из них
На заметку! В данном разделе понадобится учетная запись
можете создать бесплатную учетную за п ись на
Azure. Если у вас ее пока нет. тогда
h t tps : / / a z ure . mi c rosof t. с от/.
Создание баз данных
Начальной задачей является создание баз данных. которые приложение
SportsStore
будет использовать в производственной среде . Такую задачу можно выполнять как
часть процесса разработки в
Visua l Studio.
но трудность ситуации в том. что нужно
342
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
знать строки подключений для баз данных до развертывания, а это относится к про­
цессу. который создает базы данных.
Внимание! Портал
Azure
часто меняется по мере того, как в
Microsoft
добавляют новые
средства и пересматривают существующие. Инструкции, приводимые в настоящем раз­
деле, были точными на время его написания, но к моменту выхода книги необходимые
шаги могут слегка измениться. Базовый подход должен остаться таким же, но имена по­
лей данных и точный порядок шагов может потребовать определенного экспериментиро­
вания, чтобы добиться правильных результатов.
Самый простой подход предусматривает вход на портал
com
https: / /portal. azure.
Azure и создание баз данных вручную.
SQL Databases (Базы данных SQL) и щелк­
с применением своей учетной записи
После входа выберем категорию ресурсов
нем на кнопке
Add
(Добавить), чтобы создать новую базу данных.
Для первой базы данных укажем имя
products. Щелкнем на ссылке Configure
Required Settings (Конфигурировать обязательные настройки) и затем на ссылке Create
а New Server (Создать новый сервер). Введем имя нового сервера, которое должно быть
уникальным в рамках Azure, и выберем имя пользователя и пароль для администрато­
ра. В рассматриваемом примере было указано имя сервера sportsstorecore2dЬ, имя
пользователя
sportsstoreadmin
и пароль
Вам понадобится использо­
Secret123$.
вать другое имя сервера и сформировать более надежный пароль. Выберем местопо­
ложение для базы данных и щелкнем на кнопке ОК, чтобы закрыть экран параметров.
и далее на кнопке
Create
(Создать), чтобы создать саму базу данных. Порталу
Azure
потребуется несколько минут для выполнения процесса создания, после чего база
данных появится в категории ресурсов
SQL Databases.
SQL. указав
Создадим еще один сервер баз данных
на этот раз имя
identity.
Вместо создания нового сервера баз данных можно применять тот, который был со­
здан ранее. Результатом окажутся две базы данных
детали которых приведены в табл.
12. l.
SQL Server,
размещаемые
Azure,
У вас будут другие имена серверов баз дан­
ных и наверняка более надежные пароли.
Таблица
12. 1.
Базы данных
Azure
дпя приложения
SportsStore
Имя пользователя-
Имя базы данных
Имя сервера
products
sportsstorecore2dЬ
sportsstoreadmin
Secretl23$
identity
sportsstorecore2dЬ
sportsstoreadmin
Secretl23$
Пароль
администратора
Открь1тие доступа в брандмауэре для конфигурирования
Далее необходимо создать схемы баз данных, и сделать это проще всего, открыв
доступ в брандмауэре
Core
Azure,
чтобы можно было запускать команды
Entity Framework
из машины разработки.
Выберем одну их двух баз данных в категории ресурсов
SQL Databases, щелкнем на
Open in Visual Studio (Открыть в Visual
Studio). Теперь щелкнем на ссылке Configure Your Firewall (Конфигурировать брандмауэр).
щелкнем на кнопке Add Client IP (Добавить IР-адрес клиента) и щелкнем на кнопке
Save (Сохранить). В результате текущий IР-адрес получит возможность достигать
кнопке
Tools (Сервис)
и затем щелкнем на ссылке
сервера баз данных и выполнять команды конфигурирования. (Проинспектировать
схему базы данных можно, щелкнув на кнопке
Open ln Visual Studio,
чтобы открыть
Глава 12. SportsStore: защита и развертывание
Visual Studio
и воспользоваться окном
SQL Server Object Explorer
343
для исследования
базы данных.)
получение строк подключений
Вскоре понадобятся строки подключений для новых баз данных. Портал
Azure
предоставляет такую информацию по щелчку на базе данных в категории ресурсов
SQL Databases
через ссылку
Show Database Connection Strings
(Показать строки под­
ключений для баз данных). Строки подключений предлагаются для разных платформ
разработки; приложениям
которую портал
Azure
.NET требуются
строки
ADO.NET. Вот строка
products:
подключения,
предоставляет для базы данных
Server=tcp:sportsstorecore2db.database.windows.ne t,1433;
Initial Catalog=products;
Persist Security Info=False;
User ID={ваше_имя_попьзоватепя};Раsswоrd={ваш_паропь};
MultipleActiveResultSets=True;Encrypt=True;
TrustServerCertificate=False;Connection Timeout=ЗO;
В зависимости от того, как портал
Azure
подготовил базу данных, вы будете видеть
разные параметры конфигурации. Обратите внимание на выделенные полужирным
заполнители для имени пользователя и пароля. которые должны быть изменены, ког­
да вы применяете строку подключения при конфигурировании приложения.
Подготовка приложения
Перед тем. как приложение можно будет развернуть. предстоит выполнить опре­
деленные подготовительные шаrи, чтобы привести его в готовность к производствен­
ной среде. В последующих разделах будет изменен способ отображения сообщений об
ошибках и настроены строки подключения для производственных баз данных.
Создание контроллера и представления для
отображения сообщений об ошибках
В текущий момент приложение сконфигурировано на использование страющ ошибок.
дружественных к разработчику, которые предоставляют полезную информацию при воз­
никновении проблемы. Конечные пользователи не должны видеть такую информацию.
поэтому добавим в папку
Controllers
файл класса по имени
с определением простого контроллера. показанного в листинге
Листинг
12.12.
Содержимое файла
ErrorController. cs
ErrorController. cs
12.12.
из папки
Controllers
using Microsoft.AspNetCore.Mvc;
namespace SportsStore.Controllers
puЫic
class ErrorController : Controller
ViewResult Error() => View();
puЫic
В контроллере определено действие
Error,
которое визуализирует стандартное
представление. Чтобы снабдить контроллер представлением, создадим папку
Error
и добавим в нее файл представления
кой, приведенной в листинге
12.13.
Razor
по имени
Error. cshtml
Views/
с размет­
344
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Листинг
12.13. Содержимое файла Error.cshtml из папки Views/Error
@{
Layout = null;
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet" href="-/liЬ/bootstrap/dist/css/bootstrap.min.css" />
<title>Error</title>
</head>
<body>
<h2 class="text-danger">Error.</h2>
<hЗ class="text-danger">An error occurred while processing your request .</hЗ>
</body>
</html>
Страница ошибки подобного рода является последним средством, а потому ее лучше
сохранить как можно более простой и не полагаться на разделяемые компоновки, ком­
поненты представлений или другие расширенные возможности. В данном случае мы от­
ключаем разделяемые компоновки и определяем простой НТМL-документ с сообщением
о возникновении ошибки, не предоставляя никакой информации о том, что произошло.
Определение настроек производственных баз данных
Следующим шагом будет создание файла, который снабдит приложение строка­
ми подключения к его базам данных в производственной среде. Добавим в проект
SportsStore
новый файл по имени
шаблона элемента
ASP.NET
appsettings .production. json
Coпfiguratioп
File
тим в него содержимое, показанное в листинге
Совет. В списке файлов окна Solutioп
находится внутри узла
Explorer
appset tings. j son,
с применением
(Файл конфигурации ASP.NEТ) и помес­
12.14.
файл
appsettings .production. j son
который понадобится раскрыть, если позже
вы захотите отредактировать данный файл.
Листинг
12.14. Содержимое файла appsettings. production. j son
из папки SportsStore
"Data": {
"SportStoreProducts":
"ConnectionString":
"Server=tcp:sportsstorecore2db.database.windows.net,1433;
Initial Catalog=products;Persist Security Info=False;
User ID={ваше_имя_пользователя);
Password={вaш_napoль);MultipleActiveResultSets=True;Encrypt=True;
TrustServerCertificate=False;Connection
)
Timeout=ЗO;"
'
"SportStoreidentity": {
"ConnectionString":
"Server=tcp:sportsstorecore2db.database.windows.net,1433;
Глава 12. SportsStore: защита и развертывание
345
Initial Catalog=identity;Persist Security Info=False;
User ID={ваше_имя_nопьаоватепя};
Password={вaw_naponь};MultipleActiveResultSets=True;Encrypt=True;
TrustServerCertificate=False;Connection
Timeout=ЗO;"
Файл неудобен для чтения. т.к. строки подключений разрывать нельзя. Содержимое
данного файла дублирует раздел строк подключений файла
здесь используются строки подключений
имени пользователя и пароля.) Кроме того, параметр
был установлен в
True,
appset tings. j son. но
Azure. (Не забудьте заменить заполнители для
Mul tipleActi veResul tSets
что делает возможными множество параллельных запросов и
устраняет условия для возникновения распространенной ошибки, которые появляют­
ся при выполнении сложных запросов
LINQ
в отношении данных приложения.
На заметку! При вставке в строки подключения своего имени пользователя и пароля удали­
те символы фигурных скобок, чтобы в итоге получилось
Password=MyPassword, но не
Password={MyPassword).
Конфиrурирование приложения
Теперь можно изменить код класса
Startup,
чтобы в случае нахождения в про­
изводственной среде приложение вело себя по-другому. Изменения представлены в
листинге
Листинг
12.15.
12.15.
Конфигурирование приложения в файле
из папки
using
using
using
using
using
using
using
using
using
using
using
using
Startup. cs
SportsStore
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
SportsStore.Models;
Microsoft.Extensions.Configuration;
Microsoft.EntityFrameworkCore;
Microsoft.AspNetCore.Identity;
namespace SportsStore {
puЬlic
class Startup {
Startup(IConfiguration configuration) =>
Configuration = configuration;
puЫic
puЫic
IConfiguration Configuration { get; 1
void ConfigureServices(IServiceCollection services)
services.AddDbContext<ApplicationDbContext>(optio ns =>
options.UseSqlServer(
Configuration["Data:SportStoreProducts:Connectio nString"]));
puЬlic
346
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
services .AddDbContext<Appldenti t.yDbContext> ( options =>
options.UseSqlServer(
Configuration["Data:SportStoreidentity:Connection String"JI);
services.Addldentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<AppidentityDbContext>()
.AddDefaultTokenProviders();
services.AddTransient<IProductRepository, EFProductRepository>();
services.AddScoped<Cart>(sp => SessionCart.GetCart(sp));
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IOrderRepository, EFOrderRepository>();
services.AddMvc();
services.AddMemoryCache();
services.AddSession();
puЫic
void Configure(IApplicationBuilder
арр,
IHostingEnvironment env)
{
if (env.IsDevelopment()) (
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
} else (
app.UseExceptionHandler("/Error");
app.UseStaticFiles();
app.UseSession();
app.UseAuthentication();
app.UseMvc(routes => {
routes. MapRoute (name: "Error", template: "Error",
defaults: new { controller = "Error", action = "Error" }) ;
routes.MapRoute(name: null,
template: "{category}/Page{productPage:int}",
defaults: new { controller = "Product", action = "List" )
) ;
routes.MapRoute(name: null,template: "Page{productPage:int}",
defaults: new { controller = "Product",
action = "List", productPage = 1 )
) ;
routes.MapRoute(name: null, template: "{category}",
defaults: new { controller = "Product",
action = "List", productPage = 1 )
) ;
routes.MapRoute(name: null,template: "",
defaults: new { controller = "Product",
action = "List", productPage = 1 }
) ;
routes.MapRoute(name: null, template: "{controller}/{action}/{id?}");
}) ;
11 SeedData.EnsurePopulated(app);
11 IdentitySeedData.EnsurePopulated(app);
Глава 12. SportsStore: защита и развертывание
Интерфейс
IHostingEnvironment
347
используется для предоставления информа­
ции о среде, в которой функционирует приложение, такой как среда разработки или
производственная среда. В случае производственной среды
ASP.NET Core
будет за­
гружать содержимое файла
настройки в файле
appset t ings. production. j son, чтобы переопределить
appset tings. j son, а это значит, что Entity Fгamewoгk Core будет
подключаться к базам данных Azuгe вместо
LocalDB.
Доступно множество параметров
для настройки конфигурации приложения в различных средах, которые будут рас­
сматриваться в главе
14.
Также были закомментированы операторы, которые производят начальное запол­
нение баз данных, что объясняется в разделе "Управление начальным заполнением
баз данных" далее в главе.
Применение миграций к базам данных
Чтобы настроить базы данных со схемами, требующимися приложению. откроем
окно командной строки или
PowerShell
и перейдем в папку проекта
Установка среды так, что инструмент командной строки
строки подключения для
менения окна
PowerShell
Azure,
dotnet
SportsStore.
будет использовать
требует установки переменной среды. В случае при­
установка переменной среды выполняется с помощью сле­
дующей команды:
$env:ASPNETCORE ENVIRONMENT="Production"
Если же используется окно командной строки, то переменная среды устанавлива­
ется посредством такой команды:
set ASPNETCORE ENVIRONMENT=Production
Для применения миграций в проекте к базам данных
занные далее команды. находясь в папке проекта
Azure
нужно запустить пока-
SportsStore:
dotnet ef database update --context ApplicationDbContext
dotnet ef database update --context AppidentityDbContext
Переменная среды указывает размещающую среду. которая применяется для полу­
чения строк подключения к базам данных. Если приведенные команды не работают.
тогда понадобится проверить, сконфигурирован ли брандмауэр
Azure для
разрешения
доступа к машине разработки. как было описано ранее в главе, а также корректно ли
скопированы и модифицированы строки подключения.
Управление начальным заполнением баз данных
В листинге
классе
12.15
Startup,
операторы, выполняющие начальное заполнение баз данных в
закомментированы. Дело в том, что команды
Entity Framework Core.
используемые в предыдущем разделе для применения миграций к базе данных, по­
лагаются на службы. устанавливаемые классом
Startup.
При наличии упомянутых
операторов код начального заполнения баз данных вызывался бы перед применени­
ем миграций, приводя к ошибке и препятствуя нормальной работе миграций. Когда
базы данных установлены, проблема не возникает. В случае базы данных
причина в том. что метод
SeedData. EnsurePopulated ()
products
применяет миграции пе­
ред заполнением начальными данными, и в том, что начальные данные
I den t i t
у
не добавляются в приложение до тех пор, пока к базе данных не будет применена
миграция.
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
348
В отношении производственной среды мы хотим принять другой подход к запол­
нению начальными данными. Для пользовательских учетных записей мы собираемся
поместить в базу данных учетную запись администратора. когда производится попыт­
ка входа. Мы добавим к инструменту администрирования средство для начального
заполнения базы данных товаров, чтобы производственная система могла заполнять­
ся тестовыми данными или оставаться пустой для поступления реальных данных.
На заметку! Начальное заполнение данными аутентификации в производственной системе
должно делаться осторожно, а приложение обязано использовать функциональные средс­
тва, описанные в главах
28-30,
для изменения пароля сразу же после того, как приложе­
ние развернуто.
Начальное заполнение данными о пользователях
Первый шаг в изменении способа заполнения данными о пользователях предус­
матривает упрощение кода в классе
Листинг
using
using
using
using
12.16.
Identi tySeedData
Упрощение кода в файле
(листинг
12.16).
IdentitySeedData.cs из папки Models
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Identity;
Microsoft.Extensions.Dependencyinjection;
System.Threading.Tasks;
namespace SportsStore.Models (
puЫic
static class IdentitySeedData
private const string adminUser = "Admin";
private const string adminPassword = "Secretl23$";
puЫic
static async Task
EnsurePopulated(UserМanager<IdentityUser>
userМanager)
{
IdentityUser user = await userManager.FindByidAsync(adminUser);
if (user == null) (
user = new IdentityUser("Admin");
await userManager.CreateAsync(user, adminPassword);
Вместо
получения
EnsurePopulated ()
самой
службы
U serManage r< Iden t i tyU ser>
тегрировать начальное заполнение базы данных в класс
(листинг
Листинг
AccountController
12.17).
12.17.
Начальное заполнение базы данных в файле
из папки
using
using
using
using
using
метод
получает в качестве аргумента объект. что позволяет ин­
Controllers
System.Threading.Tasks;
Microsoft.AspNetCore.Authorization;
Microsoft.AspNetCore.Identity;
Microsoft.AspNetCore.Mvc;
SportsStore.Models.ViewModels;
Accoun tCon troller. cs
Глава 12. SportsStore: защита и развертывание
using
349
SportsStore.Мodels;
namespace SportsStore.Controllers
[Authorize]
class AccountController : Controller
private UserManager<IdentityUser> userManager;
private SignlnManager<IdentityUser> signlnManager;
puЫic
AccountController(UserManager<IdentityUser> userMgr,
SigninManager<IdentityUser> signlnMgr) {
userManager = userMgr;
signinManager = signlnMgr;
IdentitySeedData.EnsurePopulated(userМgr) .Wait();
puЫic
11 ... для
краткости другие методы не показаны ...
I den t i t у будет заполняться
AccountController для обработки НТТР-запроса.
Внесенные изменения гарантируют, что база данных
каждый раз, когда создается объект
Конечно, это далеко от идеала, но не существует хорошего способа начального запол­
нения базы данных, и такой подход обеспечивает возможность администрирования
приложения в производственной среде и среде разработки, хотя за счет ряда допол­
нительных запросов к базе данных.
Начальное заполнение данными о товарах
Что касается данных о товарах, то мы собираемся снабдить администратора кноп­
кой, щелчок на которой приведет к начальному заполнению базы данных, когда она
пуста. Первым делом изменим код начального заполнения, чтобы задействовать в
нем интерфейс, который позволит обращаться к службам, предоставляемым конт­
роллером, а не классом
Startup
(листинг
12.18).
Кроме того, мы закомментировали
операторы для автоматического применения любых ожидающих миграций, которые
могут привести к утере данных и должны использоваться с особой осторожностью в
производственных системах.
Листинг
12.18.
Подготовка к ручному начальному заполнению в файле
из папки
using
using
using
using
using
SeedDa ta. cs
Models
System.Linq;
Microsoft.AspNetCore.Builder;
Microsoft.Extensions.Dependencyinjection;
Microsoft.EntityFrameworkCore;
Systeш;
namespace SportsStore.Models {
puЫic
static class SeedData
static void EnsurePopulated(IServiceProvider services)
ApplicationDbContext context =
services.GetRequiredService<ApplicationDbContext> ();
puЬlic
11
context.DataЬase.Мigrate();
if (!context.Products.Any()) {
context.Products.AddRange(
Часть 1. Введение в инфраструктуру ASP NET Core MVC 2
350
// ... для
краткости другие операторы не показаны ...
) ;
context.SaveChanges();
Следующий шаг заключается в обновлении контроллера
Admin
с целью добавле­
ния метода действия, который инициирует операцию начального заполнения (лис­
тинг
12.19).
Листинг
12.19.
Начальное заполнение базы данных в файле
из папки
using
using
using
using
AdminController. сз
Controllers
Microsoft.AspNetCore.Mvc;
SportsStore.Models;
System.Linq;
Microsoft.AspNetCore.Authorization;
namespace SportsStore.Controllers {
[Authorize)
class AdminController : Controller
private IProductRepository repository;
puЫic
AdminController(IProductRepository repo) {
repository = repo;
puЫic
puЫic
ViewResult Index() => View(repository.Products);
// ... для
краткости другие методы не показаны ...
[HttpPost]
IActionResul t SeedDa taЬase () {
SeedData.EnsurePopulated(HttpContext.RequestServices);
return RedirectToAction(nameof(Index));
puЬlic
Новое действие декорируется атрибутом
лью запросов
POST,
HttpPost,
так что оно может быть це­
и после начального заполнения базы данных перенаправляет
браузер на метод действия
Index ().
Осталось лишь создать кнопку для начально­
го заполнения базы данных, которая будет отображаться, когда база данных пуста
(листинг
Листинг
12.20).
12.20.
Добавление кнопки для начального заполнения базы данных
в файле
@model
Index. cshtml
IEnumeraЫe<Product>
@{
ViewBag.Title = "All Products";
Layout = "_AdminLayout";
из папки
Views/Admin
Глава 12. SportsStore: защита и развертывание
@if (Model.Count() ==О) {
<div class="text-center m-2">
<form asp-action="SeedDataЬase" method="post">
<button type="suЬmit" class="Ьtn Ьtn-danger">Seed
</form>
</div>
else {
<tаЫе class="taЫe
351
DataЬase</button>
taЫe-striped taЫe-bordered taЬle-sm">
<tr>
<th class="text-right">ID</th >
<th>Name</th>
<th class="text-right">Price< /th>
<th class="text-center">Actio ns</th>
</tr>
@foreach (var item in Model) {
<tr>
<td class="text-right">@item. ProductID</td>
<td>@item.Name</td>
<td class="text-right">@item .Price.ToString("c")</td>
<td class="text-center">
<form asp-action="Delete" method="post">
<а asp-action="Edit" class="Ьtn Ьtn-sm Ьtn-warning"
asp-route-productid="@ite m.ProductID">
Edit
</а>
<input type="hidden" name="ProductID"
value="@item.ProductID" />
<button type="submit" class="btn btn-danger btn-sm">
Delete
</button>
</form>
</td>
</tr>
</tаЫе>
<div class="text-center">
<а asp-action="Create" class="Ьtn
</div>
Ьtn-primary">Add
Product</a>
Развертывание приложения
Чтобы развернуть приложение, щелкнем правой кнопкой мыши на элементе про­
екта
SportsStore
в окне Solutioп
Explorer
(проекта, но не решения) и выберем в кон­
текстном меню пункт PuЫish (Опубликовать). Среда
метод опубликования (рис.
Visual Studio
преД1Iожит выбрать
12.2).
Что делать, если развертывание терпит неудачу?
Самой главной причиной неудачи развертывания являются строки подключения, либо пото­
му, что они были некорректно скопированы из среды Azure, либо из-за того, что при редак­
тировании для вставки имени пользователя и пароля была допущена ошибка.
352
Часть 1. Введение в инфраструктуру ASP.NET Соге MVC 2
В случае отказа развертывания начинать поиск проблемы нужно со строк подключения.
Если команды
dotnet ef database update
из раздела "Применение миграций к базам
данных" выше в главе не приводят к ожидаемым результатам, то развертывание потерпит
неудачу. Если команды отработали успешно, но развертывание закончилось неудачей, тог­
да необходимо проверить, установлена ли переменная среды, поскольку есть вероятность
того, что вместо базы данных в облаке была подготовлена локальная база данных.
PuЬlish
Connedtd ~ces
PuЬ11sh
your 4рр to Azure or 1nothtr host. lt1rn more
Microюft
Azure
llS, FТР, <tc
AppS.Мct
foldtr
PuЫnh
S.ltct Ex1stong
Рис.
Выберем вариант
12.2.
>
Выбор метода опубликования
Microsoft Azure
Арр
Service (Служба приложений Microsoft Azure)
Create New (Создать новую); переключатель
и удостоверимся в выборе переключателя
Select
Existiпg (Выбрать существующую) используется для обновления существующего
развернутого приложения. Будет предложено предоставить детали для развертыва­
ния. Начнем со щелчка на кнопке
учетных данных
Add
ап
Account
(Добавить учетную запись) и ввода
Azure.
После ввода учетных данных можно выбрать имя для развернутого приложения
и ввести информацию относительно службы. которая будет зависеть от типа имею­
щейся учетной записи
Azure,
службы развертывания (рис.
желаемой области для развертывания и требующейся
12.3).
После того , как служба сконфигурирована, щелкнем на кнопке
Create
(Создать).
Как только служба установится. будет выдана сводка по операции опубликования. ко­
торая отправит приложение в службу размещения (рис.
12.4).
Щелкнем на кнопке PuЫish (Опубликовать), чтобы запустить процесс разверты­
вания. Выбрав пункт
ViewqQther Windows
Web
PuЫish
Activity
(Активность веб-развертывания) в меню
(Видq Другие окна) среды
Visual Studio.
можно наблюдать за хо­
дом развертывания. Наберитесь терпения. т. к. отправка всех файлов из проекта служ­
бе
Azure
может занять некоторое время. Последующие обновления будут проходить
быстрее, потому что передаче подлежат только измененные файлы.
По завершении развертывания среда
Visual Studio
откроет новое окно браузера
для развернутого приложения. Поскольку база данных товаров пуста, отобразится
компоновка, показанная на рис.
12.5.
Глава
12. SportsStore:
353
защита и развертывание
х
Create Арр Service
r1 ···Microsoft account
Host your web and moblle applications. REST APls, and more in Azure
Нosting
Srrvkes
Chang• Тур••
AppNtmt
SportsStor.Ccrt-2
Subюiption
Visual Studio Enterprise:
Resourct Group
Арр S.Мсе
v
1
Ntw".
v
1
New...
Plon
SportsStor.Cort2Plon'
Clklcing the Crt•te Ьutton will cre•I• the following Azure resources
Explore 4dd1t1on.al Azurt seJV1te-s
Арр S.rvict • SportsStor.Cort-2
Арр
S.rvict Plon · SportsStor.Cort2Pl•n
lf you h.wt rtmovod your sptnding lim~ or you or• u51ng Р•у •• You Go, th"e may Ь• mon.t•ry imp•ct if you provision •ddition1I rtsourcts.
Le&m Mcrt
Export..•
Crute
Рис. 12.З. Создание новой службы приложений
l
J
C•ncel
Azure
~ SportsStore ~ SportsStort-
-
PuЬlish
Ccnn«ted Se.nticн
J>uЫ1sh your •РР to .A.."Vrt or ьnother ho-st.
lf.tm mcrt
Э!] SportsS!ortCort-2 · Vltb Dtploy
• '
PuЫ1th
Summary
QJ
Stlt URL
http: spcrtsstof~tore·2.41.u1twt'!нrt.es..net
R.tюur<t Group
cort.2
Prl'1r1ew.
Confi9ur11t1on
Reltast
Ptname profi t~
Userrчme
SSport•St0<.Cort-2
Ot:fett prof1le
Pass't'tord
Рис.
12.4.
Сводка по операции опубликования
D
Х
354
Часть 1. Введение в инфраструктуру ASP NET Core MVC 2
Home
_J
1
Рис.
Перейдем на
вателя
12.5.
URL
вида
Начальное состояние развернутого приложения
/Admin/Index
и для аутентификации введем имя пользо­
Admin и пароль Secret123$. Произойдет начальное заполнение базы данных
Identity
по требованию. что даст возможность войти в административную часть
приложения (рис .
12.6).
D All Products
Х
Af 1Products
ID Name
Price
1 Kayak
$275.00
2 lifejacke
S48.95
З
Soccer Ball
$19.50
4 Corner Flags
SЗ4.95
Actions
1111
1111
Е11
"
""
"
$79,500.00
DIEll
Thinking Сар
$16.00
111111
7 Unsteady Chair
$29.95
8 Human Chess Воагd
575.00
5 Stadium
б
$1 ,200.00
9 Bling-BlingXing
Mfi§§E
Рис.
12.6.
Экран администрирования
Глава 12. SportsStore: защита и развертывание
Щелкнем на кнопке
Seed Database (Заполнить базу данных).
чтобы запол нить базу
данных товаров, что приведет к результату. представленному на рис.
можно перейти на корневой
С
URL прил ожения
355
12.7.
Затем
и работать с ним обычным образом.
<D sportsstorecore-2.azurewebs1tes.net1adm1n/naex
All Products
Seed Database
Add Product
Рис.
12. 7.
Заполненная база данных
Резюме
В этой и предшествующих главах демонстрировалось применение инфраструкту­
ры
ASP.NET Core
МVС для создания реалистичного приложения электронной коммер­
ции. В приведенном расширенном примере были проиллюстрированы многие основ ­
ные средства
MVC:
контролле ры . методы действий. маршрутизация , представления ,
метаданные, проверка достоверности , компоновки, аутентификация и т. д. Вы такж е
видели, как пользоваться рядом ключевых технологий, имеющих отношение к
том числе Eпtity
Framework Core,
MVC, в
внедрение зависимостей и модульное тестирование.
Результатом стало приложение с ясной и ориентированной на компоненты архитекту­
рой, обеспечивающей разделени е обязанностей, и кодовой базой, которую будет лег ­
ко ра с ширять и сопровождать . Итак. разработка приложения SportsStore завершена .
В сл едующе й главе будет показано. как применять
приложений
ASP.NET Core MVC.
Visual Studio Code
для со здания
ГЛАВА
13
Работа с
Visual Studio Code
будет показано, как создать приложение ASP.NET Соге MVC с
в настоящей главе
Vlsual Studlo Code применением
межплатформенного редактора с открытым
кодом производства
Microsoft. Несмотря на название. продукт Visual Studio Code не
имеет отношения к Visual Studio и основан на инфраструктуре Electron, используе­
мой редактором Atom. который популярен среди разработчиков, применяющих дру­
гие инфраструктуры для построения неб-приложений, такие как Angular.
Продукт Visual Studio Code поддерживает операционные системы Windows. macOS
и наиболее популярные дистрибутивы Linux. Он сформировался в удобную и полно­
функциональную среду разработки, хотя и лишен всех украшений, предлагаемых
Visua\ Studio.
Я обнаружил, что использую среду
Vlsua\ Studlo Code
все чаще и чаще,
поскольку с ней просто работать, она быстрая и располагает хорошей поддержкой
других языков вроде
JavaScript
и ТypeScript.
Настройка среды разработки
Процесс настройки
Visual Studio Code
требует выполнения определенной работы.
т.к. некоторая функциональность. включенная в состав
Code
Vlsua\ Studio,
в
Visual Studlo
обрабатывается внешними инструментами. Одни инструменты идентичны тем.
которые среда
Visua\ Studio применяет "за кулисами'', но другие являются новыми в
.NET и могут быть незнакомыми. Хорошая новость в том, что
мире разработки для
эти инструменты широко используются разработчиками, которые ориентируются на
другие инфраструктуры для построения неб-приложений, причем качество и функ­
циональные средства находятся на высоком уровне. В последующих разделах мы ис­
следуем процесс установки
Visual Studio Code
наряду с важными инструментами и
дополнениями, требующимися для разработки приложений
Установка
MVC.
Node.js
В мире разработки клиентской стороны
Node.js
(или просто
Node)
представляет
собой исполняющую среду. на которую полагаются многие популярные инструменты
разработки. Продукт
Node
был создан в
2009
году как простая и эффективная испол­
няющая среда для серверных приложений. написанных на языке
нована на механизме
JavaScript,
интерфейс для выполнения кода
Исполняющая среда
Node.js
применяемом в браузере
JavaScript
JavaScrlpt. Она ос­
Chrome, и предлагает АРl­
за пределами среды браузера.
достигла определенных успехов в качестве сервера
приложений, но в данной главе она интересна тем, что предоставляет основу для
нового поколения межплатформенных инструментов построения и диспетчеров па­
кетов. Ряд интеллектуальных проектных решений, внедренных командой разработ-
Глава 13. Работа с Visual Studio Code
357
Node. и межплатформенная поддержка. обеспечиваемая исполняющей средой
JavaScript браузера Chrome. создали благоприятную возможность. которой восполь­
чикав
зовались полные энтузиазма проектировщики инструментов. особенно те из них. кто
желал поддерживать разработку веб - приложений .
На заметку! Доступны две версии
Node.js.
Версия
(Loпg
LTS
Term Support -
долгосрочная
поддержка) предоставляет стабильный фундамент для развертывания в производствен­
ных средах, где изменения должны быть сведены к минимуму. Обновления версии LTS
выпускаются каждые 6 месяцев и сопровождаются в течение 18 месяцев . Версия Curreпt
(текущая) является более часто изменяющимся выпуском , в котором преимущество от ­
дается новым средствам взамен стабильности. В настоящей главе мы будем применять
выпуск Curreпt .
Установка
Node.js в Windows
Загрузим и запустим установщик
nodej s. o rg .
При установке
Node.js
Node.js
для
Windows
из веб-сайта
h t tp : / /
Add to
Windows,
понадобится обеспечить выбор варианта
РАТН (Добавить в переменную Р А Т Н). На рис .
13. l
показан установщик для
который предлагает модифицировать переменную среды РАТН в качестве варианта
установки.
~~t the
way you v.·ant feaЬ.Xes to Ье lnstaeed.
Instal the core Node.js runtirne
(~.ехе).
11"5 fe11ture reQIJlfes lбМВ on your
hard drJve. !t has 2 of 2
suЬfeaЬ.Xes selected. ТМ
suЬfeaь.xes r~e 2ОКВ on YfXX
>
<
harddn\le.
Next
Reset
Рис .
Установка
13.1.
Node.js
Установщик для
в
Добавление пути к
Node.js в переменную среды РАТН
macOS
macOS
можно загрузить из веб - сайта
htt ps : / /nodej s . o r g .
Запустим установщик и примем стандартные настройки. Когда устано вка заве ршит­
ся. удо стоверимся в наличии пути
/ us r / l
о са l
/ Ьi n
внутри переменной
$ РАТ Н.
358
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Установка
Node.js
в
Linux
Самый простой способ установки
Node.js в Liпux предполагает использование дис­
Node предоставила инструкции для основных
петчера пакетов: команда разработчиков
дистрибутивов по адресу
https: / /nodej s. org/en/download/package-manager /.
В среде Ubuпtu для загрузки и установки
ные в листинге
Листинг
13.1.
Node.js
применяются команды, приведен­
13.1.
Установка
Node.js
sudo curl -sL https://deb.nodesource.com/setup
sudo -Е bash -sudo apt-get install -у nodejs
Проверка установки
6.х
Node
После завершения установки откроем новое окно командной строки и запустим
команду. показанную в листинге
13.2,
чтобы проверить. работает ли
Node,
и отобра­
зить номер установленной версии.
Листинг
13.2.
Проверка успешности установки
Node
node -v
Если установка прошла успешно, и путь к
Node
добавился в переменную среды
РАТН, то появится номер версии. На момент написания главы текущей версией
была
6.11.2.
Node
В случае если во время проработки примеров. рассмотренных в главе.
вы получаете неожиданные результаты, тогда попробуйте воспользоваться указанной
конкретной версией.
Установка
Продукт
Git
Visual Studio Code
включает интегрированную поддержку
ся отдельная установка для поддержки инструмента
Bower.
Git,
но требует­
который применяется при
управлении пакетами клиентской стороны.
Установка
Git в Windows
Загрузим
и
или
macOS
запустим установщик
из
веб-сайта
ht.tps: //git-scm.
сот/
downloads.
Установка
Git в Linux
В большинстве дистрибутивов Liпux инструмент
Git уже установлен.
При желании
установить его в любом случае следует ознакомиться с инструкциями по установке
для своего дистрибутива, которые доступны по адресу:
https://gH-scm.com/download/linux
В среде Ubuпtu используется команда, представленная в листинге
Листинг
13.3.
Установка
Git
sudo apt-get install git
в
Ubuntu
13.3.
Глава 13. Работа с Visual Studio Code
Проверка установки
359
Git
После завершения установки запустим в новом окне командной строки/терминала
команду, приведенную в листинге
инструмент
Листинг
чтобы проверить, установлен и доступен ли
13.4,
Git:
Проверка успешности установки
13.4.
Git
git --version
Git. На
2.14.1. а
Команда выведет версию установленного пакета
последней версией
Установка
Windows
и
macOS
была
момент написания главы
для
Linux - 2. 7.4.
Bower
Node.js
Продукт
NPM),
для
Git
Node (Node Package Manager -
поступает с диспетчером пакетов
который применяется для загрузки и установки пакетов разработки, напи­
санных на
Для целей текущей главы нужен единственный пакет
JavaScript.
Bower,
который используется для управления пакетами клиентской стороны и был описан
в главе
6.
Запустим команду. показанную в листинге
вить Воwег в
Листинг
13.5,
чтобы загрузить и устано­
Windows.
13.5.
Установка пакета
Bower
в
Windows
npm install -g bower@l.8.0
Linux и macOS применяется
(листинг 13.6).
В средах
мандой
Листинг
та же самая команда. но с обязательной ко­
sudo
13.6.
Установка пакета
Bower
в
Linux
и
macOS
sudo npm install -g bower@l.8.0
Установка
.NET Core
При разработке приложений
Core.
ASP.NET Core MVC требуется исполняющая среда .NET
Для каждой поддерживаемой платформы предусмотрен собственный процесс
установки. описание которого доступно по адресу
В
Microsoft
предлагают установщики для
инструкции для установки в
Установка
. NET Core
Чтобы установить
в
Linux
Windows
Windows
www.microsoft.com/net/core.
и
macOS.
с использованием архивов
и
а также предоставляют
tar.
macOS
.NET Core в Windows
.NET Core SDK.
или в
macOS.
нужно просто загрузить и
запустить установщик
Установка
.NET Core
По адресу
в
Linux
www.microsoft.com/net / core
предлагаются инструкции по установ­
.NET Core в большинстве популярных дистрибутивов Linux. В главе применяется
Ubuntu, и процесс требует первоначальной настройки новой подачи для apt-get с
использованием команд из листинга 13. 7.
ке
360
Часть 1. Введение в инфраструктуру ASP.NET Соге MVC 2
Листинг 1З.7. Подготовка к установке
.NET Core в Ubuntu Linux
sudo sh -с 'echo "deb [arch=arnd64] https://apt-rno.trafficrnanager.net/repos/
dotnet-release/xenial rnain" > /etc/apt/sources.list.d/dotnetdev.list'
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:BO
--recv-keys 417А0893
sudo apt-get update
Далее производится установка
Листинг
.NET Core
(листинг
13.8).
13.8. Установка .NЕТ Core в Ubuntu Linux
sudo apt-get install dotnet-sdk-2.0.0
Проверка установки
.NET Core
Независимо от имеющейся платформы проверка того, что инфраструктура .NЕТ Соге
установлена и готова к использованию, производится одинаково. Откроем новое окно
командной строки/терминала и выполним команду, приведенную в листинге
Листинг
13.9.
13.9. Проверка версии .NЕТ Core
dotnet --version
Команда
dotnet
запускает исполняющую среду
мер версии установленного пакета
выпуском был
2.0.0,
.NET Core.
.NET,
после чего отобразится но­
На момент написания главы текущим
но ко времени выхода книги он с высокой вероятностью будет
заменен более новым выпуском.
Установка
Visual Studio Code
Visual Studio Code,
https: / / code. visualstudio. сот. Установочные
Windows. macOS и популярных дистрибутивов Linux.
Самым важным шагом является загрузка и установка редактора
который доступен на веб-сайте
пакеты предусмотрены для
Понадобится загрузить и установить пакеты для выбранной платформы.
На заметку! Компания
Microsoft предлагает новые выпуски Visual Studio Code ежемесячно, а
это значит, что устанавливаемая вами версия будет отличаться от версии, которая приме­
няется в главе. Хотя основы должны остаться теми же, выполнение некоторых примеров
может потребовать определенной доли экспериментирования.
Установка
Visual Studio Code
Чтобы установить
в
Windows
Visual Studio Code
для
установщик. После завершения процесса
окно редактора (рис.
Установка
Windows, необходимо просто запустить
Visual Studio Code запустится и появится
13.2).
Visual Studio Code
Продукт
в
macOS
VisuaJ Studio Code предоставляется в виде архива zip для Мае, который до­
https: / /go .microsoft. сот/ fwlink/?LinkID=620882.
Раскроем архив и дважды щелкнем на содержащемся в нем файле Visual Studio
Code. арр. чтобы запустить Visual Studio Code и получить окно редактора (см. рис. 13.2).
ступен для загрузки по адресу
Глава 13. Работа с Visual Studio Code
Установка
Visual Studio Code
в
361
Linux
Компания Microsoft предлагает файл . deb для Deblan и Ubuntu и файл . rpm для
Red Hat. Fedora и CentOS. Необходимо загрузить и установить файл для предпочита­
емого дистрибутива Linux. Поскольку в главе используется Ubuntu. нужно загрузить
файл . deb и установить его с помощью центра приложений Ubuntu.
После завершения установки выполним команду, приведенную в листинге 13. l О. что ­
бы запустить Visual Studio Code и получить окно редактора, показанное на рис. 13.2.
Листинг
13.10. Запуск Yisual Studio Code в Linux
/ u s r / share/code / code
Проверка установки
Visual Studio Code
Тестирование успешности установки
Visual Studio Code
сводится к проверке воз ­
можности запуска приложения и открытию окна редактора (см. рис .
13.2).
(Цветовая
схема была изменена. т.к. стандартные темные цвета не очень хорошо подходят для
получения экранных снимков.)
"
·• /Jr.trt\td-1 · V~wJSilllfto.Codt
-
rn
llrФtltd•1
'
Рис.
13.2.
Запуск
Visual Studio Code
Visual Studio Code
х
1
Установка расширения С# для
Редактор
Х
(J
в средах
Windows, macOS
и
Ubuntu Linux
Visual Studio Code
подде рживает функциональность. специфичную для
языка. через расширения, хотя это не те же самые расширения, которые поддержи­
ваются средой
Visual Studio. Самое важное расширение. касающееся разработки при­
Core MVC, добавляет поддержку для языка С#, отсутствие которой
ложений ASP.NEТ
может выглядеть как странное упущение в базовой установке . Тем не менее. такое
положе ние дел отражает тот факт, что в
Microsoft
позиционировали
Vis ual Studio Code
362
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
как универсальный межплатформенный редактор. который охватывает широчайший
спектр языков и инфраструктур.
Чтобы установить расширение С#. щелкнем на значке
расположенном в левой части окна
и отыщем в списке расширение С#
как показано на рис.
Visual Studio Code. В
for Visual Studio Code
(Расширения) ,
Extensions
поле поиска введем
(С# для
csharp
Visual Studio Code).
13.3.
[D
С#
Ф Ш•~
Microsoft
С#
Рис.
Щелкнем на кнопке
lnstall
(Установить) и
Reload
OrnniSharp)
Visual Studio Code
загрузит и установит
(Перезагрузить), чтобы перезапустить
и активизировать расширение (рис.
С#
Microsoft 1 t;> 331463
СИ
Ьу
36
1 З.З. Нахождение расширениR С#
расширение. Щелкнем на кнопке
Studio Code
for Visual Studio Code (powered
***;
".
1
Visual
13.4).
***;
L1cense
36
for V1sual Stud10 Code (pcw1ered Ьу OmnlSмrp).
f !m
Рис.
Создание проекта
~rnnst•U
13.4.
Включение расширениR С#
ASP.NET Core
Редактор
проектов
строки
Visual Studio Code не имеет интегрированной поддержки для создания
ASP.NET Core. но создавать новые проекты можно с применением командной
d o tnet .
Для корректной настройки проекта важно создать правильную структуру папок ,
особенно в случае, когда проект сопровождается модульными тестами. Создадим про ­
ект под названием
InvitesProjects
рая будет содержать проект
в подходящем местоположении
ASP.NET Core MVC
Далее создадим внутри папки
-
папке. кото­
и проект модульного тестирования.
Invit esProjects
папку по имени
Par t y i nvite s,
с использованием окна командной строки перейдем в эту папку и запустим команду.
приведенную в листинге
13.11.
Глава 13. Работа с Visual Studio Code
Листинг
1З.11.
Создание нового проекта в папке
d ot net new web --language
Команда
d o tnet new
С#
363
Partyinvi tes
--f ramework net co reapp 2 .0
предоставляет доступ посредством командной строки к
13. 1 1 шаблон web соответствует шаб­
Empty (Пустой) из Visual Studio. который применялся в предшествующих главах.
В табл. 13. 1 описан набор шаблонов проектов, предназначенных для разработки при­
ложений ASP.NET Саге. (Существуют и другие шаблоны , но при построении приложе­
ний ASP.NET Саге они не используются. Просмотреть полный список можно посредс­
твом команды d o tnet new --help .)
шаблонам проектов . и указанный в листинге
лону
Таблица
13.1.
dotnet new для
Шаблоны
разработки приложений ASP.NEТ
Core
Описание
Имя
web
Empty, применяемый в предшествующих главах, который созда­
ASP.NET Core, но не включает поддержку инфраструктуры MVC
Это шаблон
ет проект
mvc
Это шаблон
Web Applicatioп
(Model-View-Coпtroller) (Веб-приложение
2, который со­
MVC, а также запол­
(модель-представление-контроллер)), используемый в главе
здает проект, включающий поддержку инфраструктуры
нители для контроллеров и представлений
xuni t
Это шаблон xUпit
(.NET Core)),
Test Project (.NET Core)
(Проект тестирования xUпit
который настраивает модульное тестирование с помощью
пакета xUпit, как объяснялось в главе
7
Подготовка проекта с помощью
Visual Studio Code
Visual Studio Code. выберем пункт Ореп Folder (Открыть
InvitesProj ects и щелкнем на кнопке
папку). Среда Visual St.udio Code откроет проект и автомати­
Чтобы открыть проект в
папку) в меню
Select Folder
File
(Файл), перейдем в папку
(Выбрать
чески установит набор пакетов. требующихся для редактирования и отладки прило­
жений С#. Спустя несколько секунд после начала редактирования файла появится
сооб щение. предлагающее добавить элем енты в проект (рис .
Рис.
Щелкнем на кнопке
13.5.
Yes
13.5).
Приглашение добавить активы в проект
(Да). Среда
Visual Studio Code
создаст папку
. v s code
и
добавит в нее файлы, которые конфигурируют процесс построения. По умолчанию
Visual Studio Code прим еняет компоновку из трех разделов. Боковая п анель. выделен ­
ная на рис. 13.6, обеспечивает доступ к главным областям функциональности .
Самая верхняя кнопка позволяет открыть панель проводника. которая отобразит
содержимое ранее открытой папки . Остальные кнопки предоставляют доступ к средс­
тву поиска. к интегрированному управлению исходным кодом. к отладчику и к набору
установленных расширений.
364
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
OPEN EDIТORS
IN\IJTESPROJECТS
.с
.vscode
Partylnv1tes
~ btn
• Obj
V\ '\.Yroot
~
Partylnv1tes.csproJ
С•
С
Рис.
Pr09ram.cs
Start p.cs
13.6.
Боковая панель
Visual Studio Code
Щелчок на имени файла в панели проводника приводит к открытию файла для
редактирования. Допускается редактировать несколько файлов одновременно. Можно
создавать новые панели редактора. щелкая на кнопке
дактора) в правой верхней части окна . Редактор
обладая неплохой поддержкой
пакетов
NuGet
и
lntelliSense и
Split Editor
(Разделить окно ре­
Visual Studio Code
довольно хорош,
содействием в завершении имен и версий
Bower.
Кроме содержимого папки проекта панель проводника показывает. какие файлы в
текущий момент редактируются. Это позволяет легко сосредоточиться на подмножес­
тве файлов, с которыми производится работа, что является удобным дополнением.
когда приходится иметь дело с поднабором связанных файлов в крупном проекте.
Управление пакетами клиентской стороны
Как и в Visual Studio. при управлении пакетами
Visual Studio Code используется инструмент Bower.
клиентской стороны в проектах
хотя требуется дополнительная
работа .
Первым делом нужно добавить файл по имени
для того, чтобы указать
мыши на папке
Bower.
Partylnvi tes
. bowe rr c .
который применяется
где устанавливать пакеты. Щелкнем правой кнопкой
и выберем в контекстном меню пункт
New File
(Новый
файл), как демонстрируется на рис.
Установим имя файла в
букв
r)
13.7.
. bowerrc (обратите
внимание на наличие в имени двух
и поместим в него содержимое. приведенное в листинге
13. 12.
Глава 13. Работа с Visual Studio Code
365
• Ol'EN EOITORS
INV!ТESPROJfCТS
•
• vscode
• Partylnv1 1><
• bin
№w f1I• ~
• ObJ
№w
• wwwroc
R<v••I n Explortr
•
1f • "
Optn 1n Comm•nd Prompt
.с
!
Partyln
С•
Progran
С•
St. up.
Foldtr
F1nd 1n Foldtr
Сору
•V
СоруР•
h
Rl!'namt
D
Рис. 1З.7. Создание нового файла
Листинг
13.12.
Содержимое файла
. bowerrc
"d irectory ": "wwwroot/lib "
Затем создадим файл по имени
листинге
Листинг
bower . j s on
с содержимым. представл енным в
13. 13.
13.13.
Содержимое файла
bower. j son
"name ": "Partyi n vites ",
"pri va te": true,
" dep ende ncies": {
"boo tstrap" : "4.0.0-alpha. 6 "
Во с пользуемся окном ком андной строки/терминала, чтобы выполнить в папке
Partylnvi tes
команд.у из листинга
13. 14.
Листинг
1З.14.
b o wer i n s tall
Bower для
bowe r. j son.
которая применяет инструмент
загрузки и установки пакетов клиентской стороны . указанных в файле
Установка пакетов клиентской стороны
366
Часть 1. Введение в инфраструктуру ASP.NET Соге MVC 2
Конфигурирование приложения
Процесс инициализации проекта создал пустой проект без поддержки
тинге
13.15
приведены изменения, подлежащие внесению в файл
бы настроить
MVC
13.15.
что­
14.
Добавление поддержки
из папки
using
using
using
using
using
using
using
using
В лис­
с наиболее базовой конфигурацией, используя операторы, которые
были описаны в главе
Листинг
MVC.
Startup. cs,
MVC
в файле
s tartup. cs
Partyinvi tes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
namespace Partyinvites {
puЫic class Startup {
void ConfigureServices(IServiceCollection services) {
puЫic
services.AddМvc();
puЬlic
void Configure(IApplicationBuilder
арр,
IHostingEnvironment env) {
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseМvcWithDefaultRoute();
Построение и запуск проекта
Чтобы построить и запустить проект, понадобится выполнить в окне командной
строки/терминала команду из листинга
Листинг
13.16.
13.16,
находясь в папке
Partylnvi tes.
Запуск приложения
dotnet run
Среда
Visual Studio Code скомпилирует код в проекте и с помощью сервера при­
Kestrel, рассматриваемого в главе 14, запустит приложение, ожидая НТГР­
запросы на порте 5000.
Поскольку в Visual Studio Code не предоставляется поддержка для обнаружения
ложений
изменений в файлах классов С#, после их модификации потребуется остановить при­
ложение и запустить его снова.
Чтобы протестировать приложение, необходимо открыть окно браузера и перейти на
URLвида
Ошибка
htt.p: / /localhost: 5000.
404
Будет получен ответ, представленный на рис.
13.8.
связана с тем, что в текущий момент в проекте отсутствуют какие-либо
контроллеры для обработки запросов.
Глава 13. Работа с Visual Stud io Code
367
х
С
, Ф localhost.5000
Code: 404; Not Found
Stэtus
Рис.
13.8.
Тестирование примера приложения
Воссоздание приложения
Partyinvi tes
Все подготовительные шаги завершены, т.е. мы можем сосредоточиться на созда­
нии приложения
из главы
2.
MVC.
Мы собираемся воссоздать простое приложение
Partyinvites
но с несколькими изменениями и дополнениями. которые подчеркнут осо­
бенности работы с
Visual Studio Code.
Создание модели и хранилища
Для начала щелкнем правой кнопкой мыши на папке
рем в контекстном меню пункт
Установим имя папки в
New Folder
Partyin vites
Mode ls .
..
Filo Edtt S•lec on V10" Go
DoЬug
•sks H•lp
EXPLORER
• OPfN EDIТORS
using Systom;
using Syst~.Coll•ctions.Goм
using Sy:>t~m. Linq;
using Systom. Throoding. Tosks;
usi ng ~licrosoft. AspllotCoro. Bui
usiлg Microsoft .AspNotCoro.Ho
.i
•LI
а
(j
eJ
• .vscodt!
• Partytrivi
• b1n
•
оЬj
• WV.'WflX
Ь
.bowerr•
~
bo.-er.J!
!Ь
Partyll'l\I
С•
Progran
№wF1lo
асе
NowFoldor
Ыiс
~
Rtv•ol tn Explor~
Partyinvi tes {
class Startup {
ft R.
!+
Opon 11'1 Command Prompt
ft+
рuЫ ic
void Configur•St
servic•s .Addl·lvc() ·
F1nd 1n Fold•r
puЫic
Сору
С• Startup.
с
Сору Ро
h
А
t•
•
ft•
void Configure
арр. Us •D•v• loperf
арр. UseStotusCode
арр. UseStoticFil
app .UseNvctHthDef
Reni1me
Dt~t•
Рис.
13.9.
и выбе­
(Новая папка). как показано на рис .
Создание новой папки
13.9.
368
Часть 1. Введение в инфраструктуру ASP.NET Соге MVC 2
Щелкнем правой кнопкой мыши на папке
в контекстном меню пункт
New File,
поместим в файл код С# из листинга
Visual Studio Code
в панели проводника, выберем
GuestResponse. cs
и
13.17.
Работа с редактором
Продукт
Models
установим имя файла в
Visual Studio Code
(и расширение С#, установленное ранее в главе) предлагает
полнофункциональные средства для редактирования файлов С#, а также файлов распро­
страненных веб-форматов, подобных
JavaScript, CSS и HTML. Во многих отношениях на­
MVC в Visual Studio Code имеет массу общего с тем же процессом в
Visual Studio: доступна поддержка lпtelliSense, кодирование цветом и подсветка
писание приложения
редакторе
ошибок (с советами по их исправлению).
Основной недочетом
Visual Studio Code
является отсутствие возможности настройки, осо­
бенно когда речь идет о форматировании кода. На момент написания главы были доступны
варианты конфигурации для других языков, но расширение С# не допускает настройки, что
несколько затрудняет работу, если предпочитаемый вами стиль написания кода не совпада­
ет со стилем, предлагаемым по умолчанию. Однако в целом редактор отличается быстрой
реакцией и легкостью в применении, так что написание приложений
или
Linux
Листинг
MVC
в среде
macOS
не выглядит второсортным процессом.
13.17.
Содержимое файла
GuestResponse. cs
из папки
Partyinvi tes/Мodels
using System.ComponentModel.DataAnnotations;
namespace Partyinvites.Models
class GuestResponse {
int id {get; set; )
[Required(ErrorMessage = "Please enter your name")]
puЫic
puЫic
11
puЫic
Пожалуйста,
введите свое имя
string Name { get; set; )
[Required(ErrorMessage
11
=
"Please enter your email address")]
Пожалуйста,
введите свой адрес электронной почты
[RegularExpression(".+\\@.+\\ .. +",
ErrorMessage = "Please eпter а valid email address")]
11
puЬlic
Пожалуйста,
[Required(ErrorMessage
=
11
puЫic
"Please enter your phone number")]
Пожалуйста,
введите свой номер телефона
string Phone { get; set; )
[Required(ErrorMessage
=
11
puЬlic
введите допустимый адрес электронной почты
string Email { get; set; )
"Please specify whether you'll attend")]
Пожалуйста,
укажите,
примете ли участие
bool? WillAttend ( get; set; )
Добавим в папку
Models
файл по имени
IReposi tory. cs
с определением интер­
фейса, приведенным в листинге
щей главе от приложения из
13.18. Самое важное отличие приложения в настоя­
главы 2 связано с тем, что мы планируем хранить данные
модели в постоянной базе данных. Интерфейс
IRepository
описывает, каким обра­
зом приложение будет получать доступ к данным модели. не указывая реализацию.
Глава 13. Работа с Visual Studio Code
Листинг
13.18.
Содержимое файла
IRepository.cs
из папки
369
Partyinvites/Models
using System.Collections.Gener ic;
namespace Partyinvites.Models {
puЫic
interface IRepository {
Responses {get;
IEnumeraЫe<GuestResponse>
void AddResponse(GuestResponse response);
Добавим в папку
Models
файл по имени
ApplicationDbContext. cs
в нем класс контекста базы данных (листинг
Листинг
и определим
13. 19).
13.19. Содержимое файла ApplicationDbContext.cs
из папки Partyinvi tes/Models
using Microsoft.EntityFramework Core;
namespace Partyinvites.Models {
puЫic
DbContext {
class ApplicationDbContext
puЫic
ApplicationDbContext() {}
protected override void OnConfiguring(DbContextOp tionsBuilder builder) {
builder.UseSqlite("Filena me=./Partyinvites.db");
puЫic
DbSet<GuestResponse> Invites [get; set;}
Средство
SQLite
хранит данные в файле, который указывается классом контекс­
та. В создаваемом примере приложения данные будут храниться в файле по имени
Partyinvites. db,
что определено в методе
OnConf iguring ().
Чтобы завершить набор классов, необходимых для хранения и доступа к данным
модели, требуется реализация интерфейса
контекста базы данных. Добавим в папку
IReposi tory,
Models файл по
и поместим в него код. представленный в листинге
Листинг
которая использует класс
имени
EFReposi tory. cs
13.20.
13.20. Содержимое файла EFRepository.cs из папки Partyinvites/Models
using System.Collections.Gener ic;
namespace Partyinvites.Models {
class EFRepository : IRepository
private ApplicationDbContext context = new ApplicationDbContext();
puЫic
puЫic IEnumeraЫe<GuestResponse>
Responses => context.Invites;
void AddResponse(GuestResponse response) {
context.Invites.Add(respo nse);
context.SaveChanges();
puЫic
Часть 1. Введение в инфраструктуру ASP.NET Саге MVC 2
370
Класс
главе
8
EFReposi tory
следует шаблону, который похож на тот, что применялся в
для настройки базы данных
класса
ConfigureServices ()
рый сообщает инфраструктуре
класса
EFReposi tory,
SportsStore.
Startup
В листинге
13.21
видно, что к методу
добавлен оператор конфигурирования. кото­
ASP.NET Core
о необходимости создания экземпляра
когда требуются реализации интерфейса
IReposi tory.
мощью средства внедрения зависимостей (рассматриваемого в главе
Листинг
13.21.
Конфигурирование хранилища в файле
из папки
using
using
using
using
using
using
using
using
using
с по­
18).
Startup. cs
Party!nvi tes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
Partyinvites.Models;
namespace Partyinvites {
puЫic class Startup {
puЫic
void ConfigureServices(IServiceCollection services)
services.AddTransient<IRepository, EFRepository>();
services.AddMvc();
puЫic
void Configure(IApplicationBuilder
арр,
IHostingEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
Создание базы данных
В оставшихся главах книги всякий раз, когда нужно продемонстрировать функци­
ональность, требующую постоянства данных. используется средство
представляет собой упрощенную версию
ступно только в среде
Windows,
Microsoft SQL Server.
LocalDB. которое
LocalDB до­
ASP.NET Саге MVC
Но средство
поэтому при создании приложений
на других платформах понадобится какая-то альтернатива. Наилучшей альтернати­
вой
LocalDB
является
SQLite -
не нуждающаяся в конфигурировании межплатфор­
менная система управления базами данных. Она может встраиваться в приложения.
и для нее компания
Microsoft
включила поддержку в
Entity Framework Core.
которая и
представляет собой уровень доступа к данным, обычно применяемый в приложениях
ASP.NET Core МVС. В последующих разделах мы рассмотрим процесс добавления базы
данных SQLite в проект и ее использование в качестве хранилища данных для ответов
на приглашения поучаствовать в вечеринке.
Глава 13. Работа с Visual Studio Code
Использование
Одна из причин того, что
LocalDB
SQLite
при разработке
является настолько полезным инструментом, связана с
возможностью разработки с применением механизма баз данных
лает переход в производственную среду
рисков.
371
SQL Server
SQL Server,
который де­
простым и почти полностью лишенным
великолепная база данных, но она не очень хорошо подходит для круп­
SQLite -
номасштабных веб-приложений, а потому при развертывании приложения MVC требуется
переход на другую базу данных. Изменения в конфигурации могут быть упрощены с ис­
пользованием средств конфигурирования, которые будут описаны в главе
14,
но приложе­
ние должно быть тщательно протестировано в испытательной среде, чтобы выявить любые
отличия, привносимые производственной базой данных.
https: / /www. sqli te. org /whentouse. html, если вы не
SQLite в производственной среде. Там приведен обзор ситуа­
SQLite может быть хорошим вариантом, а когда нет.
Почитайте статью по адресу
уверены в том, применять ли
ций, когда база данных
SQLite не поддерживает полный набор измене­
Entity Framework Core способна генерировать для дру­
проблема, когда SQLite используется при разработке, пос­
Важно принимать во внимание тот факт, что
ний схемы, которые инфраструктура
гих баз данных. В целом это не
кольку вы можете удалить файл базы данных и сгенерировать новый файл с чистой схемой.
Тем не менее, ситуация усложняется, если вы обдумываете развертывание приложения, в
котором применяется
SQLite.
Если вы хотите использовать ту же самую базу данных в среде разработки и в производствен­
Entity Framework
https: / /docs .microsoft. com/ru-ru/ef/core/
момент написания главы список был относительно коротким, но в Microsoft
ной среде, тогда ознакомьтесь со списком поддерживаемых инфраструктурой
Соге баз данных по адресу
providers/.
На
объявили о поддержке баз данных, которые больше подходят для процесса разработки, чем
SQLite,
и могут функционировать также на платформах, отличающихся от
Windows.
Добавление пакета базы данных
Пакет
NuGet,
содержащий инструменты командной строки, которые используются
для создания и применения миграций базы данных, должен быть добавлен в проект
вручную. Откроем файл
ный в листинге
Листинг
13.22.
13.22
Partyinvi tes. csproj
и добавим в него элемент, выделен­
полужирным.
Добавление пакета
из папки
NuGet
Partyinvi tes
в файле
Partyinvi tes. csproj
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<DotNetCliToolReference Include="Мicrosoft.EntityFrameworkCore.Tools.DotNet"
Version="2.0.0" />
</ItemGroup>
</Project>
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
372
Сохраним внесенные изменения и. находясь в папке
манду, показанную в листинге
13.23,
Partyinvi tes,
запустим ко­
чтобы обеспечить загрузку и установку нового
пакета.
Листинг
13.23.
Установка пакета
NuGet
dotnet restore
Создание и применение миграции базы данных
Процедура создания базы данных следует тому же самому процессу. который ис­
пользуется в
Vlsual Studlo.
ним команду из листинга
Листинг
13.24.
Чтобы создать начальную миграцию базы данных, выпол­
13.24,
находясь в папке
Partyinvi tes.
Создание начальной миграции базы данных
dotnet ef migrations add Initial
Инфраструктура
Entlty Framework Core
создаст папку по имени М i
g r а t i on s,
содержащую файлы классов С#, которые будут использоваться для настройки схе­
мы базы данных. Чтобы применить эту миграцию базы данных, запустим в папке
Partyinvites команду
Partyinvites.
из листинга
13.25,
которая создаст базу данных внутри пап­
ки
Листинг
13.25.
Применение миграции базы данных
dotnet ef database update
Visual Studio Code не включает поддержку для инспектирования баз данных
http: //sqlitebrowser.org можно найти великолепный
инструмент с открытым кодом для Windows. macOS и Llnux.
Среда
но на веб-сайте
SQLite.
Создание контроллеров и представлений
В настоящем разделе мы добавим в приложение контроллер и представления.
Partyinvi tes/Controllers и добавления в нее файла
HomeController. cs с определением, показанным в листинге 13.26.
Начнем с создания папки
класса по имени
Листинг
13.26.
Содержимое файла
из папки
using
using
using
using
HomeCon troller. cs
Partyinvi tes/Controllers
System;
Microsoft.AspNetCore.Mvc;
Partyinvites.Models;
System.Linq;
namespace Partylnvites.Controllers
puЫic class HomeController : Controller
private IRepository repository;
HomeController(IRepository repo) =>
this.repository = repo;
puЫic
Глава 13. Работа с Visual Studio Code
ViewResult Index() {
int hour = DateTime.Now.Hour;
ViewBag.Greeting = hour < 12 ? "Good Morning"
return View ( "MyView");
373
puЫic
"Good Afternoon";
[HttpGet)
ViewResul t Rsvpform () => View ();
puЫic
[HttpPost)
ViewResult Rsvpform(GuestResponse guestResponse)
if (ModelState. IsValid) {
repository.AddResponse(guestResponse);
return View("Thanks", guestResponse);
else {
puЫic
//Имеется ошибка проверки достоверности
return View();
ViewResult ListResponses() =>
View(repository.Responses.Where(r => r.WillAttend
puЬlic
true));
Для установки встроенных вспомогательных функций дескрипторов создадим пап­
ку
Partyinvi tes/Views
и добавим в нее файл по имени_ Viewimports.
торый содержит выражение, приведенное в листинге
Листинг
cshtml,
ко­
13.27.
13.27. Содержимое файла_Viewimports. cshtml
из папки Partyinvi tes/Views
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Partyinvites/Views/Home и добавим в нее файл по
MyView. cshtml. в котором определяется представление, выбираемое мето­
дом действия Index () из листинга 13.26. Поместим в него разметку, показанную в
листинге 13.28.
Далее создадим папку
имени
Листинг
13.28. Содержимое файла МyView. cshtml
из папки Partyinvi tes/Views/Home
@{
Layout = null;
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" cor1tent="width=device-width" />
<title>Index</title>
<link rel="stylesheet" href="/liЬ/bootstrap/dist/css/bootstrap.css" />
</head>
<body class="p-2">
<div class="text-center">
374
Часть 1. Введение в инфраструктуру ASP.NET Соге MVC 2
<hЗ>We're going to have an exciting party!</hЗ>
<h4>And you are invited</h4>
<а class="btn btn-primary" asp-action="RsvpForm">RSVP Now</a>
</div>
</body>
</html>
Добавим в папку
Part.yinvites/Views/Home
с содержимым, приведенным в листинге
13.29.
файл по имени
RsvpForm.cshtml
Это представление предлагает НТМL­
форму, которая будет заполняться пользователем, чтобы принять или отклонить при­
глашение на вечеринку.
Листинг
13.29. Содержимое файла RsvpForm. cshtml
из папки Partyinvi tes/Views/Home
@model Partyinvites.Models.GuestResponse
@(
Layout = null;
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>RsvpForm</title>
<link rel="stylesheet" href="/liЬ/bootstrap/dist/css/bootstrap.css" />
</head>
<body>
<div class="m-2">
<div class="text-center"><h4>RSVP</h4></div>
<form class="p-1" asp-action="RsvpForm" method="post">
<div asp-validation-summary="All"></div>
<div class="form-group">
<label asp-for="Name">Your name:</label>
<input class="form-control" asp-for="Name" />
</div>
<div class="form-group">
<label asp-for="Email">Your email:</label>
<input class="form-control" asp-for="Email" />
</div>
<div class="form-group">
<label asp-for="Phone">Your phone:</label>
<input class="form-control" asp-for="Phone" />
</div>
<div class="form-group">
<label>Will you attend?</label>
<select class="form-coпtrol" asp-for="WillAtteпd">
<optioп value="">Choose an option</option>
<option value="true">Yes, I'll Ье there</option>
<option value="false">No, I can' t come</option>
</select>
</div>
Глава 13. Работа с Visual Studio Code
375
<div class="text-center">
<button class="btn btn-primary" type="submit">
Submit RSVP
</button>
</div>
</form>
</div>
</body>
</html>
Следующий файл представления называется
папке
Partyinvi tes/Views/Home
Thanks. cshtml,
также создается в
и имеет содержимое, показанное в листинге
13.30,
которое отображается. когда гость отправляет свой ответ.
Листинг
13.30.
Содержимое файла
из папки
Thanks. cshtml
Partyinvi tes/Views/Home
@model Partyinvites.Models.GuestResponse
@{
Layout = null;
< ! DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Thanks</title>
<link rel="stylesheet" href="/liЬ/bootstrap/dist/css/bootstrap.css" />
</head>
<body class="text-center">
<р>
<hl>Thank you, @Model.Name!</hl>
@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.
</р>
Click <а asp-action="ListResponses">here</a>
to see who is coming.
</body>
</html>
Финальный файл представления имеет имя
ListResponses. cshtml и подобно
Partyinvites/Views/Home.
другим представлениям в примере добавляется в папку
Он отображает список ответов гостей, используя разметку из листинга
Листинг
13.31.
Содержимое файла
из папки
@model
ListResponses. cshtml
Partyinvi tes/Views/Home
IEnumeraЬle<Partyinvites.Models.GuestResponse>
@{
Layout
=
null;
13.31.
376
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
< ! DOCTYPE html>
<html>
<head>
<meta name = " viewport " content="width=device- widt h" />
<l ink rel="stylesheet" href=" / l i Ь/bootstrap /dist /css / b oo t strap.c ss" />
<title>Responses</title>
</he ad >
<body>
<div class="m-1 р-1" >
<h2>Here is the list of people attending the part y</h 2>
<tаЫе clas s = "taЫe taЬle-sm taЬle-striped taЫe -bord ered">
<thead>
<tr><th>Name</th><th>Emai l< /th><th>Ph one< / th></tr >
</thead>
<t body>
@foreac h (Partyinvites .Mode ls. Gue s t Response r in Mo del ) {
<t r ><td>@r.Name</td> <td>@r.Email </ td>< td >@r.Phone</td>< / tr >
</tbody>
< /tаЫе >
</div>
</bod y >
</ html>
Выполним команду
dotnet run
в папке
проект и запустить исполняющую среду
приложение можно , перейдя на
Partylnvi te s,
чтобы скомпилировать
Увидеть в работе завершенное
ASP.NET Саге.
(рис.
http : / / localhost: 50 0 0
13. 10).
RSVP
We're goi11g to have an е)
~~
'
• •
Thank you,
'1
t
;6
.~
tl'. "".
ВоЫ
Here is the list of people attending the party
Рис.
13.10.
Процес с модульного тестирования в
Visual Studio.
......
666-12.]..4
i)QЬ~mpi~C('(n
\SS·-4'567
Выполнение завершенного приложения
Модульное тестирование в
процесс в
.......
Жl!'t:~«nplrШ'fl
Visual Studio Code
Visual Studio Code
похож на аналогичный
П ервым делом понадобится создать отдельный проект для
модульного тестирования.
Глава 13. Работа с Visual Studio Code
Создадим внутри папки
InvitesProject папку по имеЮ1 Tests
ду. представленную в листинге
Листинг
13.32.
13.32,
377
и запустим в ней коман­
которая создаст проект модульного тестирования.
Создание проекта модульного тестирования
dotnet new xunit --language
Запустим в папке
Tests
С#
--framework netcoreapp2.0
команду из листинга
13.33, чтобы добавить ссылку на про­
ект приложения, сделав содержащиеся в нем классы доступными для тестирования.
Листинг
13.33. Добавление ссылки на проект приложения
dotnet add reference .. /Partylnvites/Partylnvite s.csproj
Создание модульного теста
Модульные тесты создаются так, как было описано в главе
7.
Добавим в папку
Tests новый файл класса по имени HomeControllerTests. cs с содержимым, пока­
занным в листинге
Листинг
13.34.
13.34.
Содержимое файла
HomeControllerTests. cs
из папки
Tests
using System;
using System.Collections.Gener ic;
using Partylnvites.Controllers;
using Partylnvites.Models;
using Xunit;
using Microsoft.AspNetCore.Mvc ;
using System.Linq;
namespace Tests {
puЫic class HomeControllerTests
[Fact]
puЫic void ListActionFiltersNonAtten dees()
11
Организация
HomeController controller = new HomeController(new FakeRepository());
11
Действие
ViewResult result = controller.ListResponses ();
//Утверждение
Assert.Equal(2,
(result.Model as
IEnumeraЫe<GuestResponse>)
class FakeRepository : IRepository {
puЫic
IEnumeraЫe<GuestResponse>
new List<GuestResponse> {
new GuestResponse { Name
new GuestResponse { Name
new GuestResponse { Name
Responses =>
WillAttend = true } ,
"Alice", WillAttend = true } ,
false }
"Joe", WillAttend
"БоЬ",
};
void AddResponse(GuestResponse response)
throw new NotlmplementedException( );
puЬlic
.Count());
378
Часть 1. Введение в инфраструктуру ASP.NET Core MVC 2
Мы создали стандартный тест
xUnit,
который проверяет действие
в контроллере Ноте и корректно фильтрует в хранилище объекты
значением свойства
WillAttend,
равным
ListResponses
GuestResponse со
false.
Прогон тестов
Чтобы прогнать модульные тесты в проекте, запустим в папке
приведенную в листинге
Листинг
13.35.
Tests
команду,
13.35.
Прогон модульных тестов
dotnet test
Все тесты в проекте выполнятся. в результате чего отобразится вывод вроде пока­
занного ниже:
Starting test execution, please wait ...
[xUnit.net 00:00:00.6731479)
Discovering:
[xUnit.net 00:00:00.7900132]
Discovered:
[xUnit.net 00:00:00.8432715]
Starting:
[xUnit.net 00:00:00.9967614]
Finished:
Tests
Tests
Tests
Tests
Total tests: 2. Passed: 2. Failed: О. Skipped:
Test Run Successful.
Test execution time: 1.6974 Seconds
О.
В выводе присутствуют результаты двух тестов. поскольку шаблон проекта вклю­
чает файл по имени
UnitTestl.cs,
который содержит пустой модульный тест.
Упомянутый файл можно удалить, как демонстрировалось в главе
7.
Резюме
В главе был предложен краткий обзор работы со средой
Visual Studio Code -
лег­
ковесным инструментом разработки, который поддерживает создание приложений
ASP.NEТ
Core MVC
в
Windows, macOS и Linux. Среда Visual Studio Code пока еще не яв­
Visual Studio, но предоставляет основные средства,
построении приложений МVС, и расширяется компанией Microsoft
ляется заменой полного продукта
необходимые при
в ежемесячных выпусках.
Итак. первая часть книги завершена. Во второй части мы начнем погружение в
детали и посмотрим. как работают средства, которые использовались для создания
примера приложения.
ЧАСТЬ 11
Подробные сведения
об инфраструктуре
ASP.NET Core
МVС
2
моменту вы уже знаете, для чего существует инфраструкту­
к настоящему
ра .NET Саге MVC, а также понимаете ее архитектуру и лежащие в основе
проектные цели. Вы получили эти знания благодаря построению реалистичного
приложения электронной коммерции. Наступило время заняться исследованием
внутреннего устройства инфраструктуры.
Во второй части книги мы погрузимся в детали, начав с изучения структуры при­
ложения
ASP.NET
Саге
MVC
и способа обработки запросов. Затем мы сосредо­
точим внимание на индивидуальных средствах, таких как маршрутизация, конт­
роллеры и действия, система представлений
MVC
и вспомогательных функций
дескрипторов, а также особенности работы с моделями предметной области.
ГЛАВА
14
Конфигурирование
v
приложении
она открывает много ас­
т ема конфигурации может показаться неинтересной, но MVC
пектов, связанных с функционированием приложений
и обработкой НТГР­
запросов. Вы не должны поддаваться соблазну пропустить текущую главу. Вы обя­
заны выделить время на изучение способа. которым система конфигурации придает
форму веб-приложениям
MVC.
Данная тема образует прочный фундамент для пони­
мания материала последующих глав.
MVC, и демонстрируется.
MVC опирается на средства. предоставляемые плат­
14.1 приведена сводка. позволяющая поместить конфи­
В главе объясняется, как конфигурировать приложения
каким образом инфраструктура
формой
ASP.NET Core.
В табл.
гурирование приложений в контекст.
Таблица
14.1 .
Помещение системы конфигурации в контекст
Вопрос
Ответ
Что это такое?
Классы
Program и Startup и файлы JSON используются для
конфигурирования работы приложения, а также указания паке­
тов, от которых оно зависит
Чем она полезна?
Система конфигурации позволяет подстраивать приложения
под их среды и управлять зависимостями от пакетов
Как она используется?
Самым важным компонентом является класс
Startup, кото­
рый применяется для создания служб (объектов, предостав­
ляющих общую функциональность повсюду в приложении) и
компонентов промежуточного программного обеспечения (ПО),
используемых для обработки НТТР-запросов
Существуют ли какие-то
В сложных приложениях конфигурация может стать трудной в
скрытые ловушки или
управлении. В разделе "Работа со сложными конфигурациями"
ограничения?
далее в главе описаны средства
ASP.NET, предназначенные для
решения такой проблемы
Существуют ли
Нет. Система конфигурации
альтернативы?
и средство настройки приложений
В табл.
14.2
представлена сводка по главе.
- это неотъемлемая часть ASP.NET
MVC
Глава 14. Конфигурирование приложений
Таблица
14.2.
Сводка по главе
Задача
Решение
Добавление функциональности в
Добавьте пакеты
Листинг
NuGet
приложение
.csproj
Управление инициализацией прило­
Используйте класс
жения
381
в файл
14.5-14.8
14.9-14.11
Program
ASP.NET
Конфигурирование приложения
14.12, 14.13
Применяйте методы
ConfigureServices() и
Configure () класса Startup
Создание общей функциональности
14.14-14.16
Используйте метод
ConfigureServices ()
для
создания служб
Генерация ответов с содержимым
Создайте промежуточное ПО для
14. 17-14. 19
генерации содержимого
Предотвращение прохождения за­
Создайте промежуточное ПО для
просов через конвейер запросов
обхода
Редактирование запроса перед его
Создайте промежуточное ПО для
обработкой другими компонентами
редактирования запросов
14.20, 14.21
14.22-14.24
промежуточного ПО
Редактирование ответа, который был
обработан другими компонентами
Создайте промежуточное ПО для
14.25, 14.26
редактирования ответов
промежуточного ПО
Применяйте метод
UseMvc () или
UseMvcWithDefaultRoute()
14.27
Изменение конфигурации приложе­
Используйте службу среды
14.28
ния для разных сред
размещения
Настройка функциональности
MVC
Обработка ошибок в приложении
Используйте промежуточное ПО для
14.29, 14.30
обработки ошибок в среде разработки или производственной среде
Управление несколькими браузерами
Применяйте средство
во время разработки
(Ссылка на браузер)
Включение файлов изображений,
JavaScript
и
CSS
Browser Link
Включите промежуточное ПО для
14.32
статического содержимого
Отделение данных конфигурации
Создайте внешние источники кон-
от кода С#
фигурации, такие как файлы
Регистрация данных приложения
Применяйте промежуточное ПО для
в журнале
регистрации в журнале
Подготовка системы внедрения зави­
Отключите проверку области
симостей для использования
действия
с
14.31
14.33-14.35
JSON
14.36-14.38
14.39
Entity Framework Core
Конфигурирование служб
MVC
Используйте средства параметров
14.40
кон фигурации
Конфигурирование сложных
Применяйте несколько внешних
приложений
файлов или классов
14.41-14.45
382
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Подготовка проекта для примера
В текущей главе мы создадим новый проект по имени
зованием шаблона
Empty
ConfiguringApps
с исполь­
(Пустой). Мы будем конфигурировать приложение позже в
главе, но есть несколько базовых средств. которые необходимо поместить на свои мес­
та в плане подготовки к предстоящим изменениям.
Мы собираемся применять
му создадим файл
Bower),
(Файл конфигурации
Листинг
14.1.
Bootstrap
bower. j son,
Добавление
для стилизации НТМL-содержимоrо, поэто­
используя шаблон элемента
Bower Configuration File
14.1.
и добавим в него пакет, как показано в листинге
Bootstrap
в файле
bower. j son
из папки
ConfiguringApps
"name": "asp.net",
"private": true,
"dependencies": {
"bootstrap": "4. О. 0-alpha. 6 11
Создадим папку
Controllers
HomeController. cs
Листинг
14.2.
и поместим в нее файл класса по
с определением контроллера. приведенным в листинге
Содержимое файла
HomeController.cs
из папки
имени
14.2.
Controllers
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace ConfiguringApps.Controllers
puЫic
class HomeController : Controller
ViewResult Index()
=> View(new Dictionary<string, string>
["Message"] = "This is the Index action"
puЬlic
}) ;
Создадим папку
Index. cshtml
Views/Home
и добавим в нее файл представления по имени
с содержимым. показанным в листинге
Листинг 14.З. Содержимое файла
Index. cshtml
14.3.
из папки
Views/Home
@model Dictionary<string, string>
@{ Layout = null;}
< ! DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<link asp-href-include="liЬ/bootstrap/dist/css/*.min.css" rel="stylesheet" />
<title>Result</title>
</head>
Глава 14. Конфигурирование приложений
383
<body class= "p- 1" >
< tаЫе class="taЫe
taЫe- co nde n sed taЫe- border ed taЫ e-stripe d" >
@foreach (var kvp in Model ) {
<tr><th>@kvp.Key</th><td>@kvp.Value</td>< / tr>
< / tаЫе>
</body>
</html >
При выборе СSS-файлов
Bootstrap
элемент
link
в представлении полагается
на встроенную вспомогательную функцию дескриптора. Чтобы включить встро­
Vi ews файл
MVC View lmports Page
енные вспомогательные функции дескрипторов . со здадим в папке
_ Vie wlmpo rts . csh tml
с применением шаблона элемента
(Страница импортирования представлений
листинга
Листинг
MVC)
и поместим в него выражение из
14.4.
14.4.
Содержимое файла
_ Viewimports. csh tml
из папки
Views
@addTagHelper *, Microso ft . AspNetCor e.Mvc .TagHe l pe rs
В результате запуска приложения отобразится сообщение. представленное на
рис.
14.1 .
CJ
l0<•lhostб5417
С
Х
ф localhost 1>~41 ,
Hello World !
L
Рис.
14. 1.
Выполнение примера приложения
Конфигурирование проекта
cspro j . ко­
ASP.NET
ConfiguringApp s . csproj .
Самым важным конфигурационным файлом является < имя_ проекта > .
торый заменяет файл
proj ect . j son .
используемый в более ранних версиях
Core. Такой файл, который в проекте примера наз ывается
среда Visual Studio скрывает и разрешает к нему доступ за счет щелчка правой кноп­
кой мыши на элементе проекта в окне Solution Explorer и выбора в контекстном меню
пункта Edit ConfiguringApps.csproj (Редактировать Configur ingApps. cspro j). В лис­
тинге 14.5 приведено начальное соде ржимое файла Co nfigu ringApp s . cspro j, кото­
рое было создано Visual Studio как часть шаблона проекта Empty.
Листинг
14.5.
Содержимое файла
из папки
ConfiguringApps. csproj
ConfiguringApps
<P r oj ect Sdk="Microsoft.NET.Sdk.Web">
<Prope rtyGroup>
<TargetFramework>ne t c oreapp 2.0 </Target Fr amewo rk>
</PropertyGr o up>
384
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>
</Project>
. csproj
Файл
MSBuild,
применяется для конфигурирования инструмента
рый используется при построении проектов
помощью элементов
XML.
а в табл.
14.3
.NET.
кото­
Конфигурирование производится с
описаны элементы. находящиеся в стандарт­
ном конфигурационном файле. В более поздних примерах будут применяться другие
но, чтобы начать работу над проектом
14.3 элементов
ASP.NET Core MVC.
Таблица
XML
элементы конфигурации, но представленных в табл.
14.3.
Элементы конфигурации
в стандартном файле
вполне достаточ­
. csproj
Элемент
Описание
Project
Это корневой элемент, который обозначает конфигурационный
файл
MSBuild. Атрибут Sdk устанавливается в Microsoft. NET.
Sdk. Web,
чтобы предоставить набор неявно импортируемых паке­
тов, требующихся для построения проекта
PropertyGroup
Этот элемент группирует связанные конфигурационные свойства в
целях придания файлу структурированности
TargetFramework
Этот элемент указывает целевую платформу
.NET Framework
для процесса построения и должен быть определен внутри
PropertyGroup. Стандартным значением
netcoreapp2. О, направляющее на .NET Core 2.0
элемента
ItemGroup
является
Этот элемент группирует связанные конфигурационные элементы в
целях придания файлу структурированности
Folder
Этот элемент сообщает
MSBuild о том, как иметь дело с той или
14.5 указывает
иной папкой в проекте. Данный элемент в листинге
инструменту
MSBuild о необходимости включения папки wwwroot,
когда приложение публикуется
PackageReference
Этот элемент используется для указания зависимости от пакета
NuGet, который идентифицируется через атрибуты Include и
Version.
Пакет
Microsoft. AspNetCore. All
применяется для
обеспечения доступа ко всем индивидуальным пакетам, которые
предоставляют функциональность
ASP.NET Core
и
MVC Framework
Добавление пакетов в проект
. csproj - составить
Visua\ Studio обнаруживает
Самая важная роль файла
сит проект. Когда среда
список пакетов, от которых зави­
изменение в файле
. csproj.
она
инспектирует список пакетов, загружает любые вновь добавленные пакеты и удаляет
любые пакеты. которые больше не нужны.
С выпуском ASP.NET Core 2 вся базовая функциональность. требующаяся для
ASP.NET Core MVC, MVC Fгamework и Entity Framework Core. включена в мета-пакет
Глава 14. Конфигурирование приложений
Microsoft. AspNetCore. All -
385
удобная возможность , которая позволяет избежать
необходимости начинать разработку приложения с добавления в проект длинного
списка пакетов
NuGet.
Но даже при таких условиях по-прежнему придется добавлять пакеты
NuGet
для
сторонних или расширенных функциональных возможностей. Существует три спосо­
- выбор пункта меню Toolsq NuGet
NuGet Packages for Solutioп (Сервисq Диспетчер пакетов
пакетами NuGet для решения). что позволит управлять пакетами
ба добавления пакетов в проект. Первый способ
Package
Maпagerq Мапаgе
NuGеtqУправлять
NuGet
через легкий в использовании интерфейс. Для новичков в мире разработки
приложений
.NET
такой подход будет наилучшим, потому что он снижает вероят­
ность ошибки при выборе пакета.
Например, чтобы добавить пакет
System. Net. Ht tp,
который обеспечивает под ­
держку отправки (а не получения) запросов НТТР, можно перейти на вкладку
(Обзор) окна диспетчера пакетов
NuGet,
список доступных версий, в том числе предварительных версий (рис .
lnstalled
Updates
Browse
произвести поиск по имени и увидеть полный
14.2).
Manage Packages for Solution
Consolidate
13.1М .S......lo•d•
Pro\1dts 1 progr"mmmg inttrf4<tfoc modttn НТТР "ppftotions. 1ncludingНTTP clttnt
compon.ntJ thlt .Моw 1pphat.юns. to consumt wtЬ strvicб ovtt НТТР and НПР cc.w
System.Net.Http ЬyMkroюf\
\'<J
System.Net.Primitlves Ьу Мюоюfl.
114.З.О
12.4М downlo•:h
Provida common typ~ for networ\::·b•Ш imra1~. 1rкludlng Systf.m..Net.19AddrltS<;.
Sysltm.Нtt IPE.ndPoН\r,. 1nd Sy~tm.Nd Coo~ieCon1JW'ltf,
System.Not.Sockets
Ьу M1ao'°ft, 8.6 1М d""'nlo•d•
PtovIOts cl1sS6 wch 1s Sod.et. ТcpOttnt and UdpCl1Мl., wh1ch tnгblt devtlopeis to
stnd 1nd tt<.t1lf't d.rt• over the networt.
v4.3.0
runtime.native.-System.Net.Ht1p Ьу Mкrosoft. 6.09М downlo1ds
-AJ.O
lnttm.111 m'lplem1ntation pг<Qgt nct munt fOf d11t.ct conwmpnon . Plost do 1\ot
rtftttrк.t dirюly.
Syst•m.Net.Http.Formarting.Ext•nsion Ьу •nd«.'<J°"'""° 687К d""nlo.od vi.2.3
lnstьll
bltiten A.s:.1.rnblySysttm.Ne-t.Нttp, Form•tt1ng.dO
.m et.Ftp 1;,,nt.A :ync tr
Рис.
14.2.
Добавление пакета с применением диспетчера пакетов
NuGet
Далее необходимо выбрать пакет интересующей версии, указать проект, для ко­
торого требуется этот пакет, и щелкнуть на кнопке lпstall (Установить). Среда
Studio
загрузит пакет и обновит файл
Visual
. csproj .
Добавлять пакеты можно также с использованием командной строки. хотя в таком
случае нужно знать имя пакета (и в идеале его версию) . В листинге
манда. которую пришлось бы выполнить, находясь в папке
добавить в проект пакет
Листинг
System. Net. Http.
14.6. Добавление пакета в проект
dotnet add pa c kage System .Net .H ttp -- version 4 . 3 .2
14.6
показана ко ­
Configur i.ngApps,
чтобы
386
Часть 11. Подробные сведения об инфраструктуре ASP.NEТ Core MVC 2
Как диспетчер пакетов
менты
NuGet, так и команда dotnet add package добавляют эле­
PackageReference в файл . csproj. При желании пакеты можно добавлять
за счет редактирования конфигурационного файла, вручную добавляя в него элемен­
ты
PackageReference.
Подход является самым прямым, но требует внимательности
во избежание неправильного набора имен пакетов или указания для них несуществу­
ющих номеров версий. В листинге
Nеt.Нttрвфайле
Листинг
14. 7.
14.7 демонстрируется
добавление пакета
System.
.csproj.
Добавление пакета в файле
ConfiguringApps. csproj из папки
ConfiguringApps
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="System.Net.Http" Version="4.3.2" />
</ItemGroup>
</Project>
Элемент
PackageReference имеет атрибут Include,
Version, где задается номер версии.
в котором указывается имя
пакета, и атрибут
Добавление пакетов инструментов в проект
В то время как обычные пакеты допускается добавлять различными способами, оп­
ределенные пакеты расширяют диапазон задач, которые можно выполнять с помощью
инструмента командной строки
. csproj
dotnet,
и такие пакеты требуют применения в файле
элемента другого вида. Речь идет об элементе
указываемом вместо элемента
PackageReference,
DotNetCli ToolReference,
который используется пакетами
средств. применяемых приложением напрямую. Пакеты подобного рода могут добав­
ляться в проекты только путем непосредственного редактирования файла
В листинге
14.8 к проекту добавляется
. csproj.
пакет, который делает возможным создание
и применение миграций базы данных с использованием команд
dotnet ef,
как было
показано в первой части книги.
Листинг
14.8.
Добавление пакетов инструментов в файле ConfiguringApps. csproj
из папки
ConfiguringApps
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
Глава 14. Конфигурирование приложений
387
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
<PackageReference Include="System.Net.Http" Version="4.3.2" />
<DotNetCliToolReference
Include="Мicrosoft.EntityFra.iaeworkCore.Tools.DotNet"
Version="2.0.0
</ItemGroup>
11
/>
</Project>
При добавлении пакетов инструментов в проект можно либо поместить элемен­
DotNetCli ToolReference в тот же самый элемент ItemGroup, где находятся
PackageReference, что и было сделано в листинге 14.8, либо
создать отдельный элемент ItemGroup. После сохранения изменений, внесенных в
файл . csproj, среда Visual Studio загрузит и установит пакеты и задействует их для
конфигурирования инструмента командной строки dotnet.
ты
обычные элементы
Класс
Класс
Program
Program
определяется в файле по имени
Program. cs
и предоставляет точ­
ку входа для запуска приложения, снабжая инфраструктуру .NЕТ методом
Main ().
который может быть выполнен для конфигурирования среды размещения и выбора
класса. завершающего конфигурирование приложения
содержимого класса
тов: в листинге
14.9
Program
ASP.NET Core.
Стандартного
вполне достаточно для разработки большинства проек­
приведен стандартный код, который среда
Visual Studio
добав­
ляет в проекты.
Листинг
using
using
using
using
using
using
using
using
using
14.9.
Стандартное содержимое файла
Progra.ia.cs
из папки
System;
System.Collections.Generic;
System.IO;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore;
Microsoft.AspNetCore.Hosting;
Microsoft.Extensions.Configuration;
Microsoft.Extensions.Logging;
namespace Conf iguringApps {
puЫic class Program {
static void Main ( string [] args) {
BuildWebHost(args) .Run();
puЫic
static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
puЫic
ConfiguringApps
388
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Метод
ложения
метода
обеспечивает точку входа. которую должны предоставлять все при­
Main ()
.NET,
чтобы иметь возможность выполняться исполняющей средой. Внутри
Main ()
в классе
Program
вызывается метод
BuildWebHost
(),который несет
ответственность за конфигурирование
ASP.NET Core.
Для конфигурирования ASP.NEТ Core метод BuildWebHost () применяет статичес­
кие методы. определенные в классе WebHost. С выходом ASP.NET Core 2 конфигури­
рование упростилось за счет появления метода
конфигурирует
ASP.NET Core
проектов. Метод
CreateDefaul tBuilder
(),который
с использованием настроек. подходящих большинству
UseStartup ()
вызывается с целью идентификации класса. кото­
рый будет предоставлять конфигурацию, специфичную для приложения: по соглаше­
нию применяется класс по имени
интерфейс
Run ( ) ,
Startup.
рассматриваемый позже в главе. Метод
обрабатывает все настройки конфигурации и создает объект. реализующий
Build ()
IWebHost
и возвращаемый методу
Main
(),на котором вызывается метод
чтобы начать обработку НТТР-запросов.
Анализ деталей конфигурации
Метод
ции
CreateDefaul tBuilder ()
ASP.NET Core.
является удобной отправной точкой конфигура­
но он скрывает много важных деталей, что может стать проблемой,
если необходимо изменить способ, которым конфигурируется приложение. В листин­
ге
14.10
вызов метода
CreateDefaul tBuilder ()
заменен отдельными операторами.
создающими стандартную конфигурацию.
Листинг
14.1 О.
Детальные операторы конфигурирования в файле
из папки
using
using
using
using
using
using
using
using
using
using
Program. cs
ConfiguringApps
System;
System.Collections.Generic;
System. IO;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore;
Microsoft.AspNetCore.Hosting;
Microsoft.Extensions.Configuration;
Microsoft.Extensions.Logging;
System.Reflection;
namespace ConfiguringApps {
puЫic class Program {
static void Main ( str ing [) args) {
BuildWebHost(args) .Run();
puЫic
static IWebHost BuildWebHost(string[] args) {
return new WeЬHostBuilder()
. UseKestrel ()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((hostingContext, config) => {
var env = hostingContext.HostingEnvironment;
confiq. AddJsonFile ( "appsettings. j son" , optional: true,
reloadOnChange: true)
.AddJsonFile($ 11 appsettings.{env.EnvironmentName}.json",
optional: true, reloadOnChanqe: true);
puЫic
Глава 14. Конфигурирование nриложений
389
if (env.IsDevelopment()) {
var appAssemЫy =
AssemЫy.Load(new AssemЬlyName(env.ApplicationName));
if
(appAssemЬly
!= null) {
config.AddUserSecrets(appAssemЬly,
optional: true);
config.AddEnvironmentVariaЬles();
if (args ! = null) {
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, logging) => {
logging.AddConfiguration(
hostingContext.Configuration.GetSection("Logging "));
logging.AddConsole();
logging.AddDebug();
})
.UseIISintegration()
.UseDefaultServiceProvider((context, options) =>
options. ValidateScopes =
context.HostingEnvironment.IsDevelopment();
})
.UseStartup<Startup>()
.Build();
Все методы конфигурирования, добавленные в метод
описаны в табл.
Таблица
BuildWebHost (),
кратко
14.4.
14.4. Стандартные методы конфигурирования ASP.NET Core
Имя
Описание
UseKestrel ()
Этот метод конфигурирует веб-сервер
няется ниже во врезке "Использование
UseContentRoot()
Kestrel, как объяс­
Kestrel напрямую"
Этот метод конфигурирует корневой каталог для прило­
жения, который применяется для загрузки файлов кон­
фигурации и доставки статического содержимого, такого
как файлы изображений,
ConfigureAppConfiguration()
JavaScript
и
CSS
Этот метод используется для подготовки конфигура­
ционных данных приложения, как описано в разделе
"Конфигурирование приложения" далее в главе
AddUserSecrets ()
Этот метод применяется для хранения конфиденциаль­
ных данных за пределами файлов кода; подробные све­
дения доступны по ссылке
https://docs.rnicrosoft.com/ru-ru/aspnet/
core/security/app-secrets?view=aspnetcore2. l&tabs=windows. Средство несколько неуклюжее и
потому оно в книге не используется
390
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Окончание табл.
14.4
Имя
Описание
ConfigureLogging()
Этот метод позволяет сконфигурировать регистрацию
в журнале для приложения, как объясняется в разделе
"Конфигурирование регистрации в журнале" позже в главе
UseIISintegration()
Этот метод включает интеграцию с
UseDefaultServiceProvider()
llS
и
llS Express
Этот метод применяется для конфигурирования внедрения
зависимостей, как описано в разделе "Конфигурирование
внедрения зависимостей" далее в главе
UseStartup ()
Этот метод указывает класс, который будет использо­
ваться для конфигурирования ASP.NEТ, как описано в
разделе "Класс
Startup"
далее в главе
Позже в главе бу.цут объясняться более сложные операторы из листинга
14.10.
а
пока мы удалим некоторые операторы конфигурирования, чтобы оставить только
базовую конфигурацию (листинг
Листинг
using
using
using
using
using
using
using
using
using
using
14.11. Упрощение
14. l l).
конфиrурации в файпе
Program. cs
из папки
ConfiguringApps
System;
System.Collections.Generic;
System. 10;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore;
Microsoft.AspNetCore.Hosting;
Microsoft.Extensions.Configuration;
Microsoft.Extensions.Logging;
System.Reflection;
namespace ConfiguringApps {
puЫic class Program {
static void Main(string[] args) {
BuildWebHost(args) .Run();
puЫic
static IWebHost BuildWebHost(string[] args) (
return new WeЬHostвuilder ()
. UseKestrel ()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISintegration()
.UseStartup<Startup>()
. Build ();
puЫic
Показанные в листинге
14. l l
операторы обеспечивают базовую конфигурацию,
которая будет работать для большинства приложений
ASP.NET Core MVC.
Другие опе­
раторы будут добавлены снова по мере объяснения функциональных средств, к кото­
рым они относятся.
Глава 14. Конфигурирование приложений
Использование
Kestrel
391
напрямую
Kestrel - это межплатформенный веб-сервер, спроектированный для выполнения приложе­
ний ASP.NET Саге. Он задействуется автоматически, когда запускается приложение ASP.NET
Core с применением llS Express (сервер, предоставляемый средой Visual Studio для исполь­
зования на стадии разработки) или полной версии llS, которая была традиционной веб­
платформой для приложений .NET.
При желании веб-сервер
Kestrel
также разрешено запускать напрямую, что означает воз­
можность выполнения приложений
ASP.NET Core MVC
на любой из поддерживаемых плат­
Windows, которое накладывает
llS. Существуют два способа запуска приложения с применением Kestrel. Первый из них щелчок на стрелке рядом с правой гранью кнопки llS Express в панели инструментов Visual
Studio и выбор элемента, который соответствует имени проекта. В результате откроется
новое окно командной строки, и приложение запустится с использованием Kestrel.
форм, обходя ограничение только операционной системой
Той же цели можно достичь, открыв собственное окно командной строки, перейдя в папку с
файлами конфигурации приложения (ту, которая содержит файл
. csproj)
и запустив сле­
дующую команду:
dotnet run
По умолчанию веб-сервер
Kestrel начинает прослушивать порт 5000 на предмет поступле­
ния НТТР-запросов. Если в проекте присутствует файл
j son,
Properties/launchSettings.
тогда настройки порта НТТР и среды для приложения будут читаться из него.
Класс
Класс
Startup
Program отвечает за быстрое начало
функционирования приложения, но бо­
лее важная работа по конфигурированию делегируется через метод
UseStartup ():
.UseStartup<Startup>()
При идентификации класса, который будет конфигурировать
ASP.NET Саге. метод
UseStartup () полагается на параметр типа. По соглашению этот класс имеет имя
Startup, которое применяется шаблонами проектов ASP.NET Core МVС. в том числе
шаблоном
Empty,
используемым для создания проекта примера в текущей главе.
Изучение работы класса
Startup позволяет уловить суть способа обработки НТТР­
MVC с остальными частями платформы
запросов и интеграции инфраструктуры
ASP.NET Core.
В настоящем разделе мы начнем с простейшего из возможных классов
Startup
и добавим средства для демонстрации влияния различных конфигурационных па­
раметров, получив в итоге конфигурацию. которая будет подходящей в большинстве
проектов
MVC.
Visual Studlo
функциональность, необходимую
Листинг
14.12.
14.12 показан класс Startup,
Empty, который настраивает всего лишь
В качестве отправной точки в листинге
добавляемый средой
к проектам
ASP.NET Core для обработки НТТР-запросов.
Начальное содержимое файла
using System;
using System.Collections.Generic;
using System.Linq;
Startup. cs из папки ConfiguringApps
392
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
using
using
using
using
using
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
namespace ConfiguringApps {
puЫic class Startup {
puЬlic
void ConfigureServices(IServiceCollection services) {
)
puЬlic
void Configure(IApplicationBuilder
арр,
IHostingEnvironment env)
{
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
app.Run(async (context) => {
await context.Response.WriteAsync("Hello World!");
) ) ;
В классе
Startup
определены два метода,
ConfigureServices ()
и
Configure (),
которые настраивают совместно используемые средства. требующиеся приложению.
и указывают ASP.NEТ о том, как они должны применяться.
Когда приложение запускается. инфраструктура ASP.NET Соге создает новый эк­
земпляр класса
Startup
и вызывает его метод
ConfigureServices (),
так что при­
ложение может создать свои службы. Как объясняется в разделе ··службы ASP.NEТ"
далее в главе. службы
-
это объекты. которые предоставляют функциональность дру­
гим частям приложения. Приведенное описание не отличается особой строгостью по
той причине. что службы могут использоваться для снабжения практически любой
функциональностью.
После того как службы созданы,
метода
ASP.NET
вызывает метод
Configure ().
Целью
является настройка конвейера запросов, который представляет
Configure ()
собой набор компонентов (называемых промежуточным программным обеспечени­
ем), используемых для обработки входящих НТТР-запросов и генерации ответов на
них. В разделе "Промежуточное программное обеспечение ASP.NET' далее в главе бу­
дут даны объяснения. как работает конвейер запросов, и продемонстрировано созда­
ние компонентов промежуточного ПО. На рис.
ASP.NET
с классом
Приложение
запускается
Рис.
14.3
иллюстрируется взаимодействие
Startup.
Создается
экземпляр
класса
14.3.
Startup
Вызывается метод
Configure
Services ()
Применение класса
Вызывается
Начинается
метод
обработка
Configure ()
запросов
Службы
Конвейер
созданы
подготовлен
Startup
инфраструктурой
для конфигурирования приложения
ASP.NET
Глава 14. Конфигурирование приложений
393
Класс
щение
Startup, который для всех запросов просто возвращает одно и то же сооб­
"Hello Wo r l d 1 ",не особенно полезен. поэтому перед подробным обсуждением
методов класса имеет смысл забежать чуть вперед и включить
Листинг
using
using
using
usi n g
u sing
using
u sing
using
MVC
(листинг
14.13).
14.13. Включение MVC в файле Startup. cs из папки ConfiguringApps
System;
System.Collections.Gene ric;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builde r ;
Microsoft. AspNetCore.Hos ti ng ;
Microsoft .AspNetCore.Htt p;
Microsoft.Extensions.Depende ncyinjecti on ;
namespace ConfiguringApps {
puЫi c c l ass St a rtup {
puЫic
void Co nfigureServices ( IServiceColle ct ion services ) {
services.AddМvc();
puЫic
void Configure(IApplica ti onBuilder
app.UseMvcWithDefaultRoute();
арр,
IHostingEnvironmen t env ) {
Благодаря таким добавлениям (которые объясняются в последующих разделах) в
нашем распоряжении оказывается достаточный объем инфраструктуры , чтобы об­
рабатывать НТТР-запросы и генерировать ответы с использованием контроллеров и
представлений. На рис.
14.4
показан результат запуска приложения .
D Result
С
1
1
х
Ф localhost '416
:Мessage Tl1is is the Index actioн
L _____ - - - Рис.
14.4.
Результат включениR
MVC
Обратите внимание. что содержимое не стилизовано. Минимальная конфигурация
в листинге
14. 13 не
предлагает ка кую -либо поддержку для обслуживания статическо­
го соде ржимого. такого как таблицы стилей
1i11 k
CSS
и файлы
JavaScript. В
в НТМL-разметке, визуализируемой представлением Iпdex.
ет запрос к таблице стилей
CSS из Bootstrap,
итоге элемент
csh trnl,
иницииру ­
который приложение не в состоянии об­
работать. что предотвращает получение требуемой стилевой информации. Проблема
будет устранена в разделе "Добавление оставшихся компонентов промежуточного
программного обеспечения·· далее в гла ве.
394
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Службы
ASP.NET
Инфраструктура
ASP.NET Core
вызывает метод
Startup. Conf igureServ ices ().
так что приложение может настроить требуемые службы. Термин "служба" относится
к любому объекту, который предоставляет функциональность другим частям приложе­
ния. Как уже отмечалось, такое описание не является строгим, поскольку службы спо­
собны делать для приложения все что угодно. В качестве примера создадим в проекте
папку
Infrastructure
и добавим в нее файл класса по имени
с определением, приведенным в листинге
Листинг
14.14.
Содержимое файла
UptirneService. cs
14.14.
UptimeService. cs из папки Infrastructure
using System.Diagnostics;
namespace ConfiguringApps.Infrastructure
puЫic
class UptimeService
private Stopwatch timer;
puЬlic
UptimeService() {
timer = Stopwatch.StartNew();
puЫic
long Uptime => timer.ElapsedMilliseconds;
Когда создается экземпляр класса
UptimeService.
его конструктор запускает тай­
мер, который отслеживает, сколько времени выполнялось приложение. Это хороший
пример службы, потому что класс
UptirneService
обеспечивает функциональность,
которая может применяться в остальных частях приложения и создается при запуске
приложения.
Службы
класса
ASP.NET регистрируются с использованием метода ConfigureServices ()
в листинге 14.15 показано. как зарегистрировать класс UptimeService.
Startup;
Листинг
14.15.
Регистрация специальной службы в файле
из папки
using
using
using
using
using
using
using
using
using
s tartup. cs
ConfiguringApps
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
ConfiguringApps.Infrastructure;
namespace ConfiguringApps {
puЫic class Startup {
puЬlic
void ConfigureServices(IServiceCollection services) (
services.AddSingleton<UptimeService>();
services.AddMvc();
Глава 14. Конфигурирование приложений
void Configure (IApplicationBuilder
app.UseMvcWithDefaultRoute();
puЫic
Метод
ConfigureServices () получает в
IServiceCollection.
реализует интерфейс
395
IHostingEnvironment env) {
арр,
качестве аргумента объект, который
Службы регистрируются с помощью
расширяющего метода, вызываемого на интерфейсе
IServiceCollection,
кото­
рому указываются разнообразные конфигурационные параметры. Доступные пара­
метры для создания служб будут описаны в главе
AddSingleton (),
UptimeService по
18,
а пока мы применяем метод
что означает совместное использование единственного объекта
всему приложению.
Службы тесно связаны со средством под названием внедрение зависимостей, ко­
торое позволяет компонентам вроде контроллеров легко получать службы и подробно
рассматривается в главе
18. Получить доступ к службам, зарегистрированным в мето­
Startup. ConfigureServices (),можно за счет создания конструктора, который
принимает аргумент с требуемым типом службы. В листинге 14.16 показан конструк­
тор, добавленный в контроллер Home для доступа к совместно используемому объекту
UptimeService, который был создан в листинге 14.15. Кроме того, обновлен метод
действия Index () контроллера. так что он включает в генерируемые данные пред­
ставления значение свойства Upda te службы.
де
Листинг 14.16. Доступ к службе в файле
из папки
HomeController.cs
Controllers
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using ConfiguringApps.Infrastructure;
namespace ConfiguringApps.Controllers
puЫic class HomeController : Controller
private UptimeService uptime;
HomeController(UptimeService up) => uptime
ViewResult Index()
=> View(new Dictionary<string, string> {
[ "Message"] = "This is the Iлdex action",
up;
puЫic
puЫic
["Uptime"] = $"{uptime.Uptime}ms"
}) ;
Когда инфраструктуре
MVC
необходим экземпляр класса контроллера
обработки НТГР-запроса, она инспектирует конструктор
живает, что ему требуется объект
UptimeService.
служб, которые были сконфигурированы в классе
HomeController
Затем
MVC
Startup.
Home
для
и обнару­
инспектирует набор
выясняет. что служба
UptimeService
сконфигурирована так, чтобы для всех запросов применялся единс­
твенный объект
UptimeService,
и передает этот объект в качестве аргумента конс­
труктору при создании экземпляра
HomeController.
Службы могут регистрироваться и потребляться более сложными способами, но
рассмотренный пример продемонстрировал главную идею, лежащую в основе служб,
и показал, как определение службы в классе
Startup
позволяет определять функци­
ональность или данные, которые используются по всему приложению.
396
Часть 11 . Подр о бные сведения об инфраструктуре ASP.NET Соге MVC 2
В результате запуска приложения и запроса ст андартного
URL появится
ответ, ко­
торый включает количество миллисекунд. прошедших с момента старта приложения
(рис .
14.5).
в классе
Число миллисекунд получается из объекта
Startup .
Upt i meService,
созданного
(Строго говоря . отображаемое значение соответствует времени.
прошедшему с момента создания объекта службы
Up t i meServ ice.
но оно довольно
близко к моменту запуска приложения. чтобы для целей данной главы разница не
играла никакой роли.)
D Rrsult
С
l
1
)(
<D
localhost '5416
.Мessage Т11is is tl1e Iнdex actioн
Uptime
Рис.
5шs
14.5.
Потребление простой службы
Каждый раз . когда поступает з апрос к стандартному
создает новый объект
Uptime Serv i ce
HomeC on tr o l le r
URL.
инфраструктура
MVC
и предоставляет ему разделяемый объект
в виде аргумента конструктора. Это позволяет контроллеру
Home
по ­
лучать доступ к значению времени выполнения приложения. не заботясь о том, ка­
ким образом такая информация предоставлена или реализована .
Службы
MVC
Пакет с уровнем сложности, как у MVC. имеет дело со многими службами; одни из
них предназначены для внутреннего потребления. а другие предлагают функциональ­
ность разработчикам. Пакеты определяют расширяющие методы . которые настра­
ивают все требующиеся им службы в единственном вызове метода . Для
метод имеет имя
добавлены в
AddMvc () и является одним из двух методов.
класс Sta rt up , чтобы обеспечить работу MVC :
MVC
такой
вызовы которых были
void Con f i gu re Servic es( I ServiceCollection se r vice s ) {
services .AddSi ngle t on<UptimeSe r vice>() ;
puЫi c
services.AddМvc();
Метод
AddMvc () настраивает каждую службу. в которой
Confi gu r eServ i ces () гигантским списком
мождая метод
На заметку! Средство
lntelliSense
других расширяющих методов,
в
Visual Studio
которые
нуждается
MVC,
не загро­
индивидуальных служб .
будет отображать длинный список
можно вызывать на объекте реализации
ISer v i c eCo l l ec t i o n в методе Conf igureS e rvic e s () .Некоторые из этих мето­
Add Sin gl e ton () и AddScop ed () ,применяются для регистрации служб
различными способами . Другие методы , подобные Add Rout in g () или AddCo rs (), до­
бавляют отдельные службы , которые уже использовались методом AddMvc () . В резуль­
тате для большинства приложений метод Con f igure Serv i ces () содержит небольшое
число специальных служб , вызов метода AddMvc () и необязательно операторы конфи­
дов, такие как
гурирования встроенных служб, которые описаны в разделе "Конфигурирование служб
MVC"
далее в главе.
Глава 14. Конфигурирование nриложений
Промежуточное программное обеспечение
В
ASP.NET Core
397
ASP.NET
термин промежуточное ПО используется в отношении компонен­
тов. которые объединяются с целью формирования конвейера запросов. Конвейер
запросов организован аналогично цепочке; когда поступает новый запрос, он пере­
дается первому компоненту промежуточного ПО в цепочке. Компонент инспектирует
запрос и решает, обработать его и сгенерировать ответ или передать следующему ком­
поненту в цепочке. После того как запрос обработан, ответ, который будет возвращен
клиенту, передается по цепочке обратно, что позволяет всем предшествующим ком­
понентам инспектировать или модифицировать его.
Работа компонентов промежуточного ПО может выглядеть несколько странной,
но она допускает большую гибкость в том, как приложения собираются вместе.
Понимание того, каким образом применение промежуточного ПО придает форму при­
ложению, может оказаться важным, особенно если вы получаете не те ответы, кото­
рые ожидали. Чтобы выяснить, как функционирует система промежуточного ПО, мы
создадим несколько специальных компонентов. которые продемонстрируют каждый
из имеющихся четырех типов промежуточного ПО.
Создание промежуточного по для генерации содержимого
Самый важный тип промежуточного ПО генерирует содержимое для клиентов, и
именно к данной категории принадлежит
MVC.
Чтобы создать компонент промежу­
точного ПО для генерации содержимого, не имея дела со сложностью
в папку
Infrastructure
файл класса по имени
нием, приведенным в листинге
Листинг
14. 17.
MVC,
ContentMiddleware. cs
добавим
с определе­
14.17.
Con ten tмiddleware. cs
Infrastructure
Содержимое файла
из папки
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace ConfiguringApps.Infrastructure
class ContentMiddleware {
private RequestDelegate nextDelegate;
puЫic
puЬlic
ContentMiddleware(RequestDelegate next) => nextDelegate = next;
async Task Invoke(HttpContext httpContext) {
if (httpContext. Request. Path. ToString (). ToLower () == "/middleware")
puЫic
await httpContext.Response.WriteAsync(
"This is from the content middleware", Encoding.UTFB);
else {
await nextDelegate.Invoke(httpContext);
Компоненты промежуточного ПО не реализуют какой-либо интерфейс и не явля­
ются унаследованными от какого-то общего базового класса. Взамен они определяют
398
Часть 11. Подробные сведения об инфраструктуре ASP.NEТ Core MVC 2
конструктор. принимающий объект
RequestDelegate. и метод Invoke (). Объект
RequestDelegate представляет следующий компонент промежуточного ПО в цепоч­
ке, а метод Invoke () вызывается. когда ASP.NET получает НТТР-запрос.
Информация о запросе и ответе НТТР. которая будет возвращена клиенту, предо­
ставляется методу
Invoke ()
через аргумент
свойства рассматриваются в главе
17.
HttpContext.
Класс
HttpContext и его
Invoke () в
послан на URL вида
а пока достаточно знать. что метод
14.17 инспектирует НТТР-запрос и проверяет. был ли он
/middleware. Если это так. тогда клиенту отправляется простой текстовый ответ:
если использовался другой URL, то запрос перенаправляется следующему компоненту
листинге
в цепочке.
Конвейер запросов настраивается внутри метода
Configure () класса Startup.
14.18 из примера приложения удалены методы MVC и применен класс
ContentMiddleware в качестве единственного компонента в конвейере.
В листинге
Листинг
14. 18.
Использование специального компонента промежуточного ПО
в файле
using
using
using
using
using
using
using
using
Startup. cs
из папки
ConfiguringApps
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
using ConfiguringApps.Infrastructure;
namespace ConfiguringApps {
puЫic class Startup {
puЫic void ConfigureServices(IServiceCollection services) {
services.AddSingleton<UptimeService>();
services.AddMvc();
puЬlic
void Configure(IApplicationBuilder
арр,
IHostingEnvironment env)
{
app.UseМiddleware<Contentмiddleware>();
Специальные компоненты
расширяющего метода
UseMiddleware ()
промежуточного
UseMiddleware ()
ПО
регистрируются
внутри метода
с помощью
Configure ().
Метод
применяет параметр типа для указания класса промежуточного
ПО. Именно так инфраструктура
ASP.NET Core
может построить список всех компо­
нентов промежуточного ПО. которые собирается использовать. и затем создать их эк­
земпляры для формирования цепочки. На рис.
жения и запроса
URL
вида
14.6 показан
результат запуска прило­
/middleware.
На рис.
ем класса
14.7 изображен конвейер промежуточного ПО. созданный с применени­
ContentMiddleware. Когда инфраструктура ASP.NET Core получает НТТР­
запрос, она передает его единственному компоненту промежуточного ПО, зарегис­
трированному в классе
Startup.
Если
URL
нерирует результат, который возвращается
является
/middleware,
ASP.NET Core
то компонент ге­
и отправляется клиенту.
Глава 14. Конфигурирование прил оже ний
С
ф
399
localhost 65416/niiddfeware
This is from the content middleware
l_
Рис.
14.6.
Генерация содержимого из специального компонента промежуточного ПО
Промежуточное ПО
для генерации содержимого
r-
...,..
ш
z
а.:
(/)
1
s
:r:
1
"'
Q_
1
~
Рис.
URL
Ответ
.J
<t:
Если
-----1
14. 7.
отличается от
'-
------ <111(------
о
Пример конвейера промежуточного ПО
/midd l eware ,
тогда класс
Con te ntMidd leware
переда ­
ет запрос следующему компоненту в цепочке. Поскольку других компонентов нет. за­
прос достигает ограничительного обработчика. предоставленного инфраструктурой
ASP.NET Core
при создании конвейера. который посылает запрос обратно через кон­
вейер в противоположном направлении (после того , как вы ознакомитесь с работой
других типов промежуточного ПО, данный процесс обретет больший смысл).
Использование служб в промежуточном ПО
Применять служб ы. настроенные в методе
ко контроллеры. Инфраструктура
Confi gureSe rv ices
ASP.NET Core
() ,могут не толь­
инспектирует конструкторы клас­
сов промежуточного ПО и и спользует службы для предоставления значений любым
аргументам. которые были определены. В листинге
Cont entMiddl e ware
димости предоставления объекта
Листинг
14.19
добавлен аргумент. который сообщает
к конструктору класса
ASP.NET Core
UptimeService .
14.19. Применение службы в файле Contentмiddleware. cs
из файла Infrastructure
using System . Te x t;
usin g System.T hrea ding.Ta s ks ;
u si ng Micr o s oft . AspNetCore.Http;
names pace Co nfi guringApps .In fra st ructure
class Conte ntM i ddleware {
private RequestDel egate nextDelegate ;
puЫic
о необхо­
Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC 2
400
UptiшeService
private
uptime;
puЬlic Contentмiddleware(RequestDelegate
nextDelegate
uptiшe
=
next,
UptiшeService
up) (
next;
= up;
async Task Invoke (Htt pContext htt pCont ext) (
if (httpContext.Re que s t.Pat h .ToS trin g () . To Lowe r ()
puЫic
" /mi dd l e war e " )
{
await httpC ontext.Re s po nse.WriteAsyn c (
"This is froш the content шiddleware "+
$" (uptime: (uptime.Uptime}ms)", Encoding.UTFB);
else {
await ne xt Delegate.Invo ke (httpContext ) ;
Наличие возможности использования служб озн а чает, что компоненты проме­
жуточного ПО могут разделять общую функциональность и избегать дублирования
кода.
Запуск приложения и запрос
да. представленного на рис.
URL вида / mi dd l e ware
14.8.
D loc4!host6541 б/mtddle
С
приводит к п олучению выво ­
Х
ф loca1host 65416"1111ddle~vare
This is from the content middleware (uptime: 281ms)
Рис.
14.8.
Применение службы в специальном промежуточном ПО
Создание промежуточного ПО для обхода
Следующий тип промежуто чного ПО перехватывает запросы до того. как они до ­
стигнут компонентов для генерации содержимого , чтобы обойти процесс прохожде­
ния конвейера. часто в целях. связанных с производительностью . В л истинге
приведено содержимое файла ю~асса по имени
ленного в папку
Листинг
Sho rtCi rc ui tMiddlewa re . c s,
Inf r astru cture .
14.20. Содержимое файла ShortCircui tмiddleware . cs
из папки Infrastructure
using System.Linq ;
us ing System.Threading.Tasks;
us ing Mic ros o ft. Asp NetCore.Htt p ;
na mes pa ce Co nf igu r in gAp ps.Inf rast ru c ture
pu Ы i c
c l ass Shor t Ci rc uitMiddleware {
14.20
добав­
Глава 14. Конфигурирование приложений
401
private RequestDelegate nextDelegate;
ShortCircuitMiddleware(RequestDelegate next) =>
nextDelegate = next;
puЬlic
async Task Invoke(HttpContext httpContext)
if (httpContext.Request.Headers["User-Agent"]
.Any(h => h.ToLower() .Contains("edge")))
httpContext.Response.StatusCode = 403;
else {
await nextDelegate.Invoke(httpContext);
puЫic
Этот компонент промежуточного ПО проверяет заголовок
User-Agent
запроса.
который браузеры применяют для идентификации самих себя. Использование заго­
ловка
User-Agent
для опознания специфических браузеров не вполне надежно для
реализации в реальном приложении, но достаточно для рассматриваемого примера.
Понятие мобход" применяется из-за того, что такой тип промежуточного ПО не
всегда направляет запросы следующему компоненту в цепочке. В данном случае. если
заголовок
User-Agent содержит элемент edge,
4 03 - ForЬidden (403 - запрещено)
состояния в
тогда компонент устанавливает код
и не направляет запрос следующему
компоненту. Так как запрос отклонен, нет никакого смысла давать возможность за­
просу обрабатываться остальными компонентами, что привело бы к ненужному пот­
реблению системных ресурсов. Взамен обработка запроса прекращается раньше, а
клиенту посылается ответ
403.
Компоненты промежуточного ПО получают запросы в порядке. в котором они
были настроены в классе
Startup,
что означает необходимость настройки промежу­
точного ПО для обхода перед промежуточным ПО для генерации содержимого (лис­
тинг
14.21).
Листинr
14.21.
Реrистрация компонента промежуточноrо ПО для обхода
в файле
using
using
using
using
using
using
using
using
using
Startup. cs
из папки
ConfiguringApps
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencylnjection;
ConfiguringApps.Infrastructure;
namespace Conf iguringApps {
puЫic class Startup {
void ConfigureServices(IServiceCollection services) {
services.AddSingleton<UptimeService>();
services.AddMvc();
puЫic
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
402
puЫic
void Configure(IApplicationBuilder
арр,
IHostingEnvironment env)
(
app.UseМiddleware<ShortCircuitмiddleware>();
app.UseMiddleware<ContentMiddleware>();
В результате запуска приложения и запрашивания любого
браузера
нент
Microsoft
Ed~e появится ошибка
ShortCircui tMiddleware
с использованием
игнорирует и передает следующему компоненту в
цепочке. т.е. ответ сгенерируется, когда запрошенным
рис.
URI,
Запросы из других браузеров компо­
403.
14.9 иллюстрируется добавление
будет
URL
/middleware.
На
в конвейер компонента промежуточного ПО для
обхода.
Промежуточное ПО
Промежуточное ПО
для генерации
для обхода
1--
.
ш
------
r
z
Рис.
14.9.
-------
r
1
а.:
(J)
<(
содержимого
1
Ответ
1-
.J
"'
1
1
----
1
Ответ
1
1
------!-<
.J
'
1
с5
Добавление в конвейер компонента промежуточного ПО для обхода
Соэдание промежуточного по для редактирования запросов
Следующий тип компонента промежуточного ПО. который мы исследуем, ответ не
генерирует. Взамен компонент изменяет запросы перед тем. как они достигнут других
компонентов, находящихся дальше в цепочке. Такой вид промежуточного ПО приме­
няется преимущественно в целях интеграции с платформой, чтобы обогатить пред­
ставление
ASP.NET Core
запроса НТТР средствами, специфическими для платформы.
Он также может использоваться при подготовке запросов, чтобы их легче было об­
рабатывать последующими компонентами. В качестве демонстрации добавим в пап­
ку
Infrastructure
файл
BrowserTypeMiddleware. cs
промежуточного ПО, приведенным в листинге
Листинг
с определением компонента
14.22.
14.22. Содержимое файла BrowserТypeМiddleware. cs
из папки Infrastructure
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace ConfiguringApps.Infrastructure
Глава 14. Конфигурирование nриложений
403
class BrowserTypeMiddleware
private RequestDelegate nextDelegate;
puЫic
puЫic
BrowserTypeMiddleware(RequestDelegate next) => nextDelegate = next;
async Task Invoke(HttpContext httpContext)
httpContext.Items["EdgeBrowser"]
= httpContext.Request.Headers["User-Agent"J
.Any(v => v.ToLower() .Contains("edge") );
await nextDelegate.Invoke(httpContext);
puЫic
Новый компонент инспектирует заголовок
edge, что предполагает
HttpContext предоставляет
мент
User-Agent
запроса и ищет в нем эле­
возможность выдачи запроса браузером
через свойство
Items
Edge.
Объект
словарь, который применяется
для передачи данных между компонентами, и результат поиска в заголовке сохраня­
ется с ключом
EdgeBrowser.
Чтобы продемонстрировать, как компоненты промежуточного ПО могут взаимо­
действовать друг с другом, в листинге
14.23 показан класс ShortCircui tMiddleware,
который отклоняет запросы, когда они поступают от браузера
ние на основе сгенерированных компонентом
Листинг
14.23.
Edge,
принимая реше­
BrowserTypeMiddleware
данных.
Взаимодействие с другим компонентом в файле
ShortCircui tмiddleware. cs
из папки
Infrastructure
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace ConfiguringApps.Infrastructure
class ShortCircuitMiddleware {
private RequestDelegate nextDelegate;
puЫic
puЫic
puЫic
ShortCircuitMiddleware(RequestDelegate next) => nextDelegate = next;
async Task Invoke(HttpContext httpContext) {
if (httpContext. Iteш.s [ "EdgeBrowser"] as bool?
==
true) {
httpContext.Response.StatusCode = 403;
else {
await nextDelegate.Invoke(httpContext);
Из-за присущей им природы компоненты промежуточного ПО, редактирующие
запросы, необходимо помещать перед компонентами, с которыми они взаимодейс­
твуют или которые полагаются на вносимые ими изменения. В листинге
BrowserTypeMiddleware
14.24
класс
зарегистрирован как первый компонент в конвейере.
Часть 11. Подробные сведения об инфраструктуре ASP. NET Core MVC 2
404
Листинг
Регистрация компонента промежуточного ПО для редактирования
14.24.
запросов в файле
using
using
using
using
using
using
using
using
using
Startup. cs
из папки
ConfiguringApps
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
ConfiguringApps.Infrastructure;
namespace ConfiguringApps (
puЫic class Startup (
puЬlic
void ConfigureServices(IServiceCollection services) {
services.AddSingleton<UptimeService>();
services.AddMvc();
puЫic
void Configure ( IApplicationB11ilder
арр,
IHostingEnvironment env)
(
app.UseМiddleware<BrowserTypeМiddleware>();
app.UseMiddleware<ShortCircuitMiddleware>();
app.UseMiddleware<ContentMiddleware>();
Размещение компонента в начале конвейера гарантирует. что до попадания в дру­
гие компоненты запрос уже будет модифицирован (рис.
Промежуточное ПО
для редактирования
запросов
14.1 О).
Промежуточное ПО
Промежуточное ПО
для обхода
для генерации
содержимого
1-ш
1
z
1
Запрос
1
- - - - --
1
а.:
сп
<(
1
,
Рис.
14. 1О.
- - -
1
1
Ответ
Ответ
1
'
-·
r
1
1
- - - - ·-
1
'
]~
---·--"-.:.•--
-
'
-· ..
1
- ... rc
-·
-
-
:_' 1
~:
l :~
1
'
с
••1
/:.~
j
-·-------
Добавление в конвейер компонента промежуточного ПО
для редактирования запросов
Создание промежуточного по для редактирования ответов
Последний тип промежуточного ПО оперирует на ответах. генерируемых другими
компонентами в конвейере. Это полезно для регистрации в журнале деталей запросов
и их ответов или для обработки ошибок. В листинге
файла
ErrorMiddleware. cs.
14.25
представлено содержимое
который добавлен в папку
монстрации данного вида компонента промежуточного ПО.
Infrastructure
для де­
Глава 14. Конфигурирование приложений
Листинг
14.25.
405
ErrorMiddleware. cs
Infrastructure
Содержимое файла
из папки
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace
ConfiguringApps.Iпfrastructure
puЫic class ErrorMiddleware {
private RequestDelegate пextDelegate;
puЬlic
ErrorMiddleware(RequestDelegate next) {
пextDelegate
puЬlic аsупс
await
=
пехt;
Task
Iпvoke(HttpCoпtext
httpContext)
пextDelegate.Iпvoke(httpCoпtext);
if (httpContext.Response.StatusCode == 403) {
await httpContext.Response
.WriteAsync("Edge not supported", Encoding.UTF8);
else if (httpContext.Response.StatusCode == 404) {
await httpContext.Response
.WriteAsync("No content middleware response", Encoding.UTF8);
Компонент не заинтересован в запросе до тех пор. пока он не пройдет свой путь
через конвейер промежуточного ПО и не будет сгенерирован ответ. Если кодом состо­
яния ответа является
403
или
404.
тогда компонент добавляет к ответу описательное
сообщение. Все другие ответы игнорируются. В листинге
гистрируется в классе
14.26
класс компонента ре­
Startup.
Совет. У вас может возникнуть вопрос, откуда поступил код состояния
(404 -
4 04 - Not Found
не найдено), поскольку он не устанавливается ни одним из трех созданных ком­
понентов промежуточного ПО. Дело в том, что именно так инфраструктура ASP.NEТ кон­
фигурирует ответ, когда запрос входит в конвейер, и этот ответ будет тем результатом,
который возвращается клиенту, если компоненты промежуточного ПО его не изменяли.
Листинг
14.26.
Регистрация компонента промежуточного ПО ДllЯ редактирования
ответов в файле
using
using
using
using
using
using
using
using
using
Startup. cs
из папки
ConfiguringApps
System;
System.Collections.Generic;
System.Linq;
System.Threadiпg.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
ConfiguringApps.Infrastructure;
Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC 2
406
n a mespace Co nfiguringApps {
puЫic class Startup {
puЫic
void Con figureSer v i ces ( IService Co ll ec t i on se rv i ces ) {
services.AddSingleton<Upt imeS ervice> ();
services.AddMvc();
puЬli c
void Co nfigure(IAppli cat i o nBuilde r
арр ,
IHost ing Env i r onment env)
{
app.UseМiddleware<ErrorMiddleware>();
app.UseMiddleware<BrowserT ypeMiddleware > () ;
app.UseMiddl eware<ShortCir c u itMiddleware> () ;
app.UseMiddl e ware<ContentMiddleware> ();
Класс
ErrorMiddleware
зарегистрирован в первой позиции конвейера. что может
выглядеть странным для компонента , который заинтересован только в ответах. но
регистрация компонента в начале цепочки обеспечивает ему возможность инспекти ­
ровать ответы, сгенерированные любыми другими компонентами (рис.
14. 11).
В слу­
чае помещения компонента дальше в конвейере он сможет инспектировать ответы.
генерируе мые только компонентами. находящимися по сле него.
ПромеJ1Суточное ПО
Промежут очное П О
для редактирования
для редакти ро вания
Промежуточ но е П О
заnросов
для обхода
ответов
1--
___.
ш
z
о..:
(f)
-----------
<(
Рис.
14.11.
для ген ерации
содержимо го
г
Заnрос
Ответ
1
Отвы
1 -
1
···-
1· ·
1
+--- ---j
Промежуто чно е П О
1
-
--
1
О т ве т
1
1
<(
Добавление в конвейер компонента промежуточного ПО для редактирования
ответов
Эффект от нового пром ежуточного ПО можно увидеть, запустив прилож е ние и
запросив любой
URL кроме /middle wa re.
14.12.
Результатом будет сообщение об ошибке .
показанное на рис.
D loc~
ostб541б/oth~rVtf Х
С
Ф
localhost·65416/ott1e1 Url
content middleware response
Рис.
14. 12.
Редактирование ответов от других компонентов промежуточного ПО
Глава 14. Конфигурирование приложений
Особенности вызова метода
Платформа
ASP.NET Core
407
Conf iqure ()
исследует метод
Configure ()
перед его вызовом и по­
лучает список его аргументов, которые он предоставляет с использованием служб,
настроенных в методе
в табл.
ConfigureServices
().или специальных служб. описанных
14.5.
Таблица
14.5.
Специальные службы, доступные как аргументы метода
Confiqure ()
Описание
Тип
IApplicationBuilder
Этот интерфейс определяет функциональность, требующуюся
для настройки конвейера промежуточного ПО приложения
Этот интерфейс определяет функциональность, необходимую
IHostingEnvironment
для различения типов сред, таких как среда разработки и про­
изводственная среда
IAppl.ica tionВuil.der
Использование интерфейса
Хотя определять любые аргументы для метода
большинстве классов
Startup
IApplicationBuilder, потому
Conf igure ()
не обязательно, в
будет применяться, по меньшей мере, интерфейс
что он позволяет создать конвейер промежуточно­
го ПО, как демонстрировалось ранее в главе. В случае специальных компонентов
промежуточного ПО для регистрации классов используется расширяющий метод
UseMiddleware
().Сложные пакеты промежуточного ПО для генерации содержимого
предоставляют единственный метод. который настраивает все их компоненты проме­
жуточного ПО за один шаг
-
точно так же, как они предоставляют единственный ме­
тод для определения применяемых ими служб. Для МVС доступны два расширяющих
метода. описанные в табл.
Таблица
14.6.
14.6.
Расширяющие методы
IApplicationBuilder для
МУС
Описание
Имя
UseMvcWithDefaultRoute()
Этот метод настраивает компоненты промежуточного ПО
для
UseMvc ()
MVC
с использованием стандартного маршрута
Этот метод настраивает компоненты промежуточного ПО
для
MVC
с применением специальной конфигурации мар­
шрутизации, указанной посредством лямбда-выражения
Маршрутизация
-
это процесс, посредством которого
URL запросов сопоставля­
ются с контроллерами и действиями, определенными в приложении; маршрутиза­
ция подробно рассматривается в главах
15 и 16.
полезен при изучении разработки приложений
ний будет вызываться метод
UseMvc ().
Метод
UseMvcWi thDefaul tRoute ()
MVC.
но в большинстве приложе­
даже если в результате явно определя­
ется точно такая же конфигурация маршрутизации. как и создаваемая методом
UseMvcWi thDefaul tRoute ().
В листинге
14.27
приведен пример. Такой подход де­
лает конфигурацию маршрутизации. используемую в приложении. очевидной для
других разработчиков и облегчает дальнейшее добавление новых маршрутов (что в
какой-то момент требуется практически во всех приложениях).
408
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Листинг
14.27.
Настройка компонентов промежуточного ПО для МУС
в файле
using
using
using
using
using
using
using
using
using
Startup. cs
из папки
ConfiguringApps
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
ConfiguringApps.Infrastructure;
namespace ConfiguringApps {
puЫic class Startup {
void ConfigureServices(IServiceCollection services} {
services.AddSingleton<UptimeService>(};
services.AddMvc(};
puЫic
void Configure(IApplicationBuilder арр, IHostingEnvironment env}
app.UseMiddleware<ErrorMiddleware>();
app.UseMiddleware<BrowserTypeMiddleware>(};
app.UseMiddleware<ShortCircuitMiddleware>(};
app.UseMiddleware<ContentMiddleware>();
puЫic
app.UseМvc(routes
=>
routes.МapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
}) ;
Поскольку
настраивает компоненты промежуточного ПО для генерации со­
MVC
держимого, метод
UseMvc {)
вызывается после регистрации всех других компонен­
тов промежуточного ПО. Чтобы подготовить службы, от которых зависит
AddMvc ()
должен вызываться в методе
Использование интерфейса
Интерфейс
MVC.
метод
ConfigureServices ().
IHostingEnvironment
IHostingEnvirorunent
предоставляет базовую
-
но важную
-
инфор­
мацию о среде размещения, в которой функционирует приложение, с применением
свойств, описанных в табл.
Таблица
14. 7.
Свойства
14.7.
IHos tingEnvironmen t
Имя
Описание
ApplicationName
Это свойство возвращает имя приложения, установленное
размещающей платформой
EnvirorunentName
Это свойство возвращает строку, которая описывает текущую
среду, как объясняется далее в главе
409
Глава 14. Конфигурирование приложений
Окончание табл .
14.7
Имя
Описание
Co пtentRootPa t h
Это свойство возвращает путь , по которому находятся файлы
содержимого и конфигурации приложения
Это свойство возвращает строку, указывающую каталог, в ко ­
WebRoot Path
тором находится статическое содержимое для приложения .
Обычно им будет папка
Соп t.е пt Root
Fi l e Prov ider
wwwroo t
Это свойство возвращает объект, который реализует ин­
тepфeйc
Micr oso ft. AspNetCore. Fi l e Providers.
I Fil ePro v ider и может использоваться для чтения файлов
из папки , указанной свойством Conten tRootPath
Это свойство возвращает объект, который реализует ин­
WebRoo t Fil e Pro v ider
Micro s o ft. AspNe t Co re . File Pr o v i de r s.
IFi l e Prov i der и может применяться для чтен и я файлов
папки, указанной свойством WebRootPath
терфейс
Свойства
Conten tRoo tPath
и
Web RootPath
из
интерес ны. но в большинстве при­
ложений не нужны. потому что имеется встроенный компонент пром ежуточного ПО .
который можно использовать для доставки статического содержимого , ка к описан о в
разделе " Включение статич еского соде ржимого·· дал ее в гл аве.
Важным свойством является
Enviro nmen tName ,
которое позволяет модифициро­
вать конфигурацию приложения на основе среды. где оно выполняется. По соглаше­
нию различают три среды (среда разработки
да
(St aging)
и производственная среда
(Deve l o pmen t).
(Produc t ion)).
подготовительная сре­
Текущая среда размещения устанавливается с применением пе р ем е нной среды
по имени
ASPNETCORE_ENVIRONMEN T.
Чтобы установить п ерем енную с реды. выбе ­
ConfiguringApps Properties (Свойств а Co пf i g u rin gApp s ) и з меню Project
(Проект) в Visual Studio и в открывшемся диалоговом окне перейдем на вкладку
Debug (Отладка). Дважды щелкнем н а поле Value (Значение) для переменной среды .
рем пункт
по умолчанию установленной в
De ve lopme nt.
и изменим ее на
St agi ng
(рис.
Сохраним изменения, чтобы новое имя среды вступило в силу.
МфЦ
llSEx!>reos
нsex.r."
L"""""URL
Erмrorment VамЬ.еs
Nan-.e
дSPNE1COREJ:
~~
/1RONME>•
Slogr.g
,..... . . . . ..". ., ,. , . . ."..._. . _.,.._.",....._. .",."........ ......""_,. r.,,.
\\ltl>S........~
Рис.
14. 1З .
~"
Установка имени среды размещения
14.13).
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
410
Совет. Имена сред не чувствительны к регистру символов, так что
трактуются как одна и та же среда. Хотя
Staging и staging
Development, Staging и Production явля­
ются общепринятыми именами сред, допускается использовать любое желаемое имя. Это
может быть полезно, например, когда над проектом трудятся несколько разработчиков,
каждый из которых требует разных конфигурационных настроек. В разделе "Работа со
сложными конфигурациями" далее в главе объясняется, как учитывать сложные различия
между конфигурациями сред.
Выяснить внутри метода
можно через свойство
Configure (), какая среда размещения применяется.
IHostingEnvironment. EnvironmentName или путем вызо­
ва одного из расширяющих методов, которые оперируют с объектами реализации
IHostingEnvironment
Таблица
14.8.
и описаны в табл.
Расширяющие методы
14.8.
IHostingEnvironment
Имя
Описание
IsDevelopment ()
Этот метод возвращает
является
IsStaging ()
Этот метод возвращает
является
IsProduction ()
IsEnvironment(env)
если именем среды размещения
true,
если именем среды размещения
true,
если именем среды размещения
true,
если имя среды размещения сов­
Staging
Этот метод возвращает
является
true,
Development
Production
Этот метод возвращает
падает со значением аргумента
env
Расширяющие методы применяются для изменения набора компонентов про­
межуточного ПО в конвейере, чтобы приспособить поведение приложения к раз­
личным средам размещения. В листинге
14.28
с помощью одного из расширяю­
щих методов гарантируется, что специальные компоненты промежуточного ПО.
созданные ранее в главе, присутствуют только в среде размещения
Листинг
14.28.
Использование среды размещения в файле
из папки
using
using
using
using
using
using
using
using
using
Development.
Startup. cs
ConfiguringApps
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
ConfiguringApps.Infrastructure;
namespace ConfiguringApps {
puЫic class Startup {
puЫic
void ConfigureServices(IServiceCollection services) {
services.AddSingleton<UptimeService>();
services.AddMvc();
Глава 14. Конфигурирование приложений
puЫic
void Configure(IApplicationBuilder
411
IHostingEnvirorunent env) {
арр,
if (env.IsDevelopment()) (
app.UseМiddleware<ErrorМiddleware>();
app.UseМiddleware<BrowserТypeМiddleware>();
app.UseМiddleware<ShortCircuitМiddleware>();
app.UseМiddleware<Contentмiddleware>();
app.UseMvc(routes =>
routes.MapRoute(
name: "default",
template: "{controller=Home)/{action=Index)/{id?)");
}) ;
Специальные компоненты промежуточного ПО не добавляются в конвейер при
текущей конфигурации, в которой переменная среда была установлена в
В результате запуска приложения и запроса
ошибка
4 04 - Not Found,
вида
/middleware
Staging.
будет получена
т.к. доступными компонентами промежуточного ПО явля­
ются лишь те. которые настроены методом
для обработки указанного
URL
UseMvc (),
а они не имеют контроллера
URL.
На заметку! Протестировав влияние от изменения среды размещения, не забудьте изменить
ее снова на
Development,
иначе оставшиеся примеры в главе не будут работать долж­
ным образом.
Добавление оставшихся компонентов
промежуточного программного обеспечения
Существует набор наиболее часто используемых компонентов промежуточного ПО,
которые полезны в большинстве проектов МVС и применяются в примерах, рассмат­
риваемых в настоящей книге. По мере добавления таких компонентов к конвейеру
запросов в последующих разделах будет объясняться их работа.
Обеспечение возможности обработки исключений
Даже самое тщательно написанное приложение будет сталкиваться с исключения­
ми, поэтому важно их надлежащим образом обрабатывать. В листинге
трируется добавление к конвейеру запросов в классе
Startup
14.29 демонс­
компонентов промежу­
точного ПО, которые имеют дело с исключениями. Кроме того, удалены специальные
компоненты промежуточного ПО. чтобы можно было сосредоточиться на МVС.
Листинг
14.29.
Добавление компонентов промежуточного ПО для обработки
исключений в файле
using
using
using
using
Startup. cs из папки ConfiguringApps
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
412
using
using
using
using
using
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencylnjection;
ConfiguringApps.Infrastructure;
namespace ConfiguringApps (
puЫic class Startup (
puЫic
void ConfigureServices(IServiceCollection services) (
services.AddSingleton<UptimeService>();
services.AddMvc();
puЫic
void Configure(IApplicationBuilder
арр,
IHostingEnvironment env) (
if (env.IsDevelopment()) (
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
else {
app.UseExceptionHandler("/Home/Error");
app.UseMvc(routes =>
routes.MapRoute(
name: "default",
template: "(controller=Home)/(action=Index)/(id?}");
}) ;
Метод
IJseStatusCodePages () добавляет к ответам, не имеющим содержимого,
4 04 - Not Found (404- не найдено), что может быть
описательные сообщения вроде
удобно, т.к. не все браузеры отображают пользователю собственные сообщения.
Метод
IJseDeveloperExceptionPage ()
настраивает компонент промежуточного
ПО для обработки ошибок, который отображает детали исключения в ответе, в том
числе связанную трассировочную информацию. Информация подобного рода не долж­
на быть видимой пользователям, поэтому вызов
IJseDeveloperExceptionPage ()
де­
лается только в среде разработки, которая определяется с помощью объекта реализа­
ции
IHostingEnvironmment.
В подготовительной или производственной среде взамен используется метод
IJseExceptionHandler ().
Он настраивает обработку ошибок, позволяющую отобра­
жать специальное сообщение об ошибке, которое не раскрывает особенности внут­
ренней работы приложения. Аргументом метода
URL,
IJseExceptionHandler ()
является
на который должен быть перенаправлен клиент, чтобы получить сообщение об
ошибке. Это может быть любой
URL, предоставляемый приложением. но по соглаше­
/Home/Error.
В листинге 14.30 добавлена возможность генерации исключений по требованию
действия Index контроллера Home и определено действие Error, так что запросы,
генерируемые IJseExceptionHandler (), могут быть обработаны.
нию применяется
Глава 14. Конфигурирование прил ожений
Листинг
14.30.
413
Генерация и обработка исключений в файле HomeController. cs
из папки
Controllers
using System. Col le ct ions.Gen eric;
using Micr osoft . Asp NetCore.Mvc ;
using Configur i ngApps.Infrastru c t u r e;
n amespace ConfiguringApps . Controllers
p uЫic class HomeCont rol l er : Cont r ol l er
private UptimeService uptime;
puЫic HomeCon tr oller(UptimeService up ) => up t ime = up ;
puЬlic ViewResult Index(bool throwException = false)
if (throwException) {
throw new System . NullReferenceException() ;
return View(new Dictionary<string, string>
[ "Message"] = "This is the Index action",
["Uptime"] = $"{uptime.Uptime}ms "
}} ;
ViewResul t Error (} => View (nameof (Index} ,
new Dictionary<string, string> {
[ "Message"] = "This is the Error action " }) ;
puЬlic
I ndex, с помощью средства привязки моделей .
26. обеспечивают получение значения thr owExcepti o n
из запроса. Действие генерирует исключение Nul lRe feren ceExcept i on , если
значение thr owExcepti o n равно t ru e. и выполня ется нормально, если знач е ние
throw Ex cepti o n равно false.
Действи е Er r or использует пр едставле ние Inde x для отображения простого с ооб­
щения. Запустив приложение и запросив URL вида / Home/I ndex?throwException
=tr ue. можно просмотреть результаты функционирования разных компонен­
Изменения. внесенные в действие
которое обсуждается в главе
тов промежуточного ПО для обработки исключений . Строка запроса предоставля­
ет значение для аргумента действия
In dex.
от имени среды размещения. На рис.
14. 14
а наблюдае мый ответ будет зависеть
показан вывод . порожда ем ый методом
Us e De ve l ope rEx c ep t i onPage () (дл я среды размещения Devel opment)
UseE x ceptionHand ler () (для всех остальных сред размещения) .
АГ' u11r1;зr
dled е
ceotюn
oc... LJrred 'll11k
• ....
+-
Рис.
14.14.
с
ф
-
.t)
'
~
n
"(>
и методом
х
!
Обработка исключений в среде разработки и подготовительно й/
производственной среде
414
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Страница исключения для разработчика предлагает детали исключения, а также
возможность исследовать его информацию трассировки стека и запрос, который при­
вел к исключению. В противоположность этому страница исключения для пользова­
теля должна применяться просто для указания о том, что что-то пошло не так.
Включение средства
Средство
Browser Link
Browser Link (Ссылка на браузер).
описанное в главе
6,
предназначено
для управления браузерами на стадии разработки. Серверная часть средства
Link
Browser
реализована в виде компонента промежуточного ПО, который должен быть до­
бавлен в классе
Startup
как часть конфигурации приложения, потому что без тако­
го компонента интеграция с
Visual Studio
работать не будет. Средство
Browser Link
полезно только на стадии разработки и не должно использоваться в подготовитель­
ной или производственной среде, поскольку оно модифицирует ответы, генерируемые
другими компонентами промежуточного ПО, с целью вставки кода
JavaScript,
кото­
рый открывает НТГР-подключения с серверной стороной, чтобы она могла получать
уведомления о перезагрузке страницы. В листинге
тод
UseBrowserLink
ко для среды размещения
Листинг
14.31.
14.31
показано, как вызывать ме­
(),который регистрирует компонент промежуточного ПО толь­
Development.
Включение средства
из папки
Browser Link в файле Startup.cs
ConfiguringApps
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Dependencyinjection;
using ConfiguringApps.Infrastructure;
namespace ConfiguringApps {
puЫic class Startup {
puЫic void ConfigureServices(IServiceCollection services) {
services.AddSingleton<UptimeService>();
services.AddMvc();
void Configure(IApplicationBuilder арр, IHostingEnvironment env) {
if (env.IsDevelopment()) (
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseBrowserLink();
else {
app.UseExceptionHandler("/Home/Error");
puЫic
app.UseMvc(routes =>
routes.MapRoute(
name: "defaul t",
template: "{controller=Home}/{action=Index}/{id?}");
)) ;
Глава 14. Конфигурирование nриложений
415
Включение статического содержимого
Финальный компонент промежуточного ПО, полезный для большинства проектов,
предоставляет доступ к файлам в папке
чать изображения, файлы
wwwroot,
что позволяет приложениям вклю­
JavaScrlpt и таблицы стилей CSS.
Метод
UseSta ticFiles ()
добавляет компонент, который обходит конвейер запросов для статических файлов
14.32).
(листинг
Листинг
14.32.
Включение статического содержимого в файле
из папки
using
using
using
using
using
using
using
using
using
Startup. cs
ConfiguringApps
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
ConfiguringApps.Infrastructure;
namespace ConfiguringApps {
puЫic class Startup {
void ConfigureServices(IServiceCollection services) {
services.AddSingleton<UptimeService>();
services.AddMvc();
puЫic
puЫic
void Configure ( IApplicationBuilder
арр,
IHostingEnvironment env) {
if (env.IsDevelopment()) (
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseBrowserLink();
else (
app.UseExceptionHandler("/Home/Error");
app.UseStaticFiles();
app.UseMvc(routes => {
routes.MapRoute(
name: "default",
template: "{controller=Home)/{action=Index}/{id?}");
}) ;
Статическое содержимое обычно требуется вне зависимости от среды размеще­
ния.из-за чего метод
что элемент
link
UseStaticFiles ()
узеру загрузить таблицу стилей
деть результат, представленный
вызывается для всех сред. Это означает,
Index будет надлежаще работать и позволит бра­
CSS из Bootstrap. Запустив приложение. можно уви­
на рис. 14. 15.
в представлении
416
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
D Resu1t
С
х
ф localhos 65416
Message
This is the lndex action
Uptime
12ms
L_ _
Рис.
14.15.
Включение статического содержимого
Конфигурирование приложения
Некоторые данные конфигурации часто изменяются. например. когда приложение
переносится из среды разработки в производственную среду и потому для серверов
баз данных требуются друтие параметры. Вместо того чтобы жестко кодировать по­
добную информацию в классе
Startup.
платформа
ASP.NET Core
разрешает постав ­
лять данные конфигурации из целого диапазона более легко изменяемых источников.
таких как переменные среды , аргументы командной строки и файлы в формате
(JavaScгipt
Object
Notatioп
-
система обозначений для объектов
JSON
JavaScript).
Данные конфигурации обычно обрабатываются автоматически. но из-за того. что
мы заменили стандартные настройки в классе
Program.
понадобится явно добавить
код, который будет получать данные и делать их доступными остальной части прило­
жения (листинг
Листинг
using
using
using
using
using
using
using
using
using
using
14.33).
14.33. Загрузка данных конфигурации в файле Program. cs
из папки ConfiguringApps
System;
System.Collections.Generic;
System.IO;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore;
Microsoft.AspNetCore.Hosting;
Microsoft.Extensions.Configuration;
Microsoft.Extensions.Logging;
System.Reflection;
namespace ConfiguringApps (
puЫic class Program (
puЬlic
static void Main (string [) args) (
BuildWebHost(args) .Run();
puЫic
static IWebHost BuildWebHost(string[) args) (
return new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory() )
Глава 14. Конфигурирование nриложений
417
.ConfigureAppConfiguration((hostingContext, config) => {
config.AddJsonFile("appsettings.json",
optional: true, reloadOnChange: true);
config.AddEnvironшentVariaЫes();
if (args != null) {
confiq.AddC01111DandLine(arqs);
}
})
.UseIISintegration()
.UseStartup<Startup>()
. Build ();
Метод
ConfigureAppConfiguration ()
применяется для обработки данных кон­
WebHost.BuilderContext и объект, ре­
IConfigurationBuilder. В классе WebBostBuilderContext
описанные в табл. 14.9.
фигурации: его аргументами являются объект
ализующий интерфейс
определены свойства,
Таблица
14.9.
Свойства, определенные в классе WeЬВostвuilderContext
Описание
Ими
HostingEnvirorunent
Это свойство возвращает объект, который реализует интер­
фейс
IHostingEnvirorunent
и предоставляет информа­
цию о среде размещения, где функционирует приложение.
Подробные сведения были даны в разделе "Использование
интерфейса
Configuration
IHostingEnvirorunent"
ранее в главе
Это свойство возвращает объект, который реализует интер­
фейс
IConfiguration,
предоставляющий доступ только
по чтению к данным конфигурации в приложении
Интерфейс
IConfigurationBuilder
применяется при подготовке данных кон­
фигурации для остальной части приложения, которая обычно выполняется с исполь­
зованием расширяющих методов. Три метода. задействованные в листинге
добавления данных конфигурации, описаны в табл.
Таблица
14.1 О.
Расширяющие методы интерфейса
14.33 для
14.10.
IConfigura tionBui lder,
предназначенные для добавления данных конфигурации
Ими
Описание
AddJsonFile ()
Этот метод применяется для загрузки данных конфигура­
ции из файла
AddEnvironrnentVariaЫes
()
JSON,
такого как
appset tings. j son
Этот метод используется для загрузки данных конфигура­
ции из переменных среды
AddCoпunandLine()
Этот метод применяется для загрузки данных конфигура­
ции из аргументов командной строки, указываемых при
запуске приложения
418
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Из трех методов, которые использовались для загрузки данных конфигурации
14.33, самым интересным считается AddJsonFile ().В аргументах ме­
AddJsonFile () указываются имя файла, является ли файл необязательным, и
в листинге
тода
должны ли данные конфигурации загружаться повторно, если файл изменяется:
config.AddJsonFile("appsettings.json",
optional: true, reloadOnChange: true);
В значениях аргументов указан файл по имени
appsettings. j son,
ставляет собой общепринятое имя для конфигурационного файла
которое пред­
JSON.
Файл необя­
зателен, т.е. если он не существует, то никакого исключения не генерируется,
и он
будет отслеживаться на предмет изменений, так что данные конфигурации могут ав­
томатически обновляться.
Повторная загрузка данных конфигурации
Система конфигурации
ASP.NET Core
поддерживает повторную загрузку данных, когда кон­
фигурационные файлы изменяются. Некоторые встроенные компоненты промежуточного
ПО, такие как система регистрации в журнале, поддерживают такую функциональность, что
означает возможность изменения уровней регистрации во время выполнения без необхо­
димости в перезапуске приложения. Аналогичные средства можно также предусмотреть при
разработке специальных компонентов промежуточного ПО.
Однако просто тот факт, что средство делает что-то возможным, вовсе не означает, что оно
является целесообразным. Внесение изменений в файлы конфигурации производственных
систем
- верный способ вызвать простой из-за отказа. Слишком легко допустить ошибку
при модификации и получить неправильно работающую конфигурацию. Даже при успешном
изменении могут возникать непредвиденные последствия, такие как переполнение дисков
журнальными данными или резкое снижение производительности.
Рекомендуется избегать динамического редактирования и удостоверяться, что все измене­
ния прошли через стандартные процедуры разработки и тестирования в подготовительной
среде, прежде чем развертывать их в производственной среде. Может возникнуть соблазн
заняться модификацией активной системы, чтобы установить причину проблемы, но это
редко заканчивается хорошо. Если вы обнаруживаете, что занимаетесь редактированием
файлов конфигурации производственной системы, то должны задаться вопросом, готовы
ли вы превратить небольшую проблему в гораздо более крупную.
Создание конфигурационного файла
JSON
Самое распространенное применение файла а рр s е t
t i ngs . j s оn -
хранение
строк подключения к базам данных и настроек регистрации в журналах, но в нем
можно держать любые данные, в которых нуждается приложение.
Чтобы посмотреть, как работает система конфигурации. добавим в корневой ката­
лог проекта новый файл
денным в листинге
Листинг
14.34.
JSON
по имени
appsettings. j son
с содержимым, приве­
14.34.
Содержимое файла
appsettings. json из папки Confi9urin9Apps
"ShortCircui tMiddleware": {
"EnaЫeBrowserShortCircuit":
true
Глава 14. Конфигурирование приложений
Формат
JSON
419
делает возможной структуру. предназначенную для настроек кон­
JSON в листинге 14.34 определяет категорию конфигура­
ShortCircui tMiddleware, содержащую свойство по имени
EnaЬleBrowserShortCircui t, которое установлено в true.
фигурации. Содержимое
ции под названием
Формат
JSON:
кавычки и запятые
Если ранее вы не имели дела с форматом
полезно уделить некоторое время
чтению спецификации на веб-сайте
форматом
JSON, тогда
www. j son. org. С
JSON легко работать, к
тому же большинство платформ предлагают хорошую поддержку для генерации и разбора
данных
JSON,
в числе которых и приложения
MVC
(примеры ищите в главах
уровне клиента используется простой АРl-интерфейс
JavaScript.
20
и
21 ),
а на
В действительности боль­
шая часть разработчиков приложений MVC вообще не будут взаимодействовать с JSON на­
JSON вручную требуется только в файлах конфигурации.
прямую, и написание кода
Существуют две ловушки, в которые попадают многие разработчики, не имеющие опыта
работы с JSON. Хотя вы по-прежнему должны найти время на чтение спецификации, знание
наиболее распространенных проблем предоставит определенную отправную точку в ситуа­
ции, когда
Visual Studio или ASP.NET Соге не смогут провести разбор ваших файлов JSON.
global. j son, в котором
Ниже показано добавление к стандартному содержимому файла
присутствуют две самые распространенные проблемы:
{
"ShortCircuitMiddleware": 1
"EnaЫeBrowserShortCircuit":
true
mysetting : [ fast, slow ]
Во-первых, практически все в
JSON помещается в кавычки. Очень легко забыть, что вы пи­
шете вовсе не код С#, и посчитать, что имена свойств и значения должны быть восприняты
без кавычек. Тем не менее, в JSON все кроме булевских значений и чисел должно поме­
щаться в кавычки:
"ShortCircuitMiddleware":
"EnaЫeBrowserShortCircuit":
true
"mysetting": [ "fast", "slow"]
Во-вторых, при добавлении нового свойства к JSОN-описанию объекта вы должны помнить
о необходимости добавления запятой после предшествующего символа фигурной скобки:
"ShortCircuitMiddleware": {
"EnaЫeBrowserShortCircuit":
)
true
,
"mysetting": [ "fast", "slow"]
Заметить отличие довольно трудно даже когда оно выделено полужирным (именно потому
такая ошибка является распространенной), но здесь помещена запятая после символа
закрывающего раздел
ShortCircui tMiddleware.
),
Однако будьте внимательны, потому
что замыкающая запятая, после которой отсутствует раздел, также приводит к ошибке. Если
внесенные вами изменения в файл
JSON вызвали проблемы, то в первую очередь нужно
выполнить проверку на предмет двух указанных выше ошибок.
Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC 2
420
Использование данных конфигурации
Класс
Startup
может получить доступ к данным конфигурации путем опреде­
ления конструктора с аргументом типа
IConfiguration. Когда в классе Program
UseStartup (),данные конфигурации, подготовленные методом
ConfigureAppConfiguration, применяются для создания объекта Startup. В лис­
тинге 14.35 демонстрируется добавление к классу Startup конструктора и доступ к
вызывается метод
данным конфигурации.
Листинг
14.35.
Получение и использование данных конфиrурации
в файле
using
using
using
using
using
using
using
using
using
using
Startup. cs
из папки
ConfiguringApps
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
ConfiguringApps.Infrastructure;
Microsoft.Extensions.Configuration;
namespace ConfiguringApps {
puЫic class Startup {
puЬlic Startup(IConfiguration configuration)
Configuration = configuration;
IConfiguration Configuration { get; )
void ConfigureServices(IServiceCollection services) {
services.AddSingleton<UptimeService>();
services.AddMvc();
puЬlic
puЫic
puЫic
if
void Configure(IApplicationBuilder
арр,
IHostingEnvironment env)
((Configuration.GetSection("ShortCircuitмiddleware")?
.GetValue<bool>("EnaЫeBrowserShortCircuit"))
.Value)
app.UseMiddleware<BrowserTypeMiddleware>();
app.UseMiddleware<ShortCircuitMiddleware>();
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage{);
app.UseStatusCodePages();
app.UseBrowserLink();
else {
app.UseExceptionHandler("/Home/Error");
app.UseStaticFiles();
app.UseMvc(routes => {
routes.MapRoute(
name: "default",
template: "{controller=Home)/{action=Index)/{id?)");
)) ;
Глава 14. Конфигурирование приложений
Конструктор получает объект реализации
свойству по имени
Configuration,
IConfiguration
421
и присваивает его
которое затем может применяться для доступа
к данным конфигурации, загруженным из переменных среды, командной строки и
файла
appsettings. j son.
Чтобы получить значение, понадобится пройти по структуре данных до требуемо­
го раздела конфигурации, представленного с помощью еще одного объекта, который
IConfiguration, предоставляющий
IConfigurationRoot членов (табл. 14. l l).
реализует интерфейс
для
Таблица
14.11.
Члены, определяемые интерфейсом
подмножество доступных
IConfiqura tion
Описание
Имя
[key]
Этот индексатор используется для получения строкового зна­
чения, соответствующего указанному ключу
GetSection(name)
Этот метод возвращает объект реализации
key
IConfiguration,
который представляет раздел данных конфигурации
GetChildren ()
Этот метод возвращает перечисление объектов реализации
IConfiguration,
которые представляют подразделы текуще­
го объекта конфигурации
14.12, можно применять для опери­
IConfiguration с целью получения значений и их
Расширяющие методы, описанные в табл.
рования объектами реализации
преобразования из строк в другие типы.
Таблица
14.12.
Расширяющие методы для интерфейса
IConfiqura tion
Имя
Описание
GetValue<T>(keyName)
Этот метод получает значение, ассоциированное с указанным
ключом
GetValue<T>(keyName,
defaultValue)
keyName,
и пытается преобразовать его в тип т
Этот метод получает значение, ассоциированное с указанным
ключом keyName, и пытается преобразовать его в тип т. Если
значение для ключа в данных конфигурации отсутствует, тогда
будет использоваться стандартное значение
Важно
не предполагать,
В листинге
14.35
defaul tValue
что конфигурационное значение будет указано.
применяется
n u 11-условная
операция, чтобы удостоверить­
ShortCircui tMiddleware до того, как пытаться из­
EnaЬleBrowserShortCircui t. В результате специальный компо­
ся в получении раздела
влечь значение
нент промежуточного ПО добавится к конвейеру запросов, только если значение
EnaЬleBrowserShortCircui t из раздела
лено и установлено в
ShortCircui tMiddleware
было опреде­
true.
Конфигурирование регистрации в журнале
Платформа
ASP.NET Core включает поддержку сбора и обработки журнальных дан­
ных. а многие встроенные компоненты промежуточного ПО написаны так, чтобы ге­
нерировать журнальные сообщения. В большинстве проектов регистрация в журнале
настраивается автоматически. но поскольку в классе
Program
используются индиви­
дуальные конфигурационные операторы, для настройки средства регистрации в жур­
нале необходимо добавить операторы. показанные в листинге
14.36.
Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC 2
422
Листинг
14.36.
Конфигурирование регистрации в журнале в файле
из папки
using
using
using
using
using
using
using
using
using
using
Program. cs
ConfiguringApps
System;
System.Collections.Generic;
System.IO;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore;
Microsoft.AspNetCore.Hosting;
Microsoft.Extensions.Configuration;
Microsoft.Extensions.Logging;
System.Reflection;
namespace ConfiguringApps {
puЫic class Program (
static void Main(string[] args) {
BuildWebHost(args) .Run();
puЫic
puЫic
static IWebHost BuildWebHost(string[] args) {
return new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration( (hostingContext, config) => {
config.AddJsonFile("appsettings.json",
optional: true, reloadOnChange: true);
config.AddEnvironmentVariaЫes();
if (args != null) {
config.AddCommandLine(args);
}
) )
.ConfigureL099in9((hostin9Context, 1099in9) => {
1099in9.AddConfiguration(
hostin9Context.Configuration.GetSection("Lo99in9"));
1099in9.AddConsole();
1099in9.AddDebu9();
})
.UseIISintegration()
.UseStartup<Startup>()
.Build();
Метод
ConfigureLogging ()
настраивает систему регистрации в журнале с при­
менением лямбда-функции, которая получает объект
WebHostBuilderContext (упо­
ILoggingBuilder. Для
мянутый ранее в главе) и объект, реализующий интерфейс
интерфейса
ILoggingBuilder
предусмотрен набор расширяющих методов. позволя­
ющих конфигурировать систему регистрации в журнале (табл.
14.13).
Глава 14. Конфигурирование приложений
Таблица
Расширяющие методы дпя интерфейса
14.13.
423
ILoggingBuilder
Описание
Имя
Этот метод используется для конфигурирования системы регис­
AddConfiguration ()
трации в журнале с применением данных конфигурации, которые
были загружены из файла
appsettings. j son,
из командной
строки либо из переменных среды
Этот метод отправляет журнальные сообщения консоли, что
AddConsole ()
удобно в случае запуска приложения с использованием команды
dotnet run
Этот метод отправляет журнальные сообщения в окно
AddDebug ( )
(Вывод), когда выполняется отладчик
Output
Visual Studio
Этот метод отправляет журнальные сообщения в журнал событий
AddEventLog ()
Windows, что удобно в ситуации, когда приложение ASP.NET Core
MVC развернуто на сервере Windows и нужно, чтобы сообщения
из него смешивались с сообщениями от приложений других типов
Данные конфигурации системы регистрации в журнале
Метод
AddConfiguration ()
применяется для конфигурирования системы регис­
трации в журнале, используя данные конфигурации, которые обычно определены в
файле
14.37 в файл appsettings. j son добавляется
Logging, которое соответствует имени, применяемо­
AddConfiguration () из листинга 14.36.
appsettings. j son.
В листинге
раздел конфигурации по имени
му для метода
Листинг
14.37.
Добавление раздела конфигурации в файле appsettings. json
из папки
ConfiguringApps
"ShortCircuitMiddleware": (
}
,
"EnaЫeBrowserShortCircuit":
true
"Logging": {
"Lo9Level 11 :
"Default": 11 DeЬu9 11 ,
"System": "Information",
"Мicrosoft": "Information"
В конфигурации системы регистрации в журнале указан уровень сообщений, ко­
торые должны отображаться из различных источников журнальных данных. Система
регистрации в журнале поддерживает шесть уровней отладочной информации, кото­
рые описаны в табл.
Элемент
14.14
Defaul t
нальных сообщений в
Debug
в порядке их важности.
в листинге
Debug,
14.37
устанавливает порог для отображения жур­
т.е. отображаться будут только сообщения с уровнем
и выше. Остальные элементы переопределяют стандартные настройки для
журнальных сообщений из специфических пространств имен, чтобы журнальные со­
общения, которые поступают из пространств имен
лись только в случае, если они относятся к уровню
System и Microsoft,
Information и выше.
отобража­
424
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Таблица
14.14.
Уровни отладочной информации ASP.NEТ
Уровень
Описание
Trace
Этот уровень используется для сообщений, которые полезны во время
разработки, но не требуются в производственной среде
Debug
Этот уровень применяется для детализированных сообщений, требующих­
ся разработчикам при отладке с целью выявления проблем
Information
Этот уровень используется для сообщений, которые описывают общее
функционирование приложения
Warning
Этот уровень применяется для сообщений, описывающих события, кото­
рые не предвиделись, но и не приводят к прерыванию работы приложения
Error
Этот уровень используется для сообщений, которые описывают ошибки,
прерывающие работу приложения
Critical
Этот уровень применяется для сообщений, описывающих катастрофичес­
кие отказы
None
Этот уровень используется для отключения регистрации журнальных
сообщений
Для просмотра результатов включения регистрации в журнале запустим при­
ложение с применением отладчика
Debugging
Visual Studio,
выбрав пункт меню
{ОтладкаQЗапустить отладку). Заглянув в окно
Output,
DebugqStart
можно увидеть жур­
нальные сообщения. которые показывают. как обрабатывался каждый НТГР-запрос:
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[l]
Request starting HTTP/l.l GET http://localhost:65417/
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActioninvoker[l]
Executing action method ConfiguringApps.Controllers.HomeController.Index
(ConfiguringApps) with arguments (False) - ModelState is Valid
info: Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewResultExecutor[l]
Executing ViewResult, running view at path /Views/Home/Index.cshtml.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActioninvoker[2]
Executed action ConfiguringApps.Controllers.HomeController.Index
(ConfiguringApps) in 1597.ЗSЗSms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 1695.6314ms 200 text/html; charset=utf-8
Создание специальных журнальных сообщений
Журнальные сообщения в предыдущем разделе генерировались компонента­
ми
ASP.NET Core
и
MVC,
которые обрабатывают НТТР-запрос и производят ответ.
Сообщения подобного рода способны предоставлять полезную информацию, но мож­
но также генерировать специальные журнальные сообщения, которые являются спе­
цифичными для конкретного приложения, как демонстрируется в листинге
Листинг
14.38.
Специальная регистрация в журнале в файле
из папки
using
using
using
using
Controllers
System.Collections.Generic;
Microsoft.AspNetCore.Mvc;
ConfiguringApps.Infrastructure;
Microsoft.Extensions.Logging;
14.38.
HomeController. cs
Глава 14. Конфигурирование nриложений
425
namespace ConfiguringApps.Controllers {
puЫic
class HomeController : Controller
private OptimeService uptime;
private ILogger<HomeController> logger;
puЫic
HomeController(UptimeService up, ILogger<HomeController> log)
uptime
logger
puЫic
up;
log;
ViewResult Index(bool throwException = false) {
{Request.Path} at uptime {uptime.Uptime}");
logger.LogDeЬug($"Handled
if (throwException) {
throw new System.NullReferenceException();
return View(new Dictionary<string, string>
["Message"] = "This is the Index action",
["Optime"] = $"{uptime.Optimelms"
1);
puЫic
ViewResul t Error () => View (nameof ( Index),
new Dictionary<string, string> {
[ "Message"] = "This is the Error action" 1);
Интерфейс
ILogger определяет функциональность, требуюIЦУюся для созда­
ния журнальных записей и получения объекта, который реализует этот интер­
фейс. а класс
HomeController имеет
ILogger<HomeController>. Параметр
конструктор, принимающий аргумент типа
типа позволяет системе регистрации в жур­
нале использовать имя класса в журнальных сообщениях, и значение для аргумента
конструктора предоставляется автоматически через средство внедрения зависимос­
тей. которое будет описано в главе
18.
Располагая объектом реализации
ILogger,
журнальные сообщения можно со­
здавать с применением расширяющих методов, определенных в пространстве имен
Microsoft. Extensions. Logging. Предусмотрены методы для каждого уровня от­
14.14. Класс HomeCon trol ler использует метод
LogDebug () для создания сообщения на уровне Debug. Чтобы увидеть результат, по­
ладочной информации из табл.
надобится запустить приложение с применением отладчика
в окне
Output
Visual Studio
и поискать
журнальное сообщение вроде такого:
dbug: ConfiguringApps.Controllers.HomeController[O]
Handled / at uptime 12
При запуске приложения отображается много сообщений. что может затруднить
выявление отдельных сообщений. Одиночные сообщения легче найти, если щелкнуть
на кнопке
Clear All
(Очистить все) в верхней части окна
Output
и затем перезагрузить
страницу в браузере. что обеспечит отображение журнальных сообщений, относя­
щихся к единственному запросу.
426
Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC 2
Конфигурирование внедрения зависимостей
Стандартная конфигурация для приложений
ASP.NET
Саге включает подготовку
поставщика служб, который используется средством внедрения зависимостей, де­
тально описанным в главе
фигурирования в класс
Листинг
using
using
using
using
using
using
using
using
using
using
14.39.
18.
В листинге
14.39 показано добавление
операторов кон­
Program.
Конфигурирование служб в файле
Program.cs из папки ConfiguringApps
System;
System.Collections.Generic;
System. IO;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore;
Microsoft.AspNetCore.Hosting;
Microsoft.Extensions.Configuration;
Microsoft.Extensions.Logging;
System.Reflection;
namespace Conf iguringApps {
puЬlic class Program {
static void Main ( string [] args) {
BuildWebHost(args) .Run();
puЫic
static IWebHost BuildWebHost(string[] args) {
return new WebHostBuilder()
. UseKestrel ()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration( (hostingContext, config) => {
config.AddJsonFile("appsettings.json",
optional: true, reloadOnChange: true);
puЫic
config.AddEnvironmentVariaЬles();
i f (args != null) {
config.AddCommandLine(args);
)
})
.ConfigureLogging( (hostingContext, logging) => {
logging.AddConfiguration(
hostingContext.Configuration.GetSection("Logging" ));
logging.AddConsole();
logging.AddDebug();
))
.UseIISintegration()
.UseDefaultServiceProvider((context, options) =>
options.ValidateScopes =
context.HostingEnvironment.IsDevelopment();
})
.UseStartup<Startup>()
.Build ();
Глава 14. Конфигурирование nриложений
Метод
UseDefaultServiceProvider ()
ASP.NET Core.
427
применяет встроенный поставщик служб
Хотя доступны альтернативные поставщики служб, в большинстве про­
ектов подойдут встроенные средства, а использовать сторонний компонент рекомен­
дуется только при решении специфической задачи и наличии хорошего понимания
концепции внедрения зависимостей. которая будет обсуждаться в главе
UseDefaul tServiceProvider ()
объекты WebHostBuilderContext
Метод
лучает
18.
принимает лямбда-функцию, которая по­
и
ServiceProviderOptions,
применяе­
мые для конфигурирования встроенного поставщика служб. Единственное свойство
конфигурации называется
тельной при работе с
ValidateScopes,
Entity Framework Core,
Конфигурирование служб
Когда метод
са
Startup,
AddMvc ()
и его установка в
false
как объяснялось в главе
является обяза­
8.
MVC
вызывается внутри метода
ConfigureServices ()
клас­
он настраивает все службы, которые требуются приложениям
Преимущество такого подхода связано с удобством, поскольку все службы
MVC
MVC.
регис­
трируются за один шаг, но если необходимо изменить стандартное поведение, тогда
понадобится выполнить дополнительную работу по реконфигурированию служб.
Метод
AddMvc ()
инфраструктура
возвращает объект, реализующий интерфейс
MVC
IMvcBuilder,
а
предлагает набор расширяющих методов, предназначенных
для расширенного конфигурирования, наиболее полезные из которых описаны в
табл.
14.15.
Многие методы конфигурирования связаны со средствами. рассматрива­
емыми в последующих главах.
Таблица
14.15.
Полезные расширяющие методы
IMvcBuilder
Описание
Имя
AddМvcOptions
()
Этот метод конфигурирует службы, используемые инфра­
структурой
AddFormatterMappings()
MVC,
как объясняется ниже
Этот метод применяется для конфигурирования средства,
которое позволяет клиентам указывать формат получае­
мых данных (глава
AddJsonOptions ()
20)
Этот метод используется для конфигурирования способа
создания данных
AddRazorOptions()
JSON
(глава
20)
Этот метод применяется для конфигурирования механиз­
ма визуализации
AddViewOptions ()
Razor
(глава
21)
Этот метод используется для конфигурирования способа
обработки представлений инфраструктурой
MVC,
в том
числе применяемые механизмы визуализации (глава
Метод
AddMvcOptions ()
конфигурирует самые важные службы МVС. Он прини­
мает функцию, получаюшую объект
MvcOptions,
который предоставляет набор кон­
фигурационных свойств: наиболее полезные свойства приведены в табл.
Перечисленные в табл.
21)
14.16 методы
14.16.
конфигурирования используются для точ­
ной настройки способа функционирования инфраструктуры
MVC,
и в указанных
главах вы найдете подробные описания связанных с ними средств. Тем не менее,
в качестве небольшой демонстрации в листинге
нять метод
AddMvcOpt.ions ()
14.40
показано, как можно приме­
для изменения параметра конфигурации.
428
Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC 2
Таблица
14.16.
Избранные свойства
MvcOptions
Имя
Описание
Conventions
Это свойство возвращает список соглашений модели,
которые используются для настройки способа создания
контроллеров и действий инфраструктурой
Filters
MVC
(глава
31)
Это свойство возвращает список глобальных фильтров
(глава
FormatterMappings
19)
Это свойство возвращает сопоставления, применяемые
для того, чтобы позволить клиентам указывать формат
получаемых ими данных (глава
InputFormatters
20)
Это свойство возвращает список объектов, используе­
мых для разбора данных запросов (глава
ModelBinders
20)
Это свойство возвращает список связывателей модели,
которые применяются для разбора запросов (глава
ModelValidatorProviders
Это свойство возвращает список объектов, используе­
мых для проверки достоверности данных (глава
OutputFormatters
27)
Это свойство возвращает список классов, которые фор­
матируют данные, отправляемые из контроллеров
(глава
RespectBrowserAcceptHeader
Это свойство указывает, должен ли учитываться заголо­
вок
14.40.
Accept,
когда принимается решение о том, какой
Изменение параметра конфигурации в файле
из папки
API
20)
формат данных задействовать для ответа (глава
Листинг
26)
20)
Startup. cs
ConfiguringApps
void ConfigureServices(IServiceCollection services) {
services.AddSingleton<UptimeService>();
puЫic
services.Adc!Мvc() .AddМvcOptions(options
=> {
options.RespectBrowserAcceptHeader = true;
}) ;
Лямбда-выражение, передаваемое методу
ект
MvcOptions, который
AcceptHeader в true. Это
AddMvcOptions (). принимает объ­
RespectBrowser
используется для установки свойства
изменение позволяет клиентам оказывать большее влия­
ние на формат данных. выбираемый процессом согласования содержимого. как будет
объясняться в главе
20.
Работа со сложными конфигурациями
Если вы нуждаетесь в поддержке большого количества сред размещения или меж­
ду средами размещения имеется много отличий, тогда применение операторов
для ветвления конфигураций в классе
Startup
i f
может в результате дать конфигура­
цию. которую трудно читать и редактировать, не вызывая неожиданных изменений.
Глава 14. Конфигурирование приложений
429
В последующих разделах будут описаны разнообразные способы использования клас­
са
Startup
для сложных конфигураций.
Создание разных внешних конфигурационных файлов
Стандартная конфигурация для приложения, обеспечиваемая классом
ищет конфигурационные файлы
JSON,
ния. где запускается приложение. Таким образом, файл по имени
production. j son
JSON
Листинг
в класс
14.41.
Prograrn,
14.41
восстановлен оператор. который загружает
удаленный в начале главы.
Загрузка файлов среды в файле
из папки
using
using
using
using
using
using
using
using
using
using
appsettings.
можно применять для хранения настроек. специфичных для про­
изводственной среды. В листинге
файл
Prograrn.
которые специфичны к среде размеще­
Program. cs
ConfiguringApps
System;
System.Collections.Generic;
System.IO;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore;
Microsoft.AspNetCore.Hosting;
Microsoft.Extensions.Configuration;
Microsoft.Extensions.Logging;
System.Reflection;
namespace ConfiguringApps {
puЫic class Program {
static void Main ( striпg [] args) {
BuildWebHost(args) .Run();
р11Ыiс
puЫic
static IWebHost BuildWebHost(string[] args) {
return new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration( (hostingContext, config) => {
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json",
optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName} .json",
optional: true, reloadOnChange: true);
config.AddEnvironmentVariaЫes();
if (args != null) {
config.AddCommandLine(args);
1
))
.ConfigureLogging( (hostingContext, logging) => {
logging.AddConfiguration(
hostingContext.Configuration.GetSection("Logging "));
logging.AddConsole();
logging.AddDebug();
})
.UseIISintegration()
430
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
.UseDefaultServiceProvider( (context, options) =>
options.ValidateScopes =
context.HostingEnvironment.IsDevelopment();
) )
.UseStartup<Startup>()
.Build();
При загрузке данных конфигурации из файла. специфичного для платформы, кон­
фигурационные настройки. которые содержатся в нем. переопределяют любые сущес­
твующие данные с теми же самыми именами. Например. в листинге
содержимое файла по имени
нением шаблона элемента
appsettings. development. j son.
ASP.NET
Coпfiguratioп
File.
приведено
Данные конфигурации в этом
файле устанавливают значение EnaЬleBrowserShortCi
Совет. Может показаться, что файл
14.42
созданного с приме­
rcui t
в
false.
appset tings. development. j son исчезает сразу же
appsettings. j son в окне
после его создания. Щелкнув на стрелке слева от записи
Solutioп
Explorer,
легко увидеть, что
Visual Studio
группирует вместе элементы с похо­
жими именами.
Листинг
14.42. Содержимое файла appsettings. development. j son
из папки ConfiguringApps
"ShortCircuitMiddleware": {
"EnaЬleBrowserShortCircuit":
false
Файл appsettings. j son загрузится при запуске приложения. а за ним файл
appset tings. developmen t. j son, если приложение выполняется в среде разработ­
ки. В результате свойство EnaЬleBrowserShort.Circui t получает значение false,
когда приложение функционирует в среде разработки, и true - когда в подготови­
тельной или производственной среде.
Создание разных методов конфигурирования
Выбор разных файлов с конфигурационными данными может быть удобен, но не
предоставлять полного решения для сложных конфигураций. т.к. файлы данных не
содержат операторы С#. Если желательно варьировать операторы конфигурирования,
используемые для создания служб или регистрации компонентов промежуточного
ПО. тогда допускается применять разные методы, причем имя метода должно вклю­
чать название среды размещения (листинг
Листинг
14.43).
14.43. Использование разных имен методов в файле Startup. cs
из папки ConfiguringApps
using System;
using System.Collections.Generic;
Глава 14. Конфигурирование приложений
using
using
using
using
using
using
using
using
431
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
ConfiguringApps.Infrastructure;
Microsoft.Extensions.Configuration;
namespace ConfiguringApps {
puЫic class Startup {
Startup(IConfiguration configuration)
Configuration = configuration;
puЫic
puЫic
IConfiguration Configuration { get; )
void ConfigureServices(IServiceCollection services) {
services.AddSingleton<UptimeService>();
services.AddMvc() .AddMvcOptions(options =>
options.RespectBrowserAcceptHeader = true;
puЫic
) ) ;
void ConfigureDevelopmentServices(IServiceCollection services) {
services.AddSingleton<UptimeService>();
puЫic
services.AddМvc();
void Configure(IApplica.tionВuilder арр, IHostingEnviroruaent env) {
app.UseExceptionHandler("/Home/Error");
app.UseStaticFiles();
app.UseМvc(routes => {
routes.MapRoute(
name : "defaul t" ,
template: "{controller=Home}/{action=Index}/{id?}");
puЬlic
}) ;
void ConfigureDevelopment(IApplicationBuilder
IHostingEnvironment env) {
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseBrowserLink();
app.UseStaticFiles();
puЬlic
арр,
app.UseМvcWithDefaultRoute();
Когда инфраструктура
Configure ()
в классе
ASP.NET Core
Startup,
ищет методы
ConfigureService ()
и
она сначала выясняет, имеются ли методы, имена
которых включают название среды размещения. В листинге
14.43
был добавлен ме­
ConfigureDeveloprnentServices (),который в среде Developrnent будет при­
меняться вместо метода ConfigureServices (),и метод ConfigureDeveloprnent (),
который в среде Developrnent будет использоваться вместо метода Configure ().
тод
432
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Можно определить отдельные методы для каждой среды, которую необходимо подде­
рживать, и полагаться на стандартные методы. вызываемые в случае недоступности
методов. специфичных для той или иной среды. В рассматриваемом примере это оз­
начает. что для подготовительной и производственной сред будут применяться мето­
ды
ConfigureServices ()
и
Configure ().
Внимание! Если определены методы, специфичные для среды, то стандартные мето­
ды не вызываются. Например, в листинге
14.43 инфраструктура ASP.NET Core не бу­
Configure () в среде Development, потому что имеется метод
ConfigureDevelopment ().Другими словами, каждый метод несет ответственность за
дет вызывать метод
полную конфигурацию, требуемую для среды, на которую он ориентирован.
Создание разных классов конфигурирования
Использование разных методов означает. что вы не обязаны применять операто­
ры
для проверки имени среды размещения, но в результате могут быть получе­
i f
ны крупные классы, которые сами по себе являются проблемой. В случае особенно
сложных конфигураций последним усовершенствованием будет создание собствен­
ного конфигурационного класса для каждой среды размещения. Когда инфраструк­
тура
ASP.NET Core
ищет класс
Startup,
она первым делом проверяет, есть ли класс с
именем, включающим название текущей среды размещения. С этой целью добавим в
проект файл класса по имени
зано в листинге
Листинг
14.44.
содержимое которого пока­
Содержимое файла
из папки
using
using
using
using
StartupDevelopment. cs,
14.44.
StartupDevelopment. cs
ConfiguringApps
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.Extensions.Dependencylnjection;
ConfiguringApps.Infrastructure;
namespace ConfiguringApps {
puЫic class StartupDevelopment {
puЬlic
void ConfigureServices(IServiceCollection services) {
services.AddSingleton<UptimeService>();
services.AddMvc();
puЬlic
void Configure(IApplicationBuilder
арр,
IHostingEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseBrowserLink();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
Класс StartupDevelopment содержит методы ConfigureServices () и
Configure (),которые специфичны для среды размещения Development. Чтобы
Глава 14. Конфигурирование nриложений
433
возможность найти класс
спе­
предоставить инфраструктуре ASP.NEТ
Core
цифичный для среды. в класс
потребуется внести изменение, показанное в
листинге
Листинг
Startup,
14.45.
14.45.
Startup, специфичного для среды,
Program. cs из папки ConfiguringApps
Включение класса
в файле
using
using
using
using
using
using
using
using
using
using
Program
System;
System.Collections.Generic;
System.IO;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore;
Microsoft.AspNetCore.Hosting;
Microsoft.Extensions.Configuration;
Microsoft.Extensions.Logging;
System.Reflection;
namespace ConfiguringApps
puЫic class Program {
puЫic static void Main(string[] args) {
BuildWebHost(args) .Run();
static IWebHost BuildWebHost(string[] args) {
return new WebHostBuilder()
. UseKestrel ()
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration( (hostingContext, config) => {
var env = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json",
optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName) .json",
optional: true, reloadOnChange: true);
puЫic
config.AddEnvironmentVariaЬles();
i f (args !=null) {
config.AddCommandLine(args);
)
))
.ConfigureLogging( (hostingContext, logging) => {
logging.AddConfiguration(
hostingContext.Configuration.GetSection("Logging "));
logging.AddConsole();
logging.AddDebug();
})
.UseIISintegration()
.UseDefaultServiceProvider( (context, options) =>
options.ValidateScopes =
context.HostingEnvironment.IsDevelopment();
))
.Usestartup(nameof(ConfiguringApps))
.Build();
434
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Вместо указания какого-то
1U1acca
методу
UseStartup ()
передается имя сборки,
которая должна использоваться. Когда приложение запускается. инфраструктура
ASP.NET Core
1U1acc, имя которого включает название среды размеще­
StartupDevelopment или StartupProduction, и возвращаться к
класса Startup, если специфичный для среды 1U1acc отсутствует.
будет искать
ния, такой как
применению
Резюме
В главе рассматривались способы конфигурирования приложений
сана роль 1U1ассов
Startup и Program,
MVC.
Была опи­
а также стандартных настроек конфигурации,
которые они обеспечивают. Кроме того, обсуждалась обработка запросов с примене­
нием конвейера и использование различных типов промежуточного ПО для управле­
ния потоком запросов и результирующих ответов. В следующей главе будет представ­
лена система маршрутизации, посредством которой инфраструктура МVС имеет дело
с отображением
URL
запросов на контроллеры и действия.
ГЛАВА
15
Маршрутизация
URL
ASP.NET предполагалось наличие прямой связи между запра­
в ранних версиях
URL
шиваемыми
и файлами на жестком диске сервера. Работа сервера заключа­
лась в получении запроса от браузера и доставке вывода из соответствующего файла.
Подобный подход успешно работал для инфраструктуры
страница
ASPX
Web Fonns,
в которой каждая
представляла собой и файл, и самодостаточный ответ на запрос.
Это совершенно не имеет смысла в приложении
MVC,
где запросы обрабатывают­
ся методами действий из классов контроллеров, и однозначное соответствие между
запросами и дисковыми файлами отсутствует.
При обработке
URL
в
MVC
платформа
ASP.NET применяет систему маршрутиза­
ASP.NET Core. В настоящей главе вы
ции, которая была модернизирована для версии
узнаете, как использовать систему маршрутизации для обеспечения мощной и гибкой
обработки
URL
в своих проектах. Вы увидите, что система маршрутизации позволяет
создавать любой желаемый шаблон
URL
и выражать его в ясной и лаконичной мане­
ре. Система маршрутизации выполняет две функции.
•
Исследование входящих
URL
и выбор контроллера и действия для обработки
запроса.
•
Генерация исходящих
URL.
Такие
URL
встречаются в НТМL-разметке. визуали­
зированной из представлений. так что специфическое действие может быть
инициировано, когда пользователь щелкает на ссылке (после чего ссылка снова
становится входящим
URL).
В главе внимание будет сосредоточено на определении маршрутов и их примене­
нии для обработки входящих
URL,
чтобы пользователь мог достичь контроллеров и
действий. Есть два способа создания маршрутов в приложении
MVC:
маршрутиза­
ция на основе соглашений и маршрутизаt~ия с помощью атрибутов. Мы рассмотрим
здесь оба подхода.
Затем в следующей главе будет показано, как использовать те же самые маршру­
ты для генерации исходящих
URL,
которые необходимо включать в представления, а
также объяснены способы настройки системы маршрутизации и продемонстрирова­
но применение связанного средства под названием области. В табл.
сводка. позволяющая поместить маршрутизацию в контекст.
В табл.
15.2
представлена сводка по главе.
15. 1 приведена
436
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Таблица
15.1.
Помещение маршрутизации в контекст
Вопрос
Ответ
Что это такое?
Система маршрутизации несет ответственность за
обработку входящих запросов, а также выбор кон­
троллеров и методов действий для их обработки.
Вдобавок система маршрутизации используется для
генерации маршрутов в представлениях, известных
как исходящие
URL
Система маршрутизации обеспечивает гибкую обра­
Чем она полезна?
ботку запросов без привязки
сов в проекте
Как она используется?
URL к
структуре клас­
Visual Studio
Сопоставление
URL
с контроллерами и методами
действий определяется в файле
путем применения атрибута
Startup. cs или
Rou te к контроллерам
Существуют ли какие-то скрытые
Конфигурация маршрутизации для сложного прило­
ловушки или ограничения?
жения может стать трудной в управлении
Существуют ли альтернативы?
Нет. Система маршрутизации
-
неотъемлемая часть
ASP.NET Core
Таблица
15.2.
Сводка по главе
Задача
Решение
Листинг
Определите маршрут
15.9
Возможность не указывать сегменты
Определите стандартные значения
URL
для сегментов маршрута
15.1015.12
Отображение между
URL
и методами
действий
Сопоставление сегментов
URL,
кото­
Определите статические сегменты
15.1315.16
Определите специальные переменные
15.1715.19
рые не имеют соответствующих пере­
менных маршрутизации
Передача сегментов
URL
методам
действий
сегментов
Возможность не указывать сегменты
Определите необязательные сегменты
15.20,
15.21
Используйте сегмент общего захвата
15.22,
15.23
Применяйте ограничения маршрута
15.2415.33
Определение маршрута внутри
Используйте маршрутизацию с помощью
контроллера
атрибутов
15.3415.38
URL,
для которых не предусмотрены
стандартные значения
Определение маршрутов, которые
соответствуют любому количеству
сегментов
URL
Ограничение
URL,
которым может
соответствовать маршрут
Глава 15. Маршрутизация URL
437
Подготовка проекта для примера
Empty (Пустой) по имени
ASP.NET Core Web Application (.NET Core)
Для целей главы мы создадим новый проект типа
UrlsAndRoutes
(Веб-приложение
с применением шаблона
ASP.NET Core (.NET Core)).
показанные в листинге
15. 1,
Startup операторы.
MVC Framework. страниц
Добавим в класс
чтобы обеспечить поддержку
ошибок для разработчиков и статических файлов.
Листинг
15.1.
Конфигурирование приложения в файле
из папки
using
using
using
using
using
using
using
using
Startup. cs
UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
namespace UrlsAndRoutes {
puЫic class Startup {
puЫic void ConfigureServices(IServiceCollection services) {
services.AddМvc();
void Configure(IApplicationBuilder
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
puЬlic
арр,
IHostingEnvironment env) {
app.UseМvc();
Создание класса модели
Все усилия в текущей главе сконцентрированы на сопоставлении
URL
запросов с
действиями. Единственному имеющемуся классу модели передаются детали о конт­
роллере и методе действия. которые были выбраны для обработки запроса. Создадим
папку
Models
и добавим в нее файл класса по имени
веденным в листинге
Листинг
15.2.
Resul t. cs
15.2.
Содержимое файла
Resul t. cs
из папки
Models
using System.Collections.Generic;
namespace UrlsAndRoutes.Models {
puЬlic class Result {
puЫic string Controller { get; set; }
puЫic striпg Action { get; set; }
puЫic IDictionary<string, object> Data { get; }
= new Dictionary<striпg, object>();
с определением. при­
438
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Свойства
Controller
и
Action будут использоваться для указания того, как за­
Data - для хранения других деталей о запросе, выда­
прос был обработан, а словарь
ваемых системой маршрутизации.
Создание примеров контроллеров
Для демонстрации работы маршрутизации нам нужны простые контроллеры.
Создадим папку
cs
Controllers
и добавим в нее файл класса по имени
с содержимым из листинга
HomeController.
15.3.
Листинг 15.З. Содержимое файла
HomeController.cs
из папки
Controllers
using Microsoft.AspNetCore.Mvc;
using UrlsAndRoutes.Models;
namespace UrlsAndRoutes.Controllers
puЫic
class HomeController : Controller
ViewResult Index() => View("Result",
new Result {
Controller = nameof(HomeController),
Action = nameof(Index)
puЬlic
)) ;
Метод действия
определенный в контроллере
Home, вызывает метод
Resul t (которое определяет­
ся в следующем разделе) и передает экземпляр Resul t в качестве объекта модели.
Свойства объекта модели устанавливаются с применением операции nameof; они
Index (),
для визуализации представления по имени
View ()
будут использоваться для указания, какие контроллер и метод действия бьmи задейс­
твованы для обслуживания запроса.
Аналогичным
образом
CustomerContr·oller. cs
Листинг
15.4.
добавим
в
папку
Со
с определением контроллера
Содержимое файла
namespace UrlsAndRoutes.Controllers
class CustomerController : Controller
ViewResul t Index () => View ( "Resul t",
new Result {
Controller = nameof(CustomerController),
Action = nameof(Index)
puЫic
) ) ;
ViewResult List() => View("Result",
new Result {
Controller = nameof(CustomerController),
Action = nameof(List)
puЫic
) ) ;
о 11 е r s
файл класса
Customer (листинг 15.4).
CustomerController.cs
using Microsoft.AspNetCore.Mvc;
using UrlsAndRoutes.Models;
puЫic
nt r
из папки
Controllers
Глава 15. Маршрутизация URL
Третий (и последний) контро;шер определяется в файле по имени AdminCon tro l
(листинг
внутри папки
Controllers
Листинг
Содержимое файла
15.5.
15.5).
439
ler. cs
Он следует тому же подходу. как и ранее.
AdminController. cs
из папки
Controllers
using Microsoft.AspNetCore.Mvc;
using UrlsAndRoutes.Models;
namespace UrlsAndRoutes.Controllers
puЫic
class AdminController : Controller
ViewResult Index() => View("Result",
new Result {
Controller = nameof(AdminController),
Action = nameof(Index)
puЬlic
}) ;
Создание представления
Во всех методах действий, определенных в предыдущем разделе, указано представ­
ление
Resul t.
что позволяет создать одно представление, которое будет разделяться
всеми контроллерами. Создадим папку
нее новый файл
представления по имени
в листинге
Листинг
15.6.
Views/Shared и добавим в
Resul t. cshtml с содержимым, показанным
Содержимое файла
Result.cshtml
из папки
15.6.
Views/Shared
@model Result
@{ Layout = null;
< ! DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Routing</title>
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.
min.css" />
</head>
<body class="m-1 р-1">
<tаЫе class="taЫe taЬle-bordered taЬle-striped taЫe-sm">
<tr><th>Controller:</th><td>@Model.Controller</td ></tr>
<tr><th>Action:</th><td>@Model.Action</td></tr>
@foreach (string key in Model.Data.Keys) 1
<tr><th>@key :</th><td>@Model.Data[key]</td></tr>
</tаЫе>
</body>
</html>
Bootstrap таблицу, которая
Bootstrap в проект, создадим
файл bower. j son, используя шаблон элемента Bower Configuration File (Файл конфи­
гурации Bower). и укажем пакет Bootstrap в разделе dependencies (листинг 15.7).
Представление содержит стилизованную с помощью
отображает свойства объекта модели. Чтобы добавить
440
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Листинг 15. 7. Добавление пакета Bootstrap в файле Ьower. j
из папки
son
UrlsAndRoutes
"name": "asp.net ",
"private": true ,
"dependen cie s": {
"bootstrap": "4. О. 0-alpha. 6"
Финальный подготовительный шаг предусматривает создание в папке
файла_ Viewimports.
cshtml,
гательные функции дескрипторов для применения в представлениях
тируется пространство имен модели (листинг
Листинг
V i ew s
внутри которого настраиваются встроенные вспомо­
Razor
и импор­
15.8).
15.8. Содержимое файла_Viewimports. cshtml из папки Views
@using UrlsAndRoutes.Models
@addTagHelpe r *, Microsoft.AspNetCore.Mvc.TagHelpers
Конфигурация в классе
того. как инфраструктура
Startup
MVC
не содержит никаких инструкций относительно
должна сопоставлять НТТР-запросы с контроллерами
и действиями. После запуска приложения любой запрашиваемый
результате ответ
404 -
Not Found (404 -
С l0<~ikost5"1839
С
ф
URL будет давать
15.1.
в
не найдено). как видно на рис .
Х
localhos ~~ ,9
Status Code: 404; Not Found
Рис.
15.1.
Введение в шаблоны
Запуск примера приложения
URL
Система маршрутизации работает с использованием множества маршрутов .
Вместе такие маршруты образуют схему
бой набор
URL.
URL для
приложения. представляющую со­
которые приложение будет распознавать и реагировать на них.
Набирать вручную все индивидуальные
URL,
которые планируется поддерживать
URL. с кото­
URL соответствует шаблону. тогда
обработки этого URL. Начнем с про­
в приложении. не придется . Взамен каждый маршрут содержит шаблон
рым сравниваются входящие
URL.
Если входящий
он применя ется системой маршрутизации для
стого
URL:
http://mysite.com/Adrnin/Index
URL
могут быть разбиты на сегменты
-
части
URL кроме имени хоста и строки
/ . В приведенном выше примере
запроса , которые отделяются друг от друга символом
URL есть
два сегмента. как показано на рис.
15.2 .
Глава 15. Маршрутизация URL
441
http://mysite.com/Adm in/Index
t
t
Рис.
Первый
Второй
сегмент
сегмент
Сегменты в примере
15.2.
Первый сегмент содержит слово
Admin,
а второй
-
URL
слово
Index.
В глазах чело­
века совершенно очевидно, что первый сегмент имеет отношение к контроллеру, а
второй
-
к действию. Однако. естественно, такое отношение должно быть выражено
URL, который будет в состоянии воспринимать
URL. соответствующий примеру URL:
с использованием шаблона
система
маршрутизации. Вот шаблон
{controllerf/{action}
При обработке входящего НТТР-запроса задача системы маршрутизации заключа­
ется в сопоставлении запрошенного
URL
с шаблоном и извлечении из
URL
значений
для переменных сегментов, определенных в шаблоне.
Переменные сегментов выражаются с применением фигурных скобок (символов
{
}).
и
В приведенном выше примере шаблона присутствуют две переменные сег­
ментов с именами
controller
будет
Приложение
controller и action, так что
Admin, а значением переменной
MVC
значением переменной сегмента
сегмента
action -
Index.
обычно будет иметь несколько маршрутов, и система маршру­
тизации будет сравнивать входящий
URL
с шаблоном
URL каждого маршрута до тех
URL будет соответствовать
пор, пока не найдет совпадение. По умолчанию шаблон
любому
URL,
который имеет подходящее количество сегментов. Например, шаблон
{controller} / { action}
как описано в табл.
будет соответствовать любому
с двумя сегментами,
URL
15.3.
Таблица 15.З. Сопоставление
URL
URL запроса
Переменные сегментов
http://mysite.com/Admi.n/I ndex
controller=Admin,action=I ndex
h t tp: / /mys i te. com/ Admi.n
Соответствия нет
-
ht tp: / /mysi te. com/Admin/Index/Soccer
Соответствия нет
- сегментов слишком много
В табл.
•
15.3
Шаблоны
сегментов слишком мало
отражены две ключевые линии поведения шаблонов
URL
URL.
консервативны в отношении количества сопоставляемых сегмен­
тов. Совпадение будет происходить только для тех
URL,
которые имеют то же
самое количество сегментов. что и шаблон. Это можно наблюдать во втором и
третьем примерах в таблице.
•
Шаблоны
тов. Если
URL либеральны в отношении содержимого сопоставляемых сегмен­
URL имеет правильное количество сегментов, то шаблон извлечет зна­
чение каждого сегмента для переменной сегмента, каким бы оно ни было.
Описанные две стандартные линии поведения являются ключом к пониманию
функционирования шаблонов
URL.
стандартные линии поведения.
Позже в главе будет показано, как изменить такие
442
Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC 2
Создание и регистрация простого маршрута
Имеющийся
шаблон
URL
можно
Маршруты определяются в файле
методу
UseMvc (),
В листинге
15.9
использовать для
Startup. cs
определения
маршрута.
и передаются в качестве аргументов
который применяется для настройки
MVC
в методе
Configure ().
показан базовый маршрут. отображающий запросы на контроллеры
в примере приложения.
Листинг
15.9.
Определение базового маршрута в файле
из папки
using
using
using
using
using
using
using
using
Startup. cs
UrlsAnd.Routes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.DependencyinJection;
namespace UrlsAndRoutes {
puЫic class Startup {
void ConfigureServices(IServiceCollection services) {
services.AddMvc();
puЬlic
puЬlic
void Configure(IApplicationBuilder арр, IHostingEnvironment env) {
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
арр. UseМvc (routes => {
routes.МapRoute(name: "default", template: "{controller}/{action}");
}) ;
Маршруты создаются с использованием лямбда-выражения, передаваемого в виде
аргумента конфигурационному методу
ект. который реализует интерфейс
UseMvc ().Лямбда-выражение получает объ­
IRouteBuilder из пространства имен Microsoft.
AspNetCore. Routing, а маршруты определяются с применением расширяющего ме­
тода MapRoute (). Чтобы облегчить понимание маршрутов, по соглашению при вы­
зове метода MapRoute () указываются имена аргументов, что объясняет наличие в
листинге явно именованных аргументов name и template. В аргументе name указы­
вается имя маршрута, а в аргументе template определяется шаблон.
Совет. Назначать маршрутам имена не обязательно, и существует философский аргумент
относительно того, что именование приносит в жертву четкое разделение обязанностей,
которое в противном случае обеспечила бы маршрутизация. В разделе "Генерирование
URL
из специфического маршрута" главы
стать проблемой.
16
объясняется, почему именование может
Глава 15. Маршрутизация URL
443
Запустив пример приложения. можно увидеть эффект от изменений. внесенных
в маршрутизацию. При первом запуске приложения ничего не изменится
жнему возникает ошибка
лону
404,
но если перейти на
{controller} / {action}.
URL.
-
по-пре­
который соответствует шаб­
тогда получится результат, подобный приведенному
на рис. 15.З. где иллюстрируется переход на
/Admin /Index.
<D localhost 54839/Adnм/lndex
С
Controller:
AdminController
Action:
lndex
Рис. 15.З. Навигация с использованием простого маршрута
Корневой
в файле
URL для
приложения не работает из-за того. что маршрут. добавленный
Startup.cs.
не сообщает инфраструктуре
контроллера и метод действия. когда
URL запроса
MVC
о том. как выбирать класс
не содержит сегментов. Мы испра­
вим это в следующем разделе.
Определение стандартных значений
Пример приложения возвращает ошибку
ный
URL.
404.
когда запрашивается стандарт­
поскольку он не соответствует шаблону маршрута. определ енному в классе
StaL· t up . Так
как в стандартном
ветствовать переменным
URL отсутствуют
contr o lle r
и
act i o n.
сегменты. которые могли бы соот­
определенным в шаблоне маршрута.
система маршрутизации не дает совпадения.
Ранее объяснялось. что шаблоны
URL будут
соответствовать только
URL с указан­
ным количеством сегментов. Один из способов изменить такое поведение предусмат­
ривает применение стандартных значений. Стандартное значение используется .
когда
URL
не содержит сегмент. который можно было бы сопоставить с шаблоном
маршрута . В листинге
15. 10
определен маршрут. в котором применяется стандарт­
ное значени е.
Листинг
15.1 О.
Предоставление стандартного значения в файле
из папки
us ing
using
us i ng
u s i ng
using
using
u s ing
using
UrlsAndRoutes
System ;
System.Co lle c tions.Gen er i c;
Sy s t e m.Linq;
Sy stem.Th reading.Tas ks;
Microsoft.AspNetCore.Bu i l de r;
Microsoft.Asp NetCore.Hos ting ;
Microsoft.AspNet Core.Http;
Micr osof t.Extensi o ns.Dependenc yinjec ti on;
namespace Url s AndRout es
puЫi c c l a ss S t a r tup {
Startup. cs
444
Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC 2
void ConfigureServices (IServiceColle ctio n services)
services.AddMvc();
puЬli c
void Configure (IAppli cationBuilder
app.UseDevel operExcepti o n Page ();
app.UseStatu s CodePages( ) ;
app.UseStaticFiles();
puЫic
app.UseМvc(routes
арр,
IHostingEnvi r onment env ) {
=> {
routes.МapRoute(
name: "default",
template: "{controller}/{action}",
defaul ts: new { action = "Index" } ) ;
}) ;
Стандартные значения задаются как свойства анонимного типа, передаваемого
методу
MapRoute ()
в аргументе
de fa ul ts. В листинге 15.10 для
I n dex.
переменной
acti on
предоставлено стандартное значение
URL.
URL вида ht tp : / /mydoma i n . com/Home / Index маршрут
извлечет Home в качестве значения для control1er и I ndex - для act ion.
Но теперь. когда имеется стандартное значение для сегмента ас t i on. маршрут
будет также соответствовать и односегментным URL. При обработке односегментно­
го URL система маршрутизации извлечет значение для переменной c on tr o1ler из
URL и будет применять стандартное значение для переменной acti on . Таким обра­
зом, запрос пользователем / Home приведет к тому. что MVC вызовет метод действия
Index () контроллера Home (рис. 15.4).
Как и ранее, этот маршрут будет соответствовать всем двухсегментным
Например. в случае запроса
С
Ф
localhost S4
Controller:
AdminController
Action:
lndex
Рис.
15.4.
Использование стандартного действия
Определение встраиваемых стандартных значений
Станда ртные значения могут также быть выражены как часть шаблона
URL,
что дает более лаконичный способ определения маршрутов. как показано в листин­
ге
15. 11.
Встраиваемый синтаксис может применяться только для предоставления
стандартных значений переменным. которые являются частью шаблона
URL.
но. как
вскоре вы узнаете. зачастую удобно иметь возможность устанавливать стандартные
значения за пределами шаблона. По указанной причине важно понимать оба способа
выражения стандартных значе ний .
Глава 15. Маршрутизация URL
Листинг
15.11.
445
Определение встраиваемых стандартных значений
в файле
Startup.cs
из папки
UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
using
using
using
using
using
using
using
using
namespace UrlsAndRoutes {
puЫic class Startup {
void ConfigureServices(IServiceCollection services) {
services.AddMvc();
puЫic
void Configure(IApplicationBuilder
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
routes.MapRoute(
name: "default",
puЫic
арр,
IHostingEnvironment env) {
template: "{controller}/{action=Index}");
1);
Мы можем пойти еще дальше и сопоставлять с
URL,
которые вообще не содержат
переменных сегментов, полагаясь при идентификации действия и контроллера толь­
ко на стандартные значения. Для примера в листинге
ражение корневого
URL
15.12
демонстрируется отоб­
приложения за счет предоставления стандартных значений
для обоих сегментов.
Листинг
15.12.
Предоставление стандартных значений для переменных
и
using
using
using
using
using
using
using
using
action
в файле
Startup.cs
из папки
controller
UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
namespace UrlsAndRoutes {
puЬlic class Startup {
void ConfigureServices(IServiceCollection services) {
services.AddMvc();
puЫic
446
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
void Configure( I ApplicationBuilder
app.Us eDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app .UseMvc(ro utes => {
routes.MapRoute(
name: "defaul t",
puЫic
арр,
IHos tingEnvi ronment env) {
template: "{controller=Home}/{action=Index}"};
}) ;
За счет предоставления стандартных значений для переменных
action маршрут
(табл. 15.4).
Таблица
будет соответствовать
URL
co ntroller
15.4. Соответствие URL
Количество
сегментов
На что отображается
Пример
1
controller=Home, action =Index
/Customer
controller=Customer, action=Index
2
/Customer/List
controller=Customer, action=List
3
/C ustomer/List/All
Соответствия нет
о
и
с нулем. одним или двумя сегментами
Чем меньше сегментов получено во входящем
URL.
- сегментов слишком много
тем больше маршрут полагает­
ся на стандартные значения, вплоть до момента, когда
URL
без сегментов сопостав­
ляется с использованием только стандартных значений.
Запустив пример приложения, можно увидеть результат применения стандар­
тных значений. Когда браузер запрашивает корневой
менных сегментов
controller
и
acti on
чения, которые приведут к тому, что инфраструктура
Index ()
контроллера
Home
С\ Routing
С
(рис.
MVC
вызовет метод действия
х
ф locafhost 54339
HomeController
Action:
lndex
15. 5.
приложения. для пере­
15.5).
Controller:
Рис.
URL
будут использоваться стандартные зна ­
Применение стандартных значений для расширения
области действия маршрута
Глава 15. Маршрутизация URL
Использование статических сегментов
Не все сегменты в шаблоне
URL
447
URL
должны быть переменными. Допускается так­
же создавать шаблоны, имеющие статические сегменты. Предположим, что при­
ложение нуждается в сопоставлении с
URL,
которые снабжены префиксом PuЬlic,
например:
http://mydomain.com/PuЫic/Home/Index
Это можно сделать с применением шаблона
тинге
Листинг
15.13.
подобного показанному в лис­
Использование статических сегментов в файле
из папки
using
using
using
using
using
using
using
using
URL,
15.13.
Startup. cs
UrlsAnd.Routes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
namespace UrlsAndRoutes {
puЫic class Startup {
void ConfigureServices(IServiceCollection services) {
services.AddMvc();
puЫic
void Configure(IApplicationBuilder арр, IHostingEnvironment env) {
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}");
puЫic
routes.мapRoute(name:
template:
"",
"PuЬlic/{controller=Home}/{action=Index}");
}) ;
Новый шаблон будет соответствовать только
URL,
содержащим три сегмента, пер­
вым из которых должен быть PuЫic. Остальные два сегмента могут содержать лю­
бые значения, и они будут использоваться для переменных
controller
и
action.
Если последние два сегмента не указаны, тогда будут применяться стандартные
значения.
Можно также создавать шаблоны
URL,
которые имеют сегменты, содержащие как
статические, так и переменные элементы, вроде шаблона в листинге
15.14.
448
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Листинг
using
using
using
using
using
using
using
using
15.14.
Смешивание сегментов в файле
Startup. cs
из папки
UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
namespace UrlsAndRoutes {
puЫic class Startup {
puЫic
void ConfigureServices(IServiceCollection services) {
services.AddMvc();
puЫic
void Configure(IApplication8uilder арр, IHostingEnvironment env ) {
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
routes .МapRoute ("", "Х{ controller} / {action} ") ;
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}");
routes.MapRoute(name: "",
template: "PuЬlic/{controller = Home}/{action=Index}"I;
}) ;
Шаблон в показанном маршруте соответствует любому двухсегментному
котором первый сегмент начинается с буквы Х . Значение для
первого сегмента, исключая Х. Значение для
action
CJ
R.outing
С
URL вида /XHome/ Index.
(рис. 15.6).
ф localhost 54839/XHor.e/lndex
HomeController
Action:
lndex
в
можно увидеть эффект
х
Controller:
URL.
берется из
получается из второго сегмента.
Запустив приложение и перейдя на
от добавления нового маршрута
controller
Рис. 15.б. Смешивание в одном сегменте статических
и переменных элементов
Глава 15. Маршрутизация URL
449
Упорядочение маршрутов
В листинге
15.14
новый маршрут был определен и помещен перед другими маршрутами.
Я поступил так потому, что маршруты применяются в порядке, в котором они определе­
ны. Метод
MapRoute ()
добавляет маршрут в конец конфигурации маршрутизации, а это
означает, что обычно маршруты применяются в порядке их определения. Формулировка
"обычно" объясняется тем, что есть методы, которые позволяют вставлять маршруты в спе­
цифические позиции. Я стараюсь не использовать такие методы, поскольку применение
маршрутов в порядке их определения упрощает понимание маршрутизации в приложении.
Система маршрутизации пытается сопоставить входящий
URL
с шаблоном
URL
маршрута,
который был определен первым, и продолжает сопоставление со следующим маршрутом
только в случае, если не произошло совпадение. Маршруты проверяются последовательно,
пока не обнаружится соответствие или не исчерпается набор маршрутов. По указанной при­
чине самые специфичные маршруты должны быть определены первыми. Маршрут, добав­
ленный в листинге
15.14,
является более специфичным, чем следующие за ним маршруты.
Предположим, что порядок следования маршрутов изменен на обратный:
routes.MapRoute("MyRoute", "{controller=Horne)/{action=Index)");
r о u t е s . Мар Rо u t е ( " " , "Х { с о n t r о 11 е r ) / {а с t i о n ) " ) ;
Тогда первый маршрут, который соответствует любому
URL с
нулем, одним или двумя сегмен­
тами, будет использоваться всегда. Более специфичный маршрут, который теперь второй в
списке, никогда не будет достигнут. Новый маршрут исключает ведущую букву Х из
это не будет сделано из-за совпадения со старым маршрутом. Таким образом,
URL
URL,
но
вида:
http://rnydornain.com/XHorne/Index
будет нацелен на контроллер по имени
класса
XHomeController
Статические сегменты
XHorne,
предполагая существование в приложении
и наличие в нем метода действия
URL
Index ().
и стандартные значения можно комбинировать с це­
URL. После развертывания приложе­
URL формирует контракт с пользователями, и если в будущем
производится рефакторинг приложения, то предыдущий формат URL необходимо
предохранить, чтобы любые URL, добавленные в закладки, макросы или сценарии,
лью создания псевдонима для специфического
ния применяемая схема
написанные пользователем, продолжили работать.
Представим, что у нас использовался контроллер по имени
заменен контроллером
Home.
предохранения старой схемы
Листинг
15.15
Shop,
который теперь
показано, как создать маршрут для
URL.
15.15. Сегменты и стандартные значения в файле Startup. cs
из папки
using
using
using
using
using
using
using
using
В листинге
UrlsAndRoutes
Systern;
Systern.Collections.Generic;
Systern.Linq;
Systern.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
450
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
n amespace Url s AndR o u tes (
puЫic clas s Startup (
puЫic
v oi d Configur eSe rvices( ISe r v iceColl ec tion servi ces ) (
servi c e s .AddMvc () ;
puЫ ic
void Con f igu r e(I Applica tionB u ilder
app. Us e De veloperE xc e p tionPag e( );
app.Us e S t atusCod ePag es();
app.Us e StaticFil es ();
app. Us eM vc( r o ut es => {
а рр ,
I Hos ting Environment e nv ) (
routes.мapRoute(
name: "ShopSchema" ,
template: "Shop/ {action}",
defaults: new { controller =
rout e s. Ma pRo ute ( "",
"Х
"Ноше"
}) ;
{ contr o l l er) / { a ct i o n) " ) ;
r o u tes .MapRoute(
name : "default",
t e mpl a te: "{c o n tro ller=H o me) /(action =Ind e x)");
rout es .MapRoute(n a me: "",
templa te: "PuЬli c/ (contr o l l e r= Home)
/ { actio n=Ind e x ) " ) ;
}) ;
Добавленный маршрут соответствует любому двухсегментному
п е рвым сегментом является
Шаблон
URL
Shop . Значение a c ti on
не содержит переменного сегмента для
ся стандартное значение. Аргумент
defa ults
URL,
в котором
берется из второго сегмента
cont r o ller.
URL.
поэтому выбирает­
предоставляет значение
controll er .
потому что отсутствует какой-либо сегмент, к которому можно было бы применить
значение как часть шаблона
URL.
В результате запрос действия из контроллера
Shop транслируется в запрос для
URL вида / Shop/ Index. можно
оценить эффект от добавления такого маршрута. Как видно на рис. 15.7, новый мар­
шрут приводит к тому. что инфраструктура MVC вызывает метод действия Index ()
контроллера
Home . Запустив
приложение и перейдя на
из контроллера Н оте.
С
1
<D
loca l host :54839/Shop/lлdex
Controller:
HomeController
Action:
lпdex
Рис.
15. 7. Создание псевдонима для предохранения схемы URL
Глава 15. Маршрутизация URL
451
Можно продвинуться на шаг дальше и создать псевдонимы также для методов
действий, которые в результате рефакторинга перестали суmествовать в контролле­
URL
15.16).
ре. Чтобы сделать это. понадобится просто создать статический
стандартные значения для
Листинг
15.16.
и
action
(листинг
и предоставить
Создание псевдонима для контроллера и действия в файле
из папки
using
using
using
using
using
using
using
using
controller
Startup. cs
UrlsAndRoutes
System;
System.Collections.Gener ic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Bui lder;
Microsoft.AspNetCore.Hos ting;
Microsoft.AspNetCore.Http ;
Microsoft.Extensions.Dep endencyinjection;
namespace UrlsAndRoutes {
puЫic class Startup {
void ConfigureServices(IServi ceCollection services) {
services.AddMvc();
puЫic
void Configure ( IApplicationBuilder
app.UseDeveloperExceptio nPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
puЫic
арр,
IHostingEnvironment env) {
routes.мapRoute(
name: "ShopSchema2" ,
template: "Shop/OldAction",
defaults: new { controller
"Home", action
"Index" } ) ;
routes.MapRoute(
name: "ShopS chema",
template: "Shop/ { action} ",
defaults: new { controller = "Home" });
routes. MapRoute ( "",
"Х
{ control ler} / { action} ") ;
routes.MapRoute(
name: "default",
template: "{controller=Home}/{actio n=Index}");
routes.MapRoute(name: "",
template: "PuЬlic/{controller=Home}/{action=Index}");
}) ;
Обратите внимание. что новый маршрут определен первым, поскольку он явля­
ется более специфичным. чем маршруты, следующие после него. Если запрос для
Shop/OldAction обработается, скажем, следующим определенным маршрутом, тогда
может быть получен результат, отличающийся от того, который желательно получить
при наличии контроллера с методом действия
OldAction ().
Часть 11. Подробные сведения об инфраструктуре ASP.NET Соге MVC 2
452
Определение специальных переменных сегментов
Переменные сегментов
в приложениях
MVC
controller
и
action
имеют специальное предназначение
и соответствуют контроллеру и методу действия, которые будут
использоваться для обслуживания запроса. Они являются всего лишь встроенными
переменными сегментов. и допускается также определять специальные переменные
сегментов. как показано в листинге
15. 17.
(Маршруты. определенные в предыдущем
разделе, удалены, чтобы можно было начать сначала.)
Листинг
15.17.
Определение дополнительных переменных внутри шаблона
в файле
using
using
using
using
using
using
using
using
Startup. cs
из папки
URL
UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Bu1lder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
namespace UrlsAndRoutes (
puЫic class Startup (
puЫic
void ConfigureServices(IServiceCollection services) (
services.AddMvc();
puЫic
void Configure(IApplicationBuilder
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => (
арр,
IHostingEnvironment env) {
routes .МapRoute (name: "МyRoute",
template: "(controller=Home}/{action=Index}/{id=Defaultid}");
) ) ;
Шаблон
URL
определяет стандартные переменные
специальную переменную по имени
id.
controller
и
action
плюс
URL
с ко­
Маршрут будет соответствовать
личеством сегментов от нуля до трех. Содержимое третьего сегмента присваивает­
ся переменной
id,
а при отсутствии третьего сегмента применяется стандартное
значение.
Внимание! Некоторые имена зарезервированы и не доступны для использования в качестве
имен специальных переменных сегментов. К ним относятся
Назначение первых двух очевидно, а области
В классе
(area)
controller, action
и
area.
обсуждаются в следующей главе.
Controller, который является базовым для контроллеров, определено
RouteData. Оно возвращает объект Microsoft .AspNetCore. Routing.
RouteData, предоставляющий детали о системе маршрутизации и о способе, ко-
свойство
Глава 15. Маршрутизация URL
453
торым маршрутизировался текущий запрос. Внутри контроллера можно получать
доступ к любой переменной сегмента в методе действия с применением свойства
Ro u teData. Values,
которое возвращает словарь, содержащий переменные сегмен­
та. В целях демонстрации в контроллер Ноте добавлен метод действия по имени
CustomVariaЫe () ,приведенный в листинге
Листинг
15.18.
15.18. Доступ к специальной переменной сегмента внутри метода
действия в файле HomeController. cs из папки Controllers
us i ng Microsoft.AspNetCore.Mvc ;
using Ur lsAndRout es.Models;
namespace UrlsAndRoutes.Con t r o ll e rs
puЬlic
class HomeContro ll er : Control l er
ViewResu lt Index( ) => View("Result",
new Resul t {
Controller = nameof ( HomeControl l er),
Action = nameof ( Index)
puЬlic
}) ;
ViewResul t CustomVariaЬle ()
Result r = new Result (
Controller = nameof(HomeController),
Action = nameof(CustomVariaЬle),
puЫic
} ;
r.Data["Id"] = RouteData.Values["id"];
return View("Result", r);
Метод действия Cust omV a riaЫ e
id
в шаблоне
URL
()
получает значение специальной переменной
маршрута, используя свойство
RouteData. Values,
которое воз­
вращает словарь переменных. порожденных системой маршрутизации. Специальная
переменная добавляется к объекту модели представления и может быть просмотрена
путем запуска приложения и запроса следующего
URL:
/ Home/CustomVariaЫe/Hello
Шаблон м аршрута распознает третий сегмент в данном
ременной
i d,
давая приведенный на рис.
Рис.
15.8
URL как
значение для пе­
результат.
Controller:
HomeController
Action:
CustomVariaЫe
ld:
Hello
15.8. Отображение значения специальной п еременной сегмента
454
Часть 11. Подробные сведения об инфраструктуре ASP NET Саге MVC 2
Шаблон
URL
в листинге
15.17
определяет стандартное значение для сегмента
т.е. маршрут может также соответствовать
URL.
id .
которые имеют два сегмента. Увидеть
применение стандартного значения можно. запросив такой
URL:
/Home/CustomVariaЫe
Система маршрутизации использует стандартное значение для специальной пере­
менной, как показано на рис.
f-
Рис.
С
15.9.
Q) loca lhost .54v39/Ho1YJe/CustoniVar1aЫe
Controller:
HomeController
Action:
CustomVariaЫe
ld:
Defaultld
15.9.
Стандартное значение для специальной переменной сегмента
Использование специальных переменных
в качестве параметров метода действия
Работа с коллекцией
RouteData. Values -
лишь один способ доступа к специ­
альным переменным маршрута: другой способ может оказаться намного более эле­
гантным. Если метод действия определяет параметры с именами. которые совпадают
с именами переменных шаблона
URL,
то инфраструктура
MVC будет автоматически
URL. в виде аргументов.
Специальная переменная. определенная в маршруте из листинга 15.17, имела имя id.
передавать методу действия значения. извлеченные из
Модифицируем метод действия CustomVariaЫe
()
принимал параметр с тем же самым именем (листинг
Листинг
15.19.
Controllers
using Microsoft.AspNetC ore .Mvc;
using UrlsAndRoutes.Models;
namespace UrlsAndRoutes.Controllers
puЫic
class HomeController : Controller
ViewResult Index() => View("Result",
new Result {
Controller = nameof(HomeController),
Action = nameof(Index)
puЫic
)) ;
puhlic ViewResult
CustomVariaЬle(string
Result r = new Result (
Contr o ller = name of( HomeContr o ller),
Action = nameof( Custo mVariaЬle ) ,
id)
Ho me.
чтобы он
15. 19).
Добавление параметра к действию в файле
из папки
};
в контроллере
HomeController. cs
Глава 15. Маршрутизация URL
455
r.Data["Id"] = id;
return View( "Result", r ) ;
Когда система маршрутизации обнаруживает соответствие какого - то
15.17.
сваивается специальной переменной
Инфраструктура
id .
URL марш ­
URL при ­
значение третьего сегмента
руту. который был определен в листинге
MVC
сравнивает список пе­
ременных сегментов со списком параметров метода действия. и в случае совпадения
имен передает значения из
Типом параметра
чение из
URL данному
является
id
методу.
s t r in g.
URL в любой тип, указанный для
твия объявлен как
int
или
DateTime.
но
MVC
будет пытаться преобразовать зна­
параметра. Если параметр
id
тогда полученное значение из
метода дейс­
URL
окажется
преобразованным в экземпляр этого типа . Такое элегантное и удобное средство из­
бавляет от необходимости обрабатывать преобразования самостоятельно. Запустив
приложение и запросив
URL
вида /Hom e / CustomVa r iaЬl e / Hel lo , можно увидеть
эффект от параметра метода действия (рис.
15.1 О).
Если опустить третий сегмент,
тогда методу действия будет предоставлено стандартное значение для переменной
сегмента. которое также видно на рис .
D Rout8'g
15.10.
х
с
Рис.
С
Controller:
HomeController
Action:
CustomVariaЫe
ld :
Hello
15.1 О.
ф loиlhostS·i
Controller:
HomeController
Action:
CustomVariaЫe
ld:
Defauhld
Доступ к переменным сегментов с применением параметров метода действия
URL, в типы .NET инф­
MVC использует средство привязки моделей, которое может обрабатывать
На заметку! Для преобразования значений, содержащихся внутри
раструктура
намного более сложные ситуации, чем продемонстрированная в приведенном выше при ­
мере . Привязка моделей будет описана в главе
26.
Определение необязательных сегментов
Необязательным является такой сегмент
URL
URL,
который пользователь может не
указывать и для которого не предусмотрено стандартного значения. Н ео бя зател ьный
сегмент обозначается знаком вопроса (символом ?) после им ени се гм ента. как пока­
зано в ли стинге
Листинг
15.20.
15.20.
Определение необязательного сегмента
из папки
UrlsAndRoutes
us i ng System;
u sing System . Co llections . Generi c ;
URL
в файле
Startup. cs
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
456
using
using
using
using
using
using
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
namespace UrlsAndRoutes {
puЫic class Startup {
puЫic void ConfigureServices(IServiceCollection services) {
services.AddMvc{);
puЫic
void Configure{IApplicationBuilder арр, IHostingEnvironment env) {
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
routes.MapRoute(name: "MyRoute",
template: "{controller=Home}/{action=Index}/{id?}");
} ) ;
Данный маршрут будет соответствовать
id.
В табл.
Таблица
15.5
15.5.
Количество
сегментов
URL,
имеющим или не имеющим сегмента
иллюстрируется его работа с различными
Соответствие
URL с
URL.
необязательной переменной сегмента
ПримерURL
На что отображается
/
controller=Horne,action=Index
/Customer
controller=Customer,action=Index
2
/Customer/List
controller=Custorner,action=List
3
/Customer/List/All
controller=Custorner,action=List
id=All
4
/Customer/List/All/Delete
Соответствия нет
о
-
сегментов слишком
много
В табл.
15.5
видно. что переменная
когда во входящем
URL
id
добавляется к набору переменных, только
присутствует соответствующий сегмент. Такая возможность
удобна, когда необходимо узнать, предоставил ли пользователь значение для пере­
менной сегмента. Если для необязательной переменной сегмента значение не было
задано, тогда значением соответствующего параметра будет
контроллер
id
Home
null.
В листинге
значение не указано.
Листинг
15.21.
15.21
обновлен с целью учета ситуации. когда для переменной сегмента
Проверка необязательной переменной сегмента в файле
HomeController. cs
using Microsoft.AspNetCore.Mvc;
из папки
Controllers
457
Глава 15. Маршрутизация URL
using UrlsAndRo utes.Models;
namespace UrlsAndRoutes.Co ntroll ers
puЬlic
class HomeController : Controller
ViewResu l t Index () => View ( "Resul t",
new Result {
Contr o ller = nameo f (Home Co ntr o ller ) ,
Act i o n = name o f(Ind e x)
puЫic
}) ;
ViewRe s ult CustomV ar ia Ьle(string i d ) {
Result r = new Result {
Co ntroller = nameof(H omeCo ntroller ) ,
Ac ti o n = name o f(Cust om Va r i aЬle),
puЫic
};
r.Data["Id"] =id?? "<novalue>";
re t ur n View ( "Result", r ) ;
На рис.
15. 11
приведен результат запуска приложения и перехода по
URL
вида
/ H ome /Cu s t omVari aЫ e , в котором не включено значение для переменной сегмента
С
<D localhos .548.:19/Hon1ejCustomVar1aole
Controtler:
HomeContf'Oller
Action:
CustomVariaЫe
ld :
<no value>
Рис.
15.11.
id.
Обнаружение ситуации, когда
URL
не содержит значения
для необязательной переменной сегмента
Стандартная конфигурация маршрутизации
MVC добавляется в
UseMvc Wi thDef aul tRo ute (), который
Инфраструктура
классе
St а r t u p
с
применением
метода
является просто удобным методом для настрой­
ки наиболее распространенной конфигурации маршрутизации и эквивалентен следующему
коду:
app.UseMvc( ro u tes =>
r o utes.MapRoute(
name: "def a u lt ",
t e mplate: " {con t r o ll er=Home }/ {a ct i o n=Inde x ) / {id? f");
}) ;
458
Часть 11. Подробные сведения об инфраструктуре ASP. NET Саге MVC 2
Такая стандартная конфигурация обеспечивает сопоставление с
URL,
которые нацелены на
классы контроллеров и методы действий по их именам, а также могут содержать необязатель­
ный сегмент
id.
Если сегмент
controller
или
action
опущен, тогда используются стан­
дартные значения, чтобы направить запрос контроллеру Ноте и методу действия
Index ().
Определение маршрутов переменной длины
Еще один способ изменения консервативного стандартного поведения шаблонов
URL
предусматривает прием переменного количества сегментов
маршрутизировать
URL
URL.
Это позволяет
произвольной длины в единственном маршруте. Поддержка
переменного числа сегментов определяется путем назначения одной из переменных
сегментов в качестве переменной общего захвата, для чего она предваряется симво­
лом звездочки (листинг
Листинг
15.22.
15.22).
Назначение переменной общего захвата в файле
из папки
Startup. cs
UrlsAndRoutes
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Dependencylnjection;
namespace UrlsAndRoutes {
puЫic class Startup {
puЫic
void ConfigureServices(IServiceCollection services) {
services.AddMvc();
puЫic
void Configure(IApplicationBuilder арр) {
app.UseStatusCodePages();
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseMvc(routes => {
routes.MapRoute(
name: "MyRou te",
template: "{controller=Home}/{action=Index}/{id?}/{*catchall}");
}) ;
Здесь был расширен маршрут из предыдущего примера за счет добавления пере­
менной общего захвата по имени
твовать любому
URL
catchall.
Теперь такой маршрут будет соответс­
вне зависимости от количества содержащихся в нем сегментов
либо их значений. Первые три сегмента применяются для установки значений пере­
менных
controller, action
и
id.
Если
все они присваиваются переменной
В листинге
твие
List
модели.
15.23
URL содержит
catchall,
дополнительные сегменты. то
как показано в табл.
приведен обновленный контроллер
передает представлению значение
15.6.
Customer, в котором дейс­
переменной catchall через объект
Глава 15. Маршрутизация URL
Таблица
459
15.6. Сопоставление URL с переменной общего захвата
Количество
сегментов
Пример
На что отображается
URL
/
controller=Home,action=Index
/Customer
controller=Customer,
action=Index
2
/Customer/List
controller=Customer,
action=List
3
/Customer/List/All
controller=Customer,
action=List,id=All
4
/Customer/List/All/Delete
controller=Customer,
action=List, id=All,
catchall=Delete
5
/Customer/List/All/Delete/Perm
controller=Customer,
action=List, id=All,
catchall=Delete/Perm
о
Листинг
15.23.
Модификация метода действия в файле
из папки
CustomerController.cs
Controllers
using Microsoft.AspNetCore.Mvc;
using UrlsAndRoutes.Models;
namespace UrlsAndRoutes.Controllers
puЫic class CustomerController : Controller
ViewResult Index() => View("Result",
new Resul t {
Controller = nameof(CustomerController),
Action = nameof(Index)
puЬlic
}) ;
ViewResult List(string id)
Resul t r = new Resul t (
Controller = nameof(HomeController),
Action = nameof(List),
puЫic
};
r.Data["Id"] = id ?? "<no value>";
r.Data["catchall"] = RouteData.Values["catchall"];
return View("Result", r);
Чтобы протестировать сегмент общего захвата, понадобится запустить приложе­
ние и запросить следующий
URL:
/Customer/List/Hello/1/2/3
Верхнего предела количества сегментов, для которого шаблон
руте будет давать совпадение, не существует. На рис.
15.12
URL
в этом марш­
показан эффект от опре-
460
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
деления сегмента общего захвата. Обратите внимание. что сегменты. попадающие в
переменную общего захвата. представлены в форме сегмент/ сегмент/ сегмент. и
мы сами отвечаем за обработку строки для ее разбиения на отдельные сегменты.
~
С
ф localhost·S4839/Custoiner/l1st/Hello/1/2/3
Controller:
HomeController
Action:
List
ld:
Hello
catchall:
1/2/3
Рис.
15.12.
*
Использование сегмента общего захвата
Ограничение маршрутов
В начале главы упоминалось, что шаблоны
URL консервативны
в отношении коли­
чества сопоставляемых сегментов и либеральны в отношении содержимого сопостав­
ляемых сегментов. В нескольких предшествующих разделах объяснялись различные
приемы управления степенью консервативности: увеличение или уменьшение числа
сегментов с применением стандартных значений. необязательных переменных и т.д.
Теперь наступило время взглянуть на то, каким образом управлять либеральнос­
тью при сопоставлении с содержимым сегментов
набор
URL,
URL.
а именно
-
с которыми будет сопоставляться маршрут. В листинге
руется использование простого ограничения
URL.
как ограничить
15.24
демонстри­
с которыми будет сопоставляться
маршрут.
Листинг
using
using
using
using
using
using
using
using
15.24. Ограничение маршрута в файле Startup. cs из папки UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
namespace UrlsAndRoutes {
puЫic class Startup {
puЬlic
void ConfigureServices(IServiceCollection services) (
services.AddMvc();
461
Глава 15. Маршрутизация URL
void Configure(IApplicationBuil der арр, IHostingEnvironment env) {
app.UseDeveloperExceptio nPage();
app.UseStatusCodePages();
app.UsestaticFiles();
app.UseMvc(routes => {
routes.MapRoute(
name: "MyRou te",
template: "{controller=Home}/{actio n=Index}/{id:int?}");
puЫic
));
Ограничения отделяются от имени переменной сегмента двоеточием (символ
Ограничением в листинге является
int.
и оно применяется к сегменту
мер встраиваемого ограничения, которое определено как часть шаблона
: ).
Это при­
id.
URL
и при­
менено к одиночному сегменту:
template: "{controller)/{action)/{ id:int?)",
Ограничение
in t
разрешает сопоставление шаблона
значение которых может быть преобразовано в тип
так что маршрут будет сопоставляться с
URL без
сегмента
ет, то должен быть целочисленным значением (в табл.
Таблица
Пример
15. 7.
Сопоставление
URL
URL
только с сегментами,
Сегмент
int.
id,
15.7
необязателен,
id
но если он присутству­
приведены примеры).
с ограничением
На что отображается
URL
1
controller=Home,action=In dex, id=null
/Home/CustomVariaЬle/Hello
Соответствия нет
-
разован в значение
сегмент
id
не может быть преоб­
in t
/Home/CustomVariaЬle/l
controller=Home,action=CustomVariaЫe,
/Home/CustomVariaЬle/l/2
Соответствия нет
-
сегментов слишком много
Ограничения также могут быть указаны за пределами шаблона
зованием аргумента
constraints
метода
id=l
MapRoute ()
URL
с исполь­
при определении маршрута.
Такой прием удобен, если предпочтение отдается хранению шаблона
URL
отдельно
от ограничений или соблюдению стиля маршрутизации, принятого в более ранних
версиях MVC, где встраиваемые ограничения не поддерживались. В листинге 15.25
показано то же самое ограничение
in t
на переменной сегмента
id,
выраженное с
применением отдельного ограничения. При использовании такого формата стандар­
тные значения также выражаются внешне.
Листинг
15.25.
Выражение ограничения за пределами шаблона
в файле
Startup. cs
из папки
using System;
using System.Collections.Gener ic;
using System.Linq;
UrlsAndRoutes
URL
462
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Dependencylnjection;
using Microsoft.AspNetCore.Routing.Constraints;
namespace UrlsAndRoutes {
puЫic class Startup {
puЫic void ConfigureServices(IServiceCollection services) {
services.AddMvc();
void Configure(IApplicationBuilder арр, IHostingEnvironment env) {
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
routes.MapRoute(
name: "MyRoute",
template: "{controller}/{action}/{id?} 11 ,
defaul ts : new { con troller = "Home 11 , action = 11 Index 11 } ,
constraints: new { id = new IntRouteConstraint() }) ;
puЫic
}) ;
Аргумент
constraints
метода
MapRoute ()
определен с применением анонимного
типа, имена свойств которого соответствуют ограничиваемым переменным сегмен­
тов. Пространство имен
Microsoft .AspNetCore. Routing. Constraints
содержит
набор классов, которые можно использовать для определения индивидуальных огра­
ничений. В листинге
15.25 аргумент constraints настроен на применение объекта
IntRouteConstraint для сегмента id, что дает такой же результат, как встраивае­
мое ограничение из листинга 15.24.
В табл. 15.8 описан полный набор классов ограничений в пространстве имен
Microsoft. AspNetCore. Routing. Constraints вместе с их встраиваемыми экви­
валентами для ограничений, которые могут применяться к одиночным элементам в
шаблоне
Табпица
URL;
15.8.
Встраиваемое
ограничение
alpha
часть из них будет рассматриваться в последующих разделах.
Ограничения маршрутов на уровне сегментов
Описание
Конструктор кпасса
Соответствует алфавитным
AlphaRouteConstraint()
символам независимо от ре-
гистра
bool
(A-Z, a-z)
Соответствует значению,
BoolRouteConstraint ()
которое может быть преобразовано в тип
datetime
bool
Соответствует значению, ко-
торое может быть преобразовано в тип
DateTime
DateTimeRouteConstraint ()
Глава 15. Маршрутизация URL
Окончание табл.
Встраиваемое
ограничение
decimal
Описание
Конструктор класса
Соответствует значению,
DecimalRouteConstraint()
463
15.8
которое может быть преоб­
разовано в тип
douЫe
decimal
Соответствует значению,
DouЬleRouteConstraint()
которое может быть преоб­
разовано в тип douЫe
float
Соответствует значению,
FloatRouteConstraint()
которое может быть преоб­
разовано в тип
guid
float
Соответствует значению
GuidRouteConstraint()
глобально уникального
идентификатора (globally
unique identifier - GUID)
int
Соответствует значению,
IntRouteConstraint()
которое может быть преоб­
разовано в тип
length(len)
length(min,max)
int
Соответствует строковому
LengthRouteConstraint(len)
значению, которое содер­
жит указанное количество
LengthRouteConstraint(min, max)
СИМВОЛОВ ИЛИ ЧИСЛО СИМ­
ВОЛОВ между
min и max
(включительно)
long
Соответствует значению,
LongRouteConstraint()
которое может быть преоб­
разовано в тип
maxlength(len)
long
Соответствует строковому
MaxLengthRouteConstraint(len)
значению с количеством
символов не более
max (val)
Соответствует значению
int,
minlength(len)
len
если оно меньше
MaxRouteConstraint(val)
val
Соответствует строковому
MinLengthRouteConstraint(len)
значению, которое име-
ет, по крайней мере,
len
символов
min (val)
Соответствует значению
int,
range(min,max)
если оно больше
Соответствует значению
int,
MinRouteConstraint(val)
val
RangeRouteConstraint(min,max)
если оно нахо­
дится между
min и max
(включительно)
regex(expr)
Соответствует регулярному
выражению
RegexRouteConstraint(expr)
464
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Совет. Можно ограничить доступ к методам действий со стороны запросов, которые сдела­
ны с помощью специфических НТТР-команд, таких как
GET или POST, используя набор
17 описано применение этих
атрибутов для обработки форм в контроллерах, а в главе 20 приведен полный список до­
атрибутов
MVC
наподобие
HttpGet
и
HttpPost.
В главе
ступных атрибутов.
Ограничение маршрута с использованием регулярного выражения
Наибольшую гибкость предлагает ограничение
regex,
мент с применением регулярного выражения. В листинге
ограничивается диапазоном
Листинг
15.26.
URL,
сегмент
controller
с которыми он будет сопоставляться.
Использование регулярного выражения для ограничения
маршрута в файле
using
using
using
using
using
using
using
using
using
которое сопоставляет сег­
15.26
Startup. cs
из папки
UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
Microsoft.AspNetCore.Routing.Constraints;
namespace UrlsAndRoutes {
puЫic class Startup {
puЬlic
void ConfigureServices(IServiceCollection services) {
services.AddMvc();
void Configure(IApplicationBuilder арр, IHostingEnvironment env) {
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
routes.MapRoute(
name: "MyRoute",
teш.plate: "{controller:regex("H.*)=Home}/{action=Index}/{id ?}");
puЫic
)) ;
Примененное к маршруту ограничение обеспечивает его соответствие только та­
ким
URL.
в которых сегмент
controller
начинается с буквы Н.
На заметку! Стандартные значения используются перед проверкой ограничений. Таким об­
разом, например, при запросе
URL вида /для переменной сегмента controller при­
Horne. Затем проверяются ограничения, и поскольку зна­
controller начинается с Н, стандартный URL будет соответствовать маршруту.
меняется стандартное значение
чение
Глава 15. Маршрутизация URL
465
С помощью регулярных выражений можно ограничивать маршрут так, что к
совпадению будут приводить только специфические значения для какого-то сегмен­
та
URL.
Это делается с использованием символа
URL
(Шаблон
1.
как показано в листинге
15.27.
разбит на две части, чтобы уместиться на печатной странице, о чем
заботиться в реальном проекте нет нужды.)
Листинг
15.27.
Ограничение маршрута специфическим набором значений переменной
сегмента в файле
using
using
using
using
using
using
using
using
using
Startup. cs
из папки
UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
Microsoft.AspNetCore.Routing.Constraints;
namespace UrlsAndRoutes {
puЬlic class Startup {
void ConfigureServices(IServiceCollection services) {
services.AddMvc();
puЫic
void Configure(IApplicationBuilder арр, IHostingEnvironment env) {
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
routes.MapRoute(
name: "MyRoute",
template: "{controller:regex("B.*)=Bome)/"
+ "{action:regex("Index$1"AЬout$)=Index}/{id?)");
puЫic
}) ;
Такое ограничение разрешает маршруту соответствовать только
имеют сегмент
action
со значением
Index
совместно, так что ограничения, наложенные на значение переменной
бинируются с ограничениями, наложенными на значение переменной
Другими словами, маршрут в листинге
URL,
ем переменной
action
является
15.27
которые
action, ком­
controller.
будет соответствовать только таким
controller начинается
Index или AЬout.
в которых значение переменной
URL,
или AЬout. Ограничения применяются
с буквы Н, а значени­
Использование ограничений на основе типов и значений
Большинство ограничений применяются для ограничения маршрутов, чтобы они
соответствовали только
URL с
сегментами, значения которых могут быть преобразо­
ваны в указанный тип либо имеют специфический формат. Хорошим примером может
служить ограничение
int,
использованное в начале этого раздела: маршруты будут
466
Часть 11. Подробные сведения об инфраструктуре ASP. NET Core MVC 2
соответствовать, только когда значение ограниченного сегмента может быть преоб­
разовано в значение .NЕТ-типа
ограничения
range,
int.
В листинге
15.28
демонстрируется применение
которое ограничивает маршрут так, что он соответствует
только если значение сегмента может быть преобразовано в
in t
URL.
и находится между
указанными значениями.
Листинг
15.28.
Ограничение на основе типа и значения в файпе
из папки
using
using
using
using
using
using
using
using
using
Startup. cs
UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencylnjection;
Microsoft.AspNetCore.Routing.Constraints;
namespace UrlsAndRoutes {
puЬlic class Startup {
void ConfigureServices(IServiceCollection services) {
services.AddMvc();
puЫic
void Configure(IApplicationBuilder арр, IHostingEnvironment env) {
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
routes.MapRoute(
name: "MyRoute",
template: "{controller=Hoш.e}/{action=Index}/
{id:range(l0,20)?}");
puЫic
) ) ;
Ограничение в рассмотренном примере применялось к необязательному сегменту
id.
Ограничение будет проигнорировано, если
мере. три сегмента. Если сегмент
URL,
20
URL запроса
не содержит, по крайней
присутствует, то маршрут будет соответствовать
только когда значение сегмента может быть преобразовано в
диапазон между
и
id
10 и 20.
Ограничение
range является
in t
и попадает в
включающим, т.е. значения
10
считаются входящими в диапазон.
Объединение ограничений
Если необходимо применить к одиночному элементу сразу несколько ограничений,
тогда их можно соединить в цепочку. отделяя каждое ограничение от других двоето­
чием (листинг
15.29).
Глава 15. Маршрутизация URL
Листинг
467
15.29. Объединение встраиваемых ограничений в файле Startup. cs
из папки UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencylnjection;
Microsoft.AspNetCore.Routing.Constraints;
using
using
using
using
using
using
using
using
using
namespace UrlsAndRoutes {
puЫic class Startup {
void ConfigureServices(IServiceCollection services) {
services.AddMvc();
puЫic
void Configure(IApplicationBuilder арр, IHostingEnvironrnent env) {
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
routes.MapRoute(
name: "MyRoute",
template: "{controller=Hoшe}/{action=Index}"
+ "/{id:alpha:шinlength(б)?}");
puЫic
)) ;
В листинге
15.29 к сегменту id были
применены ограничения
alpha
и
minlength.
Знак вопроса, обозначающий необязательный сегмент, учитывается после всех ог­
раничений. В результате объединения ограничений маршрут будет соответствовать
URL,
только когда сегмент
id
опущен (поскольку он необязателен) или когда он при­
сутствует и содержит, по крайней мере, шесть алфавитных символов.
Если вы не используете встраиваемые ограничения, то должны применять класс
Microsoft. AspNetCore. Routing. Composi teRouteConstraint,
который позволя­
ет ассоциировать несколько ограничений с одиночным свойством в анонимно типи­
зированном объекте. В листинге
использовались в листинге
Листинг
15.30.
показана комбинация ограничений, которые
Объединение отдельных ограничений в файле
из папки
using
using
using
using
using
using
15.30
15.29.
UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Startup. cs
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
468
using
using
using
using
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencylnjection;
Microsoft.AspNetCore.Routing.Constraints;
Microsoft.AspNetCore.Routing;
namespace UrlsAndRoutes {
puЫic class Startup {
void ConfigureServices(IServiceCollection services) {
services.AddMvc();
puЫic
puЫic
void Configure(IApplicationBuilder арр, IHostingEnvironment env) {
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
routes.MapRoute(
name: "MyRoute",
template: "{controller}/{action}/{id?}",
defaults: new { controller = "Home", action = "Index" } ,
constraintз: new {
id = new CompoзiteRouteConзtraint(
new IRouteConзtrain t [] {
new AlphaRouteConзtraint(),
new МinLengthRouteConзtraint(б)
})
}) ;
}) ;
Конструктор класса
CompositeRouteConstraint принимает перечисление объ­
IRouteConstraint, определяющий ограни­
ектов, которые реализуют интерфейс
чения маршрутов. Система маршрутизации разрешит соответствие маршрута
URL.
только если все ограничения удовлетворены.
Определение специального ограничения
Если для обеспечения текущих потребностей стандартных ограничений не
хватает, тогда можно определить собственные ограничения,
фейс
реализовав интер­
IRouteConstraint, который находится в пространстве имен Microsoft.
AspNetCore.Routing. Чтобы проверить такую возможность, добавим в пример
проекта папку Infrastructure и поместим в нее новый файл класса по имени
WeekDayConstraint. cs с содержимым из листинга 15.31.
Листинг
15.31.
Содержимое файла
из папки
WeekDayConstraint. сз
Infrastructure
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using System.Linq;
namespace UrlsAndRoutes.Infrastructure
Глава 15. Маршрутизация URL
469
class WeekDayConstraint : IRouteConstraint {
private static string [] Days = new [] { "mon", "tue", "wed", "thu",
"fri", 11 sat", "sun" };
puЫic
bool Match(HttpContext httpContext, IRouter route,
string routeKey, RouteValueDictionary values,
RouteDirection routeDirection) {
return Days.Contains(values[routeKey]?.ToString() .ToLowerlnvariant());
puЫic
В интерфейсе
IRouteConstraint
определен метод
Match
(),который вызывает­
ся, чтобы позволить ограничению решить, должен ли запрос соответствовать марш­
руту. Параметры метода
Match ()
предоставляют доступ к запросу, поступившему от
клиента, маршруту, имени ограничиваемого сегмента, переменным сегментов, извле­
ченным из
URL,
и признаку того, для какого
или исходящего (исходящие
URL
URL
проверяется запрос
рассматриваются в главе
В этом примере с помощью параметра
routeKey
-
входящего
16).
из параметра
values
извлекает­
ся значение переменной сегмента, к которому было применено ограничение, преобра­
зуется в строку нижнего регистра и проверяется на предмет соответствия одному из
дней недели, определенных в статическом поле
Days.
В листинге
15.32
новое ограни­
чение применяется к маршруту с использованием синтаксиса отдельно определяемых
ограничений.
Листинг
15.32.
Применение специального ограничения в файле
из папки
using
using
using
using
using
using
using
using
using
using
using
Startup. cs
UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
Microsoft.AspNetCore.Routing.Constraints;
Microsoft.AspNetCore.Routing;
UrlsAndRoutes.Infrastructure;
namespace UrlsAndRoutes {
puЫic class Startup {
void ConfigureServices(IServiceCollection services) {
services.AddMvc();
puЫic
void Configure(IApplicationBuilder арр, IHostingEnvironment env) {
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
routes.MapRoute(
name: "MyRoute",
template: "{controllerl/{actionl/{id?I",
puЫic
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
470
defaul ts: new { controller = "Home", action = "Index" ) ,
constraints: new { id = new WeekDayConstraint() }) ;
) ) ;
В итоге маршрут будет соответствовать
(например,
/Custorner /List)
URL,
только если сегмент
id
отсутствует
или совпадает с одним из дней недели, которые опре­
делены в классе ограничения (скажем,
/Customer/List/Fri).
Определение встраиваемого специального ограничения
Настройка специального ограничения так.
чтобы оно могло использовать­
ся встраиваемым образом, требует дополнительного шага по конфигурированию
(листинг
15.33).
Листинг 15.ЗЗ. Применение специального ограничения встраиваемым образом
в файле
using
using
using
using
using
using
using
using
using
using
using
Startup.cs
из папки
UrlsAndRoutes
System;
System.Collections.Gener ic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Bui lder;
Microsoft.AspNetCore.Hos ting;
Microsoft.AspNetCore.Http ;
Microsoft.Extensions.Dep endencyinjection;
Microsoft.AspNetCore.Ro uting.Constraints;
Microsoft.AspNetCore.Rou ting;
UrlsAndRoutes.Infrastruc ture;
namespace UrlsAndRoutes {
puЫic class Startup {
puЬlic
void ConfigureServices(IServic eCollection services) {
services.Configure<RouteO ptions>(options =>
options. Constraintмap. Add ( "weekday" , typeof (WeekDayConstraint) ) ) ;
services.AddMvc();
puЫic
void Configure(IApplicationBuil der
app.UseDeveloperExceptio nPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
routes.MapRoute(
name: "MyRoute",
арр,
IHostingEnvironment env) {
template: "{controller=Home}/{actio n=Index}/{id:weekday?}");
) ) ;
Глава 15. Маршрутизация URL
В методе
ConfigureService ()
торый управляет рядом линий
ConstraintMap
конфигурируется объект
поведения
RouteOptions,
системы маршрутизации.
471
ко­
Свойство
возвращает словарь, используемый дЛЯ трансляции имен встраива­
емых ограничений в классы реализации
IRouteConstraint,
которые предоставляют
логику ограничений. В словарь было добавлено новое отображение, поэтому на класс
WeekDayConstraint
можно ссылаться встраиваемым образом как на
weekday:
template: "{controller=Home)/{action=Index)/{id:weekday?)",
Результат будет тем же, но настройка отображения позволяет применять специ­
альный класс ограничения встраиваемым образом.
Использование маршрутизации
с помощью атрибутов
Во всех примерах, приведенных до сих пор в главе, маршруты определялись пос­
редством приема, который называется маршрутизацией на основе соглашений.
Инфраструктура
MVC
также поддерживает прием. известный как маршрутизация
с помощью атрибутов, когда маршруты определяются через атрибуты С#. которые
применяются напрямую к классам контроллеров. В последующих разделах будет по­
казано, как создавать и конфигурировать маршруты с использованием атрибутов,
которые могут свободно смешиваться с маршрутами на основе соглашений, проде­
монстрированными в предшествующих примерах.
Подготовка для маршрутизации с помощью атрибутов
Маршрутизация с помощью атрибутов включается при вызове метода
файле
Startup. cs.
Инфраструктура
MVC
UseMvc ()
в
исследует классы контроллеров в приложе­
нии. находит любые из них, имеющие атрибуты маршрутизации, и создает нужные
маршруты.
Для целей текущего раздела мы вернем стандартную конфигурацию маршрутиза­
ции в примере приложения, которая была описана во врезке "Стандартная конфигу­
рация маршрутизации" ранее в главе (листинг
Листинг
15.34.
Применение стандартной конфигурации в файле
из папки
using
using
using
using
using
using
using
using
using
using
using
15.34).
UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencylnjection;
Microsoft.AspNetCore.Routing.Constraints;
Microsoft.AspNetCore.Routing;
UrlsAndRoutes.Infrastructure;
namespace UrlsAndRoutes
puЫic class Startup {
Startup. cs
472
Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC 2
puЫic
void ConfigureServices(IServiceCollection services)
services.Configure<RouteOptions>(options =>
options.ConstraintMap.Add("weekday", typeof(WeekDayConstraint)));
services.AddMvc();
puЫic
void Configure(IApplicationBuilder
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
арр,
IHostingEnvironment env) (
app.UseМvcWithDefaultRoute();
Стандартный маршрут будет сопоставляться с
URL.
используя следvющий шаблон:
{controllerf/{action)/{id?)
Применение маршрутизации с помощью атрибутов
Атрибут
Route
используется при указании маршрутов для индивидуальных
контроллеров и действий. В листинге
15.35
атрибут
Route
применяется к классу
CustornerController.
Листинг
15.35.
Применение атрибута Route в фай11е CustomerController.cs
из папки
Controllers
using Microsoft.AspNetCore.Mvc;
using UrlsAndRoutes.Models;
namespace UrlsAndRoutes.Controllers
puЫic class CustomerController : Controller
[Route ("myroute")]
puЫic ViewResult Index() => View("Result",
new Result {
Controller = nameof(CustomerController),
Action = nameof ( Index)
) ) ;
puЫic
ViewResult List(string id) {
Result r = new Result {
Controller = nameof(HomeController),
Action = nameof (List),
) ;
r.Data["Id"] = id ?? "<no value>";
r.Data["catchall"] = RouteData.Values["catchall"];
return View("Result", r);
Атрибут
Rou te
определяет маршрут к методу действия или контроллеру, к которому
он применен. В листинге данный атрибут применен к методу действия
занием
rnyroute
Index ()
сука­
в качестве маршрута, который должен использоваться. Результатом
будет изменение набора маршрутов, которые применяются для достижения методов
действий, определенных контроллером
Custorner
(табл.
15.9).
Глава 15. Маршрутизация URL
Таблица
15.9.
Маршруть1 для контроллера
Марwрут
Описание
/Customer/List
Этот
customer
URL нацелен
Этот
URL нацелен
List (),полагаясь
Startup. cs
на метод действия
стандартный маршрут в файле
/myrout.e
473
на метод действия
на
Index ()
Следует отметить два важных момента. Во-первых. когда используется атри­
бут
Route.
предоставленное для его конфигурирования значение применяется при
myroute становится завершенным URL
Index (). Во-вторых, присутствие атрибута Route
определении полного маршрута, так что
для достижения метода действия
предотвращает использование стандартной конфигурации маршрутизации, поэто­
му метод действия
Index ()
больше не будет достижимым с применением
URL
вида
/Customer / Index.
Изменение имени метода действия
Определение уникального маршрута для одиночного метода действия в большинс­
тве приложений не особенно полезно, но атрибут
гибким образом. В листинге
кер
[controller]
15.36 внутри
Rou te
можно использовать более
маршрута применяется специальный мар­
для ссылки на контроллер и настройки базовой части маршрута.
Совет. Изменить имя действия можно также с помощью атрибута
рассматривается в главе
Листинг
15.36.
Переименование действия в файле
из папки
ActionNarne,
CustomerController. cs
Controllers
using Microsoft.AspNetCore.Mvc;
using UrlsAndRoutes.Models;
namespace UrlsAndRoutes.Controllers
puЫic
class CustomerController : Controller
[Route("[controller]/МyAction")]
ViewResult Index() => View("Result",
new Result {
Controller = nameof{CustomerController),
Action = nameof(Index)
puЫic
1);
ViewResult List(string id) {
Resul t r = new Resul t {
Controller = nameof(HomeController),
Action = nameof(List),
1;
r.Data["Id") = id ?? "<no value>";
r.Data["catchall"J = RouteData.Values["catchall"J;
return View("Result", r);
puЫic
который
31.
474
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Применение маркера
[controller] в аргументе для атрибута Route довольно
nameof и позволяет указывать маршрут к кон­
троллеру без жесткого кодирования имени класса. В табл. 15.10 описан эффект от
наличия атрибута в листинге 15.36.
похоже на использование операции
Таблица
15.10.
Маршруты для контроллера Customer
Маршрут
Описание
/Customer/List
Этот
URL
нацелен на метод действия
Li st ()
/Customer/MyAction
Этот
URL
нацелен на метод действия
Index ()
Создание более сложного маршрута
Атрибут
Route
можно также применять к классу контроллера, позволяя опреде­
лять структуру маршрута, как показано в листинге
Листинг
15.37.
15.37.
Применение атрибута Rou te к контроллеру в файле
CustomerController. cs из папки Controllers
using Microsoft.AspNetCore.Mvc;
using UrlsAndRoutes.Models;
namespace UrlsAndRoutes.Controllers
[Route("app/[controller]/actions/[action]/{id?}")]
puЫic
class CustomerController : Controller {
ViewResult Index() => View("Result",
new Resul t {
Controller = nameof(CustomerController),
Action = nameof(Index)
puЫic
) ) ;
ViewResult List (string id) {
Result r = new Result {
Controller = nameof(HomeController),
Action = nameof(List),
puЬlic
};
r.Data["Id"J = id ?? "<no value>";
r.Data["catchall"] = RouteData.Values["catchall"J;
return View{"Result", r);
В маршруте определена смесь статических и переменных сегментов, а также
используются маркеры
[controller]
и
контроллера и методов действий. В табл.
маршрута.
[action] для ссылки на имена
15. 11 описан эффект от создания
класса
такого
Глава 15. Маршрутизация URL
Таблица
475
15.11. Маршруты для контроллера Customer
Маршрут
Описание
app/custorner/actions/index
Этот
URL нацелен
на метод действия
Index ()
app/custorner/actions/index/rnyid
Этот
URL нацелен
на метод действия
Index ()
с необязательным сегментом
ным в
id,
установлен­
rnyid
app/custorner/actions/list
Этот
URL нацелен
на метод действия
List ()
app/custorner/actions/list/rnyid
Этот
URL нацелен
на метод действия
List ()
с необязательным сегментом
ным в
id,
установлен­
rnyid
Применение ограничений к маршрутам
Маршруты, определенные посредством атрибутов, могут ограничиваться подобно
маршрутам, которые определены в файле
Startup. cs,
с помощью того же самого
встроенного подхода, используемого для маршрутов на основе соглашений. В листин­
ге
созданное ранее в главе специальное ограничение применяется к необяза­
15.38
тельному сегменту
Листинг
15.38.
id,
определенному в атрибуте
Ограничение маршрута в файле
из папки
Route.
CustomerController. cs
Controllers
using Microsoft.AspNetCore.Mvc;
using UrlsAndRoutes.Models;
namespace UrlsAndRoutes.Controllers
[Route("app/[controller]/actions/[action]/{id:week day?}")]
class CustomerController : Controller {
puЬlic ViewResult Index() => View("Result",
new Result {
Controller = nameof(CustomerController),
Action = nameof(Index)
puЫic
}) ;
ViewResult List(string id) {
Result r = new Result {
Controller = nameof(HomeController),
Action = nameof(List),
puЫic
};
r.Data["Id"] = id ?? "<no value>";
r.Data["catchall"] = RouteData.Values["catchall"];
return View("Result", r);
Можно использовать все ограничения, описанные в табл.
монстрировано в листинге
15.38,
15.8,
или, как проде­
применять специальные ограничения, которые
были зарегистрированы с помощью службы
RouteOptions.
Чтобы применить сразу
несколько ограничений. их понадобится соединить в цепочку. отделяя друг от друга
двоеточиями.
476
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Резюме
В главе подробно обсуждалась система маршрутизации. Вы увидели, как опреде­
ляются маршруты по соглашениям и с помощью атрибутов. каким образом сопостав­
ляются и обрабатываются входящие
URL.
и как настраиваются маршруты за счет
изменения способа. которым они сопоставляются с сегментами
URL.
а также за счет
использования стандартных значений и необязательных сегментов. Кроме того, было
показано. каким образом ограничивать маршруты, чтобы сузить диапазон запросов.
для которых они будут давать совпадение, с применением встроенных ограничений и
специальных классов ограничений.
В следующей главе мы рассмотрим генерацию исходящих
URL
на основе маршру­
тов внутри представлений и выясним, как использовать средство областей. которое
полагается на систему маршрутизации и может применяться для управления круп­
ными и сложными приложениями
MVC.
ГЛАВА
16
Дополнительные
возможности
маршрутизации
как использовать систему маршрутизации
в предыдущей главе было показано,
URL,
но это только часть общей картины. Мы также
для обработки входящих
должны быть в состоянии применять схему
URL
для генерации исходящих
URL,
ко­
торые могут встраиваться в представления. чтобы пользователи имели возможность
щелкать на ссылках и отправлять формы обратно приложению, нацеливаясь на кор­
ректный контроллер и действие.
В настоящей главе будут рассматриваться разнообразные приемы для генерации
исходящих
URL.
демонстрироваться способы настройки системы маршрутизации за
счет замены стандартных классов, реализующих маршрутизацию
ся, как использовать средство областей
сложное приложение
MVC
ветов по применению схем
MVC,
MVC,
и объяснять­
которое позволяет разбивать крупное и
на управляемые фрагменты. Dlaвa завершится рядом со­
URL
в приложениях
MVC.
В табл.
16.1
приведена сводка.
позволяющая поместить дополнительные возможности маршрутизации в контекст.
Таблица
16. 1. Помещение дополнительных возможностей маршрутизации в контекст
Вопрос
Ответ
Что это такое?
Система маршрутизации предоставляет возможности,
которые выходят за рамки сопоставления с
НПР­
URL для
запросов. Имеется также поддержка для генерации
URL в
представлениях, замещения встроенной функциональности
маршрутизации специальными классами и структуризации
приложения в виде изолированных разделов
Чем они полезны?
Каждое средство полезно по своим соображениям.
Наличие возможности генерирования
URL облегчает
из­
необходимости в обновлении всех
представлений. Существование возможности использова­
менение схемы
URL без
ния специальных классов позволяет подстраивать систему
маршрутизации под собственные требования. Наличие
возможности структуризации приложения упрощает пост­
роение сложных проектов
Как они используются?
Ищите детальные сведения в разделах главы
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
478
Окончание табл.
Вопрос
16. 1
Ответ
Существуют ли какие-то скры­
Конфигурация маршрутизации для сложного приложения
тые ловушки или ограничения?
может стать трудной в управлении
Существуют ли альтернативы?
Нет. Система маршрутизации
ASP.NEТ
В табл.
Таблица
16.2 приведена сводка
16.2. Сводка по главе
Задача
-
неотъемлемая часть
Core
по главе.
Решение
Листинг
Генерирование якорного
Используйте атрибуты
элемента с
и
URL
asp-action
asp-controller
Предоставление значений для
Применяйте атрибуты с префиксом
сегментов маршрутизации
аsр-rоutе-
Генерирование полностью
Используйте атрибуты
заданных
asp-host
URL
Выбор маршрута для генера­
ции
и
asp-protocol,
asp-fragment
Применяйте атрибут
asp-route
в представлении или в методе действия
16.9,
16.10
16.11,
16.12
16.13
URL
Генерирование
URL без
Используйте вспомогательный метод
НТМL-элемента
Настройка системы
Применяйте метод
маршрутизации
в классе
Создание специального
Реализуйте интерфейс
Url .Action ()
Configure ()
Startup
IRouter
16.1416.21
Создайте области и используйте
атрибут Area
16.2216.28
класса маршрутизации
Разбиение приложения на
функциональные разделы
16.216.5
16.6,
16.7
16.8
Подготовка проекта для примера
Мы продолжим работать с проектом
UrlsAndRoutes из предыдущей
Startup, rде вызов
Единственное изменение потребуется внести в класс
UseMvcWithDefaultRoute ()
заменяется явным маршрутом с тем же самым резуль­
татом, как показано в листинге
16. l.
Листинг 1б.1. Изменение конфигурации маршрутизации в файле
из папки
using
using
using
using
using
using
using
using
using
using
using
главы.
метода
UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencylnjection;
Microsoft.AspNetCore.Routing.Constraints;
Microsoft.AspNetCore.Routing;
UrlsAndRoutes.Infrastructure;
s tartup. cs
Глава 16. Дополнительные возможности маршрутизации
479
n ame spa ce UrlsAndRoutes {
p uЫ i c c la ss Startup {
vo id Co nfigureSer v i ces(ISe rviceColl ection services ) {
services.Configure<RouteOp tio ns>(options =>
o p tions.Const ra i ntMap.Add ( "weekday", type o f(WeekDayConstraint) ) ) ;
services.AddMvc();
puЫic
void Configure(IApplicat ionBui lder
app.U seDevel o perEx ceptionPage() ;
app.UseStatusCodePages();
app.Us eSta ti c file s() ;
арр. Useмvc (routes => {
puЫic
арр,
IHost ing Env i ronment env) {
routes.мapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
}) ;
После запуска приложения браузер запросит стандартный
правлен на действие
Index
контроллера
D Routing
Home
(рис .
URL, который
будет на­
16. \ ).
х
ф localhost ~41\3<>
С
Controller:
HomeController
Action:
lndex
Рис.
16.1.
Выполнение примера приложения
Генерирование исходящих
Почти в каждом приложении
MVC
URL
в представлениях
пользователи должны располагать возможнос ­
тью перехода от одного представления к друтому, что обычно предполагает включение
в первое представление ссылки. нацеленной на метод действия. который генерирует
второе представление. Было бы заманчиво просто добавить статический элемент а
(известный как якорный элемент), указав в его атрибуте
href
нужный метод дейс ­
твия. например:
<а h ref= "/Home/CustomVariaЬle" >T his
i s an outgo in g URL< / a>
Предпол а га я . что в приложении применяется станда ртная конфигурация марш ­
рутизации, такой НТМL-элемент создает ссылку, которая будет нацелена на метод
действия CustomVariaЫe
()
в контроллере
Home.
Вручную определяемые
URL подоб­
ного рода создавать быстро и просто. Но вместе с тем они чрезвычайно опасны: из­
менив схему
URL.
URL для
приложения. вы нарушите работу всех жестко закодированных
Затем придется пройти с ь по все м представлениям в приложении и обновить все
сс ылки на контроллеры и методы действий
-
утомительный , чреватый ошибками и
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
480
сложный в тестировании процесс. Более удачная альтернатива предусматривает ис­
пользование для генерации исходящих
вает применение схемы
URL
рый гарантирует учет схемы
URL
системы маршрутизации. что обеспечи­
для динамического формирования
URL
URL
способом. кото­
приложения.
Генерирование исходящих ссылок
Сгенерировать исходящий
URL
в представлении проще всего с использованием
вспомогательной функции дескриптора якоря, которая создаст атрибут
элементе а, как иллюстрируется в листинге
представлению
16.2,
href в НТМL­
в котором приведено добавление к
/Views/Shared/Resul t. csh tml.
Совет. Работа вспомогательных функций дескрипторов объясняется в главе
23.
Листинг 1б.2. Использование вспомогательной функции дескриптора якоря
в файле
Resul t. cshtml
из папки
Views/Shared
@model Result
@{ Layout = null;
< 1 DOCTYPE html>
<l1tml>
<head>
<meta name="viewport" content="width=device-width" />
<title>Routing</title>
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.min.css" />
</head>
<body class="m-1 р-1">
<tаЫе class="taЫe taЫe-bordered taЬle-striped taЬle-sm" >
<tr><th>Controller:</th><td>@Model.Controller</td ></tr>
<tr><th>Action:</th><td>@Model.Action</td></tr>
@foreach (string key in Model.Data.Keys) {
<tr><th >@ key :</th><td>@Model.Data[key)</td></tr>
</tаЫе>
<а asp-action="CustomVariaЬle">This
is an outgoing URL</a>
</body>
</html>
Атрибут
asp-action
применяется для указания имени метода действия. на кото­
рый должен быть нацелен
но увидеть на рис.
URL
в атрибуте
href.
Результат запуска приложения мож ­
16.2.
х
с
Controller:
Action:
This is an ootgoing URL
Рис.
16.2.
Использование вспомогательной функции дескриптора для генерации ссылки
Глава 16. Дополнительные возможности маршрутизации
Вспомогательная функция дескриптора устанавливает атрибут
hre f
481
элемента а с
применением текущей конфигурации маршрутизации. Просмотрев НТМL-разметку.
отправленную браузеру. легко заметить, что она содержит следующий элемент:
<а href="/Home/CustomVariaЬle">This
is an outgoing URL</a>
Может показаться, что для воссоздания вручную определенного
URL,
который был
приведен ранее, пришлось затратить слишком много дополнительных усилий, но пре­
имущество данного подхода в том, что он автоматически реагирует на изменения в
конфигурации маршрутизации. Чтобы продемонстрировать сказанное, добавим в
файл
Startup. cs
Листинг
16.3.
новый маршрут (листинг
16.3).
Добавление нового маршрута в файле
из папки
s tartup. cs
UrlsAndRoutes
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Dependencyinjection;
using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.AspNetCore.Routing;
using UrlsAndRoutes.Infrastructure;
namespace UrlsAndRoutes {
puЫic class Startup {
puЫic void ConfigureServices(IServiceCollection services) {
services.Configure<RouteOptions>(options =>
options.ConstraintMap.Add("weekday", typeof(WeekDayConstraint)));
services.AddMvc();
void Configure(IApplicationBuilder
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
puЬlic
арр,
IHostingEnvironment env) {
routes.МapRoute(
name: "NewRoute",
template: "App/Do{action}",
def aul ts: new { controller = "Home" } ) ;
routes.MapRoute(
name: "default",
template: "(controller=Home)/{action=Index)/{id?)");
)) ;
Новый маршрут изменяет схему
лер
Home.
URL для
запросов, которые нацелены на контрол­
Запустив приложение. можно заметить, что это изменение отразилось в
НТМL-разметке, сгенерированной вспомогательной функцией дескриптора:
<а href="/App/DoCustomVariaЫe">This
is an outgoing URL</a>
482
Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC 2
Генерирование ссылок с использованием вспомогательной функции дескриптора
решает важную проблему сопровождения. Мы имеем возможность изменять схему
маршрутизации.
а исходящие ссылки в представлениях автоматически отразят из­
менения без необходимости вручную редактировать представления в приложении.
Когда производится щелчок на ссылке, исходящий
URL применяется
для создания
входящего НТТР-запроса, и тот же самый маршрут затем используется для нацелива­
ния на метод действия и контроллер , которые будут обрабатывать запрос (рис.
D Routing
16.3).
х
Controller:
HomeController
Actlon:
CustomVariaЬle
d:
Рис.
1б.З.
Результатом щелчка на ссылке является применение исходящего
URL
для создания входящего запроса
Сопоставление исходящих
URL
с маршрутами
Вы уже видели, что изменение маршрутов, определяющих схему
нерации исходящих
URL.
URL,
изменяет способ ге­
В приложениях обычно определено множество маршрутов, поэто­
му важно понимать, как они выбираются для генерации
URL.
Система маршрутизации обра­
батывает маршруты в порядке их определения, и каждый маршрут проверяется по очереди
на предмет соответствия, что требует удовлетворения следующих трех условий.
•
Для любой переменной сегмента, определенной в шаблоне
URL,
должно быть доступно
значение. Чтобы найти значения для каждой переменной сегмента, система маршрути­
зации просматривает сначала предоставленные (посредством свойств анонимного типа)
значения, затем значения переменных для текущего запроса и, наконец, стандартные
значения, определенные в маршруте . (Позже в главе мы еще возвратимся ко второму ис­
точнику значений из числа упомянутых . )
•
Ни одно из значений, предоставленных для переменных сегментов, не должно конфлик­
товать с определенными в маршруте переменными, которые имеют только стандартные
значения. Это переменные, для которых были предоставлены стандартные значения, но
которые не встречаются в шаблоне
шрута
myVar
URL. Например, в показанном ниже определении мар­
является переменной, имеющей только стандартное значение :
routes. MapRo ute ( "MyRou te", " ( c o ntroll e r} / { a c t i o n) ",
n e w ( myV a.r = "true " ) ) ;
Чтобы соответствовать такому маршруту, значение для переменной
myVar
либо не долж­
но предоставляться, либо должно совпадать со стандартным значением .
•
Значения для всех переменных сегментов должны удовлетворять ограничениям маршру­
та. Примеры разных видов ограничений приводились в разделе "Ограничение маршрутов "
предыдущей главы.
Следует уяснить, что система маршрутизации не пытается найти маршрут, который дает
наилучшее совпадение. Она находит только первое совпадение и использует данный мар­
шрут для генерирования
URL; любые дальнейшие маршруты игнорируются . По указанной
Глава 16. Доnолнительные возможности маршрутизации
483
причине наиболее специфичные маршруты должны определяться первыми. Важно прове­
рить генерацию исходящих
URL.
Попытка сгенерировать
URL, для
которого не удается найти
соответствующий маршрут, приведет к созданию ссылки с пустым атрибутом
href:
href="">This is an outgoing URL</a>
<а
Такая ссылка корректно визуализируется в представлении, но не будет функционировать
ожидаемым образом, когда пользователь выполнит на ней щелчок. Если вы генерируете
только
null,
URL
(как будет показано позже в главе), тогда результирующим значением будет
которое визуализируется в виде пустой строки в представлениях. С помощью име­
нованных маршрутов можно получить определенный контроль над сопоставлением маршру­
тов. За более подробными сведениями обратитесь в раздел "Генерирование
URL
из специ­
фического маршрута" далее в главе.
Направление на другие контроллеры
В случае указания атрибута
asp-action
внутри элемента а вспомогательная фун­
кция дескриптора предполагает, что нужно установить в качестве цели действие в
том же контроллере, который вызвал визуализацию представления. Чтобы создать
исходящий
бут
URL,
который направляет на другой контроллер, можно применить атри­
asp-controller
Листинг
16.4.
(листинг
16.4).
Направление на другой контроллер в файле
из папки
Result.cshtml
Views/Shared
@model Result
@{ Layout = null;
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Routing</title>
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.min.css" />
</head>
<body class="m-1 р-1">
<tаЫе class="taЫe taЫe-bordered taЫe-striped taЫe-sm">
<tr><th>Controller:</th><td>@Model.Controller</td ></tr>
<tr><th>Action:</th><td>@Model.Action</td></tr>
@foreach (string key in Model.Data.Keys) {
<tr><th>@key :</th><td>@Model.Data[key)</td></tr>
</tаЫе>
asp-controller="Admin" asp-action="Index">
This targets another controller
<а
</а>
</body>
</html>
В результате визуализации представления сгенерируется следующая НТМL­
разметка:
<а
href="/Admin">This targets another controller</a>
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
484
Запрос к
URL,
который нацелен на метод действия
Index () контроллера Admin.
/ Admin. Системе маршру­
был выражен вспомогательной функцией дескриптора как
тизации известно, что маршрут, определенный в приложении. по умолчанию будет
использовать метод действия
Index
().позволяя опускать ненужные сегменты.
При выяснении, каким образом нацеливаться на заданный метод действия, сис­
тема маршрутизации включает маршруты, которые были определены с применени­
ем атрибута
Index
Route. В листинге 16.5 атрибут asp-controller нацелен на действие
Customer. к которому был применен атрибут Route в главе 15.
в контроллере
Листинг
16.5.
Направление на действие, декорированное атрибутом
в файле
из папки
Resul t. cshtml
Route,
Views/Shared
@model Result
@{ Layout = null;
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Routing</title>
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.min.css" />
</head>
<body class="panel-body">
<tаЫе class="taЫe
taЫe-bordered taЬle-striped
taЬle-sm">
<tr><th>Controller:</th><td>@Model.Controller</td></tr>
<tr><th>Action:</th><td>@Model.Action</td></tr>
@foreach (string key in Model.Data.Keys) {
<tr><th>@key :</th><td>@Model.Data[key]</td></tr>
</tаЫе>
<а
asp-controller="Customer" asp-action="Index">This is an outgoing URL</a>
</body>
</html>
Вот какая ссылка сгенерируется:
<а
href="/app/Customer/actions/Index">This is an outgoing lJRL</a>
Результат соответствует атрибуту
Customer
в главе
Rou te.
который применялся к контроллеру
15:
[Route("app/[controller]/actions/[action]/{id:weekday?}")]
class CustomerController : Controller {
puЫic
Передача дополнительных значений
Системе маршрутизации можно передавать значения для переменных сегментов
за счет определения атрибутов с именами, начинающимися со строки
за которой следует имя сегмента. Таким образом, атрибут
ся для установки значения сегмента
id
(листинг
16.6).
asp-route-.
asp-1·oute-id исполr,зует­
Глава 16. Доnолнительные возможности маршрутизации
Листинг
16.6.
485
Предоставление значений для переменных сегментов
в файле
Resul t. cshtml
из папки
Views/Shared
@model Result
@{ Layout = null;
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Routing</title>
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.min.css" />
</head>
<body class="m-1 р-1">
<tаЫе class="taЫe taЬle-bordered taЬle-striped taЫe-sm">
<tr><th>Controller:</th><td>@Model.Controller</td ></tr>
<tr><th>Action:</th><td>@Model.Action</td></tr>
@foreach (string key in Model.Data.Keys) {
<tr><th>@key :</th><td>@Model.Data[key]</td></tr>
</tаЫе>
asp-controller="Home" asp-action="Index" asp-route-id="Hello">
This is an outqoinq URL
<а
</а>
</body>
</html>
Здесь предоставляется значение для переменной сегмента по имени
ложение применяет маршрут, показанный в листинге
16.3,
id.
Если при­
тогда при визуализации
представления будет получена следующая НТМL-разметка:
<а
href="/App/Doindex?id=Hello">This is an outgoing URL</a>
Обратите внимание, что значение сегмента было добавлено как часть строки за­
проса, чтобы вписываться в шаблон
URL,
определенный маршрутом. Причина свя­
зана с отсутствием переменной сегмента, которая бы соответствовала
маршруте. Чтобы решить проблему, необходимо отредактировать файл
оставив только маршрут. который имеет сегмент
Листинг
16. 7.
Редактирование маршрутов в файле
из папки
using
using
using
using
using
using
using
using
using
using
using
id
(листинг
16. 7).
s tartup. cs
UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
Microsoft.AspNetCore.Routing.Constraints;
Microsoft.AspNetCore.Routing;
UrlsAndRoutes.Infrastructure;
i d
в этом
Startup. cs,
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
486
namespace UrlsAndRoutes (
puЫic class Startup {
puЫic void ConfigureServices(IServiceCollection services) {
services.Configure<RouteOptions>(options =>
options.ConstraintMap.Add("weekday", typeof(WeekDayConstraint)));
services.AddMvc();
void Configure(IApplicationBuilder арр, IHostingEnvironment env) (
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
/ / rou tes . МapRou te (
11 name: "NewRoute",
11 template: "App/Do{action}",
11 defaul ts: new { controller = "Home" } ) ;
routes.MapRoute(
name: "default",
template: "(controller=Home}/(action=Index}/(id?}");
puЬlic
}) ;
Запустив приложение еще раз, можно заметить, что вспомогательная функция де­
скриптора выдала следующий НТМL-элемент. в котором значение свойства
чено как сегмент
<а
id
вклю­
URL:
href="/Home/Index/Hello">This is an outgoing URL</a>
Повторное использование переменных сегментов
При описании способа сопоставления маршрутов для исходящих
URL было указано, что
когда система маршрутизации пытается найти значения для каждой переменной сегмента
в шаблоне
URL маршрута, она просматривает текущий запрос. Такое поведение сбивает с
толку многих программистов и может вылиться в длительный сеанс отладки.
Предположим, что приложение имеет единственный маршрут:
app.UseMvc(routes => (
routes.MapRoute(name: "MyRoute",
template: "(controller}/(action}/(color}/(page}");
}) ;
Пусть в текущий момент пользователь запросил
URL
вида
/Home/Index/Red/100
и ви­
зуализируется показанная ниже ссылка:
asp-controller="Home" asp-action="Index" asp-route-page="789">
This is an outgoing URL
<а
</а>
Можно было бы ожидать, что система маршрутизации не сумеет выполнить сопоставление с
маршрутом, т.к. не было предоставлено значение для переменной сегмента
color,
к тому
же для нее не определено стандартное значение. Однако дело обстоит не так. Система мар-
Глава 16. Дополнительные возможности маршрутизации
487
шрутизации будет сопоставлять с определенным ранее маршрутом. В результате сгенери­
руется следующая НТМL-разметка:
<а
href="/Home/Index/Red/789">This is an outgoing URL</a>
Система маршрутизации достаточно интеллектуальна, чтобы провести сопоставление с мар­
шрутом, поскольку при генерировании исходящего
URL она будет повторно использовать
URL. В рассматриваемом случае переменная
включал URL, с которого стартовал пользователь.
значения переменных сегментов из входящего
color
получит значение
Red,
т.к. его
Это не поведение, предназначенное для крайних случаев. Система маршрутизации будет
применять такой прием как часть обычной оценки маршрутов, даже если имеется последу­
ющий маршрут, который обеспечит совпадение, не требуя повторного использования зна­
чений из текущего запроса.
Я настоятельно рекомендую не полагаться на описанное поведение и предоставлять значе­
ния для всех переменных сегментов в шаблоне
URL. Если опираться на данное поведение,
то код станет гораздо труднее в восприятии, к тому же в коде будут делаться допущения
относительно порядка, в котором пользователи выдают запросы, что обернется проблема­
ми при сопровождении приложения.
Генерирование полностью заданных
URL
Все ссылки, которые генерировались до сих пор, содержали относительные
URL.
но вспомогательная функция дескриптора а способна также генерировать полностью
заданные
Листинг
URL,
16.8.
как показано в листинге
16.8.
Генерирование полностью заданного
из папки
URL
в файле
Resul t. cshtml
Views/Shared
@model Result
@{ Layout = null;
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Routing</title>
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.min.css" />
</head>
<body class="m-1 р-1">
<tаЫе
class="taЫe
taЫe-bordered taЫe-striped taЬle-sm">
<tr><th>Controller:</th><td>@Model.Controller</td></tr>
<tr><th>Action:</th><td>@Model.Action</td></tr>
@foreach (string key in Model.Data.Keys) {
<tr><th>@key :</th><td>@Model.Data[key]</td></tr>
</tаЫе>
asp-controller="Home" asp-action="Index" asp-route-id="Hello"
asp-protocol="https" asp-host="myserver.mydomain.com"
asp-fragment="myFragment">
This is an outgoing URL
<а
</а>
</body>
</html>
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
488
Атрибуты
asp-protocol, asp-host
и
asp-fragment
применяются для указания
протокола (https в листинге). имени сервера (myserver .mydomain. com) и фрагмен­
та
URL (myFragment).
Эти значения объединяются с выводом из системы маршру­
тизации для создания полностью заданного
URL.
который можно увидеть, запустив
приложение и просмотрев отправленную браузеру НТМL-разметку:
<а
href="https://myserver.mydomain.com/Home/Index/Hello#myFragment">
This is an outgoing URL
</а>
Будьте осторожны при использовании полностью заданных
URL,
поскольку они со­
здают зависимости от инфраструктуры приложения и, когда инфраструктура изменяет­
ся. вы должны не забыть внести соответствующие изменения в представления МVС.
Генерирование
URL
из специфического маршрута
В предшествующих примерах система маршрутизации выбирала маршрут для
применения при генерировании
URL.
Если важно генерировать
URL
в специфичес­
ком формате, тогда можно указать маршрут. подлежащий использованию для гене­
рации исходящего
Startup. cs
(листинг
Листинг
using
using
using
using
using
using
using
using
using
using
using
URL.
Чтобы продемонстрировать прием в работе. добавим в файл
новый маршрут. так что в примере приложения окажется два маршрута
16.9).
16.9. Добавление маршрута в файле Startup. cs из папки UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
Microsoft.AspNetCore.Routing.Constraints;
Microsoft.AspNetCore.Routing;
UrlsAndRoutes.Infrastructure;
namespace UrlsAndRoutes {
puЬlic class Startup {
puЫic
void ConfigureServices(IServiceCollection services) {
services.Configure<RouteOptions>(options =>
options.ConstraintMap.Add("weekday", typeof(WeekDayConstraint)));
services.AddMvc();
puЫic
void Configure(IApplicationBuilder
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
арр,
IHostingEnvironment env) {
11 routes.MapRoute(
name: "NewRoute",
11
template: "App/Do{action)",
11
11 defaults: new { controller = "Home" }) ;
Глава 16. Доnолнительные возможности маршрутизации
489
routes.MapRoute(
nаще: "default",
template: "{controller=Home}/{action=Index)/{id?}");
routes.мapRoute(
name: "out",
template: "outЬound/(controller=Home}/(action=Index}");
1);
Представление, приведенное в листинге
16.1 О,
содержит два якорных элемен­
та, каждый из которых указывает на те же самые контроллер и действие. Разница
в том, что второй элемент применяет атрибут
asp-route
вспомогательной функции
дескриптора для указания на необходимость использования маршрута
рации
URL.
Листинг
относящегося к атрибуту
16.10.
Генерирование
URL
ou t
при гене­
href.
в файле
Result.cshtml из папки Views/Shared
@model Result
@{ Layout = null;
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Routing</title>
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.
min.css" />
</head>
<body class="m-1 р-1">
<tаЫе class="taЫe taЫe-bordered taЫe-striped taЬle-sm">
<tr><th>Controller:</th><td>@Model.Controller</td ></tr>
<tr><th>Action:</th><td>@Model.Action</td></tr>
@foreach (string key in Model.Data.Keys) {
<tr><th>@key :</th><td>@Model.Data[key]</td></tr>
</tаЫе>
<а
asp-controller="Home"
asp-action="CustomVariaЬle">Тhis
is an outqoinq tJRL
</а>
<а asp-route="out">This is an outqoinq URL</a>
</body>
</html>
asp-route может применяться только в ситуации,
asp-controller и asp-action, т.е. выбирать можно
Атрибут
атрибуты
когда отсутствуют
лишь специфичес­
кий маршрут для контроллера и действия. которые привели к визуализации пред­
ставления. В результате запуска приложения и запрашивания
CustomVariaЫe получаются два разных
URL,
is an outgoing URL</a>
href="/outbound">This is an outgoing URL</a>
<а href="/Home/CustomVariaЫe">This
<а
URL
вида
/ Home /
сгенерированные маршрутами:
490
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Доводы против именованных маршрутов
Проблема с зависимостью от имен маршрутов при генерации исходящих
URL связана
с тем,
что это нарушает принцип разделения обязанностей, который занимает центральное место
в паттерне проектирования
MVC. При генерации ссылки либо URL в представлении или ме­
тоде действия необходимо сосредоточиться на действии и контроллере, куда пользователь
будет направлен, а не на формате
URL, который будет использоваться. Привнося знание о
различных маршрутах в представления или контроллеры, мы создаем зависимости, которых
можно было бы избежать. В своих проектах я стараюсь не именовать маршруты (указывая
null
в аргументе
name)
и предпочитаю применять комментарии в коде, чтобы не забыть о
том, для чего предназначен каждый маршрут.
Генерирование
URL
(без ссылок)
Ограничение вспомогательных функций дескрипторов заключается в том, что они
трансформируют НТМL-элементы и не могут без труда быть переориентированы в
случае, если необходимо генерировать
URL
для приложения без окружающей НТМL­
разметки.
MVC предлагает вспомогательный класс. который можно исполь­
URL напрямую. доступный через метод Url .Action (),как де­
листинге 16. l 1.
Инфраструктура
зовать для создания
монстрируется в
Листинг
16.11.
Генерирование
URL
в файле
Resul t. cshtml из папки Views/ Shared
@model Result
@( Layout = null;
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Routing</title>
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.min.css" />
</head>
<body class="m-1 р-1">
<tаЫе class="taЫe taЬle-bordered taЬle-striped taЬle-sm">
<tr><th>Controller:</th><td>@Model.Controller</td></tr>
<tr><th>Action:</th><td>@Model.Action</td></tr>
@foreach (string key in Model.Data.Keys) (
<tr><th>@key :</th><td>@Model.Data[key]</td></tr>
</tаЫе>
<p>URL:
</body>
</html>
@Url.Action("CustomVariaЬle",
В аргументах метода
Ur 1. Action ()
"Ноте",
new { id = 100
})</р>
указываются метод действия. контроллер и
значения для любых переменных сегментов. Результатом добавления разметки в лис­
тинге
16.11
<p>URL:
будет генерация следующего вывода:
/Home/CustomVariaЫe/100</p>
Глава 16. Дополнительные возможности маршрут изации
Генерирование
URL в
Ur l . Ac ti o n ()
URL внутри кода С#.
Метод
ния
контроллера
Листинг
Home .
491
методах действий
может применяться также и в методах дей ствий для созда­
В листинге
16. 12 модифицирован один из методов действий
URL с использованием Ur 1 . Act i o n ( ) .
чтобы генерировать
16.12. Генерирование URL внутри метода действия в файле
HomeController. cs из папки Controllers
u si ng Mi c r oso ft.AspNet Cor e.Mv c;
u si n g UrlsAndRo utes. Models ;
name spa ce Ur lsAn dRo u tes . Contr o l l e r s
pu Ыi c
clas s Home Co n tro ll e r : Co n t r o ller
ViewRe s u lt Ind e x( ) => Vi e w ( "Re su l t ",
Resul t . {
p u Ы ic
пеw
Co п tr o l l e r =
Ac t io n
=
пameof(Home C oп tro l ler ) ,
na meof(I nde x )
}) ;
Vi ewResul t C u sto m VariaЬle (str i ng id ) {
Resul t r = n e w Result {
Co ntr o l le r = п am e o f ( H o m eCoпt r o ller) ,
p uЬl ic
A c t io п
=
пame o f (C u s t omV a r i a Ьl e) ,
};
r.Data["Id"J = id ? '? " < п о v a l u e> ";
r.Data["Url"] = Url.Action("CustomVariaЬle", "Home", new { id = 100 }) ;
ret urn Vie w ( "Resu l t ", r ) ;
Запустив приложение и запросив
URL вида
/ H ome / Cu s t omVari aЫe . л егко заме ­
тить. что в таблице появилась строка. которая отображает данный
С
<D loca!host 60588/Horne/C~rstonNariaЫe
Coпtroller:
HomeController
Action:
CustomVariaЫe
id :
<novalue>
1/Home/CustomVariaЬle/100
url :
URL: /Нome/CustomVariaЬle/100
Рис.
16.4.
Генерирование
URL
в методе действия
URL
(ри с .
16.4).
492
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
Настройка системы маршрутизации
Вы уже видели, насколько гибкой и конфигурируемой является система маршру­
тизации, но если она не удовлетворяет существующим требованиям. тогда ее поведе­
ние можно настроить. В этом разделе будут показаны два способа такой настройки.
Изменение конфигурации системы маршрутизации
В главе 15выузнали, как конфигурировать объект
RouteOptions в файле Startup. cs
RouteOptions так­
для настройки специального ограничения маршрута. Объект
же применяется для конфигурирования ряда средств маршрутизации с помощью
свойств, описанных в табл.
Таблица
16.3.
16.3.
Конфигурационные свойства RouteOptions
Описание
Имя
AppendTrailingSlash
Когда это свойство типа
bool
системой маршрутизации
равно
true,
к генерируемым
URL добавляется завершающий
символ косой черты. Стандартным значением является
LowercaseUrls
Когда это свойство типа
bool
равно
true,
false
результирующие
URL преобразуются в нижний регистр, если контроллер, дейс­
твие или значения сегментов содержат символы нижнего ре­
гистра. Стандартным значением является
В листинге
16.13
приведено содержимое файла
false
Startup.cs
с добавленными
операторами для настройки обоих конфигурационных свойств из табл.
Листинг
16.13.
Конфигурирование системы маршрутизации в файле
из папки
using
using
using
using
using
using
using
using
using
using
using
16.3.
Startup. cs
UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
Microsoft.AspNetCore.Routing.Constraints;
Microsoft.AspNetCore.Routing;
UrlsAndRoutes.Infrastructure;
namespace UrlsAndRoutes {
puЫic class Startup {
puЫic
void ConfigureServices(IServiceCollection services) {
services.Configure<RouteOptions>(options => {
options.Constraintмap.Add("weekday", typeof(WeekDayConstraint));
options.LowercaseUrls = true;
options.AppendTrailingSlash = true;
}) ;
services.AddMvc();
Глава 16. Дополнительные возможности маршрутизации
493
void Configure (IApp1icationBuilder арр, IHostingEnvironment env) {
app.UseDeveloperExceptio nPage();
app.UseStatusCodePages( ) ;
app.UseStaticfiles();
app.UseMvc ( routes => {
routes.MapRoute(
name: "default",
template: "{ contro ller=Home}/{act i o n=Inde x )/(id?}" ) ;
routes.MapRoute(
puЫic
name: "out ",
template: "outbound / (cont roller =Home) /{ action=Index }" ) ;
}) ;
После запуска приложения и просмотра
URL.
которые сгенерировала система мар­
шрутизации, выяснится. что изменение конфигурационных свойств обеспечило пре­
образование
(рис.
URL
в нижний регистр и добавление к ним завершающей косой черты
16.5).
Controller:
HomeController
Aclion:
CustomVarlaЫe
id:
<ПО
url :
ihome/customvarlaЫe/100/
value>
URL · /home/customvariaЬle/1001
Рис.
16.5.
Конфигурирование системы маршрутизации
Соэдание специального класса маршрута
Если вам не нравится способ, которым система маршрутизации производит сопос­
тавление с
URL.
или для вашего приложения необходимо реализовать что -то специфи­
ческое, тогда можете создать собственные классы маршрутизации и использовать их
для обработки
URL.
Инфраструктура
AspNetCore. Rout i ng. IRo uter.
ASP.NET предоставляет
интерфейс
Microsof t.
который можно реализовать для создания специаль­
ного маршрута. Вот как выглядит определение интерфейса
IRout e r:
using Syste m.Threading.Tasks;
name spa ce Mi c roso f t.AspNetCo re . Routing
puЫic
inte rface IRouter (
Task Rou teAsy nc( Route Context con t ext ) ;
VirtualPathData GetVirtual Path(Virtua lPa thContext c ont ex t) ;
Часть 11. Подробные сведения об инфраструктуре ASP.NET Соге MVC 2
494
Чтобы
создать
RouteAsync ()
специальный
маршрут,
понадобится
для обработки входящих запросов и метод
нужно генерировать исходящие
реализовать
метод
GetVirtualPath ().
если
URL.
В целях демонстрации мы создадим специальный класс маршрутизации, который
будет обрабатывать запросы к унаследованным
какое-то существующее приложение в
старые
URL
MVC.
URL.
Предположим, что мы перенесли
но есть пользователи. которые поместили
в закладки или жестко закодировали их в сценариях. Мы хотим по-пре­
жнему поддерживать такие старые
URL.
Мы могли бы обработать это с применением
обычной системы маршрутизации, но решение данной задачи будет хорошим приме­
ром для целей настоящего раздела.
Маршрутизация входящих
URL
Чтобы понять, как работают специальные маршруты, мы начнем с создания одно­
го такого маршрута, который будет обрабатывать каждый аспект запроса самостоя­
тельно, не используя контроллер и представление. Создадим в папке
файл класса по имени
денной в листинге
Листинг
using
using
using
using
using
using
16.14.
LegacyRoute. cs
с реализацией интерфейса
Infrastructure
IRouter, приве­
16.14.
Содержимое файла
LegacyRou te. cs
из папки
Infras tructure
Microsoft.AspNetCore.Http;
Microsoft.AspNetCore.Routing;
System;
System.Linq;
System.Text;
System.Threading.Tasks;
namespace UrlsAndRoutes.Infrastructure
puЫic class LegacyRoute : IRouter {
private string(] urls;
puЬlic
LegacyRoute(params string(] targetUrls) {
this.urls = targetUrls;
puЫic
Task RouteAsync(RouteContext context) {
string requestedUrl = context.HttpContext.Request.Path
.Value.TrimEnd('/');
if (urls.Contains(requestedUrl, StringComparer.OrdinalignoreCase))
context.Handler = async ctx => {
HttpResponse response = ctx.Response;
byte[] bytes = Encoding.ASCII.GetBytes($"URL: {requestedUrll");
await response.Body.WriteAsync(bytes, О, bytes.Length);
};
return Task.CompletedTask;
puЬlic
VirtualPathData GetVirtualPath(VirtualPathContext context) {
return null;
495
Глава 16. Дополнительные возможности маршрутизации
LecagyRoute реализует
Rou teAs ync ( ) , который
Класс
метода
интерфейс
IRouter,
вскоре мы добавим поддержку и для исходящих
В методе
RouteAsync ()
но определяет код только для
применяется для обработки входящих запросов;
URL.
находится совсем немного операторов, но в своей работе
они полагаются на несколько важных типов ASP.NEТ. Лучше всего начать исследова­
ние с сигнатуры метода:
puЫic
Метод
async Task RouteAsync(RouteContext context)
RouteAsync ()
отвечает за оценку. может ли запрос быть обработан, и если
может, то за управление процессом, посредством которого генерируется ответ, отправ­
ляемый обратно клиенту. Такой процесс выполняется асинхронно, поэтому метод
RouteAsync () возвращает объект Task.
Метод RouteAsync () вызывается с аргументом RouteContext,
который предо­
ставляет доступ ко всему, что известно о запросе, и предлагает средства, требуемые
для отправки ответа клиенту. Класс
Microsoft. AspNetCore. Routing
Таблица
RouteContext
находится в пространстве имен
и определяет три свойства, описанные в табл.
16.4.
1б.4. Свойства, определенные в К11ассе RouteContext
Описание
Имя
RouteData
Это свойство возвращает объект
RouteData.
на средства
Microsoft. AspNetCore. Routing.
При написании специального маршрута, который полагается
MVC
(как объясняется в следующем разделе), данный объект
используется для определения контроллера, метода действия и аргументов,
применяемых для обработки запроса
HttpContext
Это свойство возвращает объект
Microsoft .AspNetCore. Http. HttpContext,
который предоставляет доступ к деталям НТТР-запроса и предназначен для
выпуска НТТР-ответа
Handler
Это свойство используется для предоставления системе маршрутизации
RequestDelegate, который будет обрабатывать запрос. Если ме­
RouteAsync не устанавливает данное свойство, тогда система маршру­
объекта
тод
тизации продолжит работу своим способом посредством набора маршрутов
в конфигурации приложения
Система маршрутизации вызывает метод
RouteAsync ()
каждого маршрута в при­
ложении и после каждого вызова проверяет значение свойства
Handler
было установлено в
RequestDelegate,
Handler.
Если свойство
тогда маршрут предоставляет систе­
ме маршрутизации делегат, который способен обработать запрос, и этот делегат вызы­
вается для генерации ответа. Ниже показана сигнатура делегата
определенного в пространстве имен
RequestDelegate,
Microsoft. AspNetCore. Http:
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Http {
puЫic delegate Task RequestDelegate(HttpContext context);
Делегат
Task,
RequestDelegate
принимает объект
HttpContext и возвращает объект
Handler не установлено ни в
который будет генерировать ответ. Если свойство
одном из маршрутов, то системе маршрутизации известно, что приложение не может
обработать запрос, и она сгенерирует ответ
404 - Not Found (404 -
не найдено).
496
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
С этой целью реализация метода
RouteAsync ()
должна выяснить, может ли она
обработать запрос, для чего обычно требуется объект
HttpContext. В рассматрива­
HttpContext. Request, которое возвращает
объект Microsoft.AspNetCore.Http.HttpRequest, описывающий запрос. Объект
HttpRequest предоставляет доступ ко всей информации о запросе, включая заголов­
емом примере применяется свойство
ки. тело и детали того, откуда запрос произошел. но мы заинтересованы в свойстве
Path.
поскольку оно дает подробные сведения относительно
ентом. Свойство
Path
URL. запрошенного кли­
PathString. предлагающий удобные мето­
URL. Мы используем свойство Value, т.к. оно
возвращает объект
ды для объединения и сравнения путей
дает раздел полного пути из
поддерживаемых
URL.
URL
в виде строки, которую можно сравнить с набором
полученным конструктором
LegacyRoute.
string requestedOrl = context.HttpContext.Request.Path.Value.TrimEnd('/');
if (urls.Contains(requestedUrl, StringComparer.OrdinalignoreCase)) {
Здесь с помощью метода
Tr imEnd ( )
из
URL удаляется
завершающая косая черта
(если она есть), которая могла быть добавлена либо пользователем, либо в результате
установки конфигурационного свойства
AppendTrailingSlash,
описанного в разде­
ле "'Изменение конфигурации системы маршрутизации" ранее в главе.
Если запрошенный путь входит в число сконфигурированных для поддержки клас­
сом
LegacyRoute,
тогда мы устанавливаем свойство
Handler
с применением лямбда­
функции, которая будет генерировать ответ:
context.Handler = async ctx => (
HttpResponse response = ctx.Response;
byte[J bytes = Encoding.ASCII.GetBytes($"URL: {requestedOrll");
await response.Body.WriteAsync(bytes, О, bytes.Length);
};
Свойство
HttpContext. Response
возвращает объект
Ht tpResponse,
который
может использоваться для создания ответа клиенту и предоставляет доступ к заголов­
кам и содержимому, подлежащему отправке клиенту. Метод HttpResponse. Body.
Wri teAsync () применяется для асинхронной записи в качестве ответа простой стро­
ки ASCII. Такой прием не будет предприниматься в реальном проекте, но он позволяет
выпустить ответ, не выбирая и не визуализируя представления (хотя в следующем
разделе будет показано, как это делать).
Когда свойство
Handler
установлено, системе маршрутизации известно, что ее
поиск маршрута закончен и можно вызывать делегат для генерации ответа клиенту.
Применение специального класса маршрута
Расширяющий метод
MapRoute ().
который до сих пор использовался для со­
здания маршрутов. не поддерживает применение специальных классов маршрутов.
Чтобы воспользоваться классом
(листинг
Листинг
LegacyRou te,
придется задействовать другой подход
16.15).
16. 15.
Применение специального класса маршрутизации
в файле
Startup. cs
из папки
using System;
using System.Collections.Generic;
UrlsAndRoutes
Глава 16. Дополнительные возможности маршрутизации
using
using
using
using
using
using
using
using
using
497
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Bui lder;
Microsoft.AspNetCore.Hos ting;
Microsoft.AspNetCore.Http ;
Microsoft.Extensions.Dep endencyinjection;
Microsoft.AspNetCore.Ro uting.Constraints;
Microsoft.AspNetCore.Rou ting;
UrlsAndRoutes.Infrastruc ture;
namespace UrlsAndRoutes {
puЫic class Startup {
void ConfigureServices(IServic eCollection services) {
services.Configure<RouteO ptions>(options => {
options.ConstraintMap.Add ("weekday", typeof(WeekDayConstraint) );
options.LowercaseUrls = true;
options.AppendTrailingSla sh = true;
puЫic
}) ;
services.AddMvc();
void Configure(IApplicationBuil der арр, IHostingEnvironment env) {
app.UseDeveloperExceptio nPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
routes.Routes.Add(new LegacyRoute(
"/articles/Windows 3.1 OVerview.html",
"/old/.NET_l.O_Cla;s_Lib rary"));
puЫic
routes.MapRoute(
name: "default",
template: "{controller=Home}/{actio n=Index}/{id?}");
routes.MapRoute(
name: "out",
template: "outbound/{controller=Hom e}/{action=Index}");
}) ;
Add ( ) на
IRouter. В теку­
При использовании специальных классов потребуется вызвать метод
коллекции маршрутов, чтобы зарегистрировать класс реализации
щем примере аргументами конструктора
URL,
являются унаследованные
LegacyRoute
которые должен поддерживать специальный маршрут. Запустив приложение
и запросив
/articles/Windows _ 3. l _ Overview. html,
специальный маршрут отобразит запрошенный
Маршрутизация на контроллеры
URL
можно увидеть результат
(рис.
MVC
Существует крупная брешь между сопоставлением с простыми строками
применением системы контроллеров, действий и представлений
туры
MVC.
-
16.6).
Razor
URL
и
инфраструк­
498
Часть 11 . Подробные сведения об инфраструктуре ASP.NET Core MVC 2
С
URL:
Ф localhos
/articles/Шndows_З.l_Overview.html
Рис.
16.6.
Использование специального маршрута
К счастью, при создании специальных маршрутов реализовывать такую функцио­
нальность самостоятельно н е придется, потому что для выполнения в се й тяжелой р а ­
боты может быть задействов а н класс. который
подготовиться к применению инфраструктуры
файл класса по имени
тинге
LegacyCont r oller. c s
MVC и с пользует "за кулисами". Что бы
MVC . до бавим в папку Co n troll ers
с содержимым . прив еденным в лис­
16.16.
Листинг
16.16.
Содержимое файла
LegacyController.cs
из папки
Controllers
u s ing Mi c r oso ft.AspN etCor e.Mvc;
n ames pa ce Ur l sAnd Routes . Cont r ollers
p uЫi c
c l ass LegacyContro ll e r : Control le r
puЫi c
Vie wResult GetL e gacyUr l(stri ng leg acyUrl)
=> View ( (o bject) l egac yUrl);
Метод дей ствия
Get LegacyU RL ()
в таком простом контроллер е принимает п а ­
раметр, который содержит унаследованный
URL,
запр о шенный клиентом. Если бы
данный контр оллер был реализован в реальном проекте. то упомянутый метод и с ­
пользовался бы для извлечения запрошенных файлов . но здесь про сто отображает с я
URL
в представлении.
Совет. Обратите внимание, что в листинге
типу
16.16
аргумент метода
ob ject . Одна из перегруженных версий метода Vie w ()
s t r ing, указывающий имя представления для визуализации,
v i ew ( )
приводится к
принимает параметр типа
и без приведения компи­
лятор С# выберет именно ее . Во избежание этого мы выполняем приведение к
object ,
чтобы однозначно вызывалась перегруженная версия, которая принимает модель пред­
ставления и применяет стандартное представление. Проблему можно было бы также ре­
шить за счет использования перегруженной версии, принимающей как имя представле­
ния, так и модель представления, но предпочтительнее по возможности не делать явных
ассоциаций между методами действий и представлениями. За дополнительными сведе­
ниями обращайтесь в главу
17.
Создадим папку View s/Le gacy и поме стим в нее файл представления по им е ни
Ge tLegacyUrl. c shtml с содержимым из листинга 16.17. Новое пр едставление отоб­
ражает знач е ние модели. которое покажет запрошенный клиентом
URL.
Глава 16. Дополнительные возможности маршрутизации
Листинг
Содержимое файла
16.17.
GetLegacyUrl. cshtml
из папки
499
Views/Legacy
@model string
@{ Layout = null;
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Routing</title>
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.min.css" />
</head>
<body class="m-1 р-1">
<h2>GetLegacyURL</h2>
The URL requested was: @Model
</body>
</html>
В листинге
им
URL
Листинг
16.18 класс LegacyRoute
модифицирован так. чтобы обрабатываемые
маршрутизировались на действие
16.18.
GetLegacyUr 1
Маршрутизация на контроллер в файле
из папки
контроллера
Legacy.
LegacyRoute. cs
Infrastructure
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.Dependencylnjection;
namespace UrlsAndRoutes.Infrastructure {
puЫic class LegacyRoute : IRouter {
private string[] urls;
private IRouter шvcRoute;
puЬlic LeqacyRoute(IServiceProvider services, paraшs string[] targetUrls)
this.urls = targetUrls;
шvcRoute
= services.GetRequiredService<МvcRouteHandler>();
}
async Task RouteAsync(RouteContext context) {
string requestedUrl = context.HttpContext.Request.Path
.Value.TrimEnd('/'};
if (urls.Contains(requestedUrl, StringComparer.OrdinalignoreCase))
context.RouteData.Values["controller"] = "Legacy";
context.RouteData.Values["action"] = "GetLegacyUrl";
context. RouteData. Values [ "legacyUrl"] = requestedUrl;
await шvcRoute.RouteAsync(context);
puЬlic
VirtualPathData GetVirtualPath(VirtualPathContext context) {
return null;
puЬlic
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
500
Класс
Microsoft. AspNetCore. Mvc. In ternal. MvcRou teHandler предоставляет
controller и action при­
механизм, посредством которого переменные сегментов
меняются для нахождения класса контроллера, выполнения метода действия и воз­
вращения результата клиенту. Этот класс был написан таким образом, что к нему
можно обращаться из специальной реализации
чения
controller
и
action,
IRouter.
которая предоставляет зна­
а также любые другие требующиеся значения вроде ар­
гументов метода действия.
В листинге
16.18
создается новый экземпляр класса
MvcRouteHandler,
которому
делегируется задача нахождения класса контроллера. Для этого необходимо предо­
ставить данные маршрутизации:
context.RouteData.Values["controller"J = "Legacy";
context. RouteData. Values [ "action"] = "GetLegacyUrl";
context.RouteData.Values["legacyUrl"J = requestedUrl;
Свойство
RouteContext. RouteData. Vales
возвращает словарь. который исполь­
зуется для предоставления значений данных классу
MvcRouteHandler.
В стандарт­
ной системе маршрутизации значения данных создаются путем применения шаблона
URL
к запросу. но в специальном классе маршрута значения жестко закодированы.
так что всегда производится направление на действие
Legacy.
GetLegacyUrl контроллера
legacyUr l. которое
Между запросами изменяется только значение данных
устанавливается в
URL запроса:
оно будет применяться в качестве аргумента с таким
же именем, получаемого методом действия.
Последнее изменение в листинге
16.18
делегирует ответственность за поиск и ис­
пользование класса контроллера для обработки запроса.
await mvcRoute.RouteAsync(context);
Объект RouteCoritext, теперь содержащий значения coritroller. action и
legacyUrl. передается методу RouteAsync () объекта MvcRouteHandler, который
берет на себя обязанность за любую дальнейшую обработку запроса. включая уста­
новку свойства
Handler.
решении о том. какие
В результате класс
URL
LegacyRoute
может сосредоточиться на
он будет обрабатывать. не имея дела с деталями работы с
контроллерами напрямую.
Объект
MvcRouteHandler,
выполняющий работу в рассматриваемом примере.
должен запрашиваться как служба. что объясняется в главе
конструктор
LegacyRoute
18.
Чтобы предоставить
с объектом реализации
обходим ему для создания объекта
IServiceProvider. который не­
MvcRouteHandler. в классе Startup обновлен
оператор, определяющий маршрут. с целью снабжения его возможностью доступа к
службам приложения (листинг
Листинг
using
using
using
using
using
using
16. 19).
16.19. Обеспечение доступа к службам приложения в файле Startup. cs
из папки UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Глава 16. Дополнительны е возможности маршрутизации
using
using
using
using
using
501
Microsoft.AspNetCore . Ht tp;
Mi c ros oft.Ex tensi ons . Dependencylnjecti on ;
Micros of t. Asp Ne tCore . Routing . Cons t raints;
Microso ft . AspNetCore . Rout ing ;
UrlsAndRoutes.Infrastruct ur e;
namespace UrlsAndRoutes {
puЫ i c c l ass Startup {
void ConfigureServices ( IServiceCollection services ) {
services . Co nfigure <RouteOptio ns >(opt i ons => {
options . ConstraintMap . Add ( "weekday", t ypeo f (Week DayConstr aint )) ;
options.LowercaseUrls = true ;
options.AppendTrailingSlash = true ;
puЫic
)) ;
se r vices . AddMv c() ;
void Configure(IApplicat i onBui lder
app.UseDeve lo perExcep t ionPa ge();
app.UseS tatusCodePag es();
app.UseSt atic fil es() ;
app .Use Mvc(routes => (
puЫic
арр,
IHostingEnvironment env) (
routes.Routes.Add(new LegacyRoute(
app.ApplicationServices ,
"/articles/Windows 3.1 Overview.html",
"/old/.NET_l.O_Cla;s_Library"));
rou te s . MapRou t.e (
name : " defaul t",
template: " {controller= Home)/{action= I ndex)/ {id ?} " ) ;
routes . MapRoute (
name : " out ",
template:
" outbound/{ cont roller=H ome) /{ a c tioп= In dex ) " );
}) ;
Запу стив приложение и запрос и в
/ articles / Wi ndows 3 . 1 _ Overv i ew . h tml
еще
раз. можно увидеть. что простой текстовый ответ был заменен выводом из представ ­
ления (рис.
16. 7).
С) Routing
GetlegacyU RL
The URL requested 111as: /articles/Windo1•1s_3. 1_0verview.htnil
Рис.
16. 7.
Делегирование работы с контроллерами и представлениями
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
502
Генерирование исходящих
URL
Для поддержки генерации исходящих
LegacyRoute
Листинг
метод
16.20.
GetVirtualPath
Генерирование исходящих
из папки
using
using
using
using
using
using
using
using
URL
мы должны реализовать в классе
(),как показано в листинге
URL
в файле
16.20.
LegacyRou te. cs
Infrastructure
Microsoft.AspNetCore.Http;
Microsoft.AspNetCore.Routing;
System;
System.Linq;
System.Text;
System.Threading.Tasks;
Microsoft.AspNetCore.Mvc.Internal;
Microsoft.Extensions.Dependencyinjection;
namespace UrlsAndRoutes.Infrastructure
puЫic class LegacyRoute : IRouter (
private string[J urls;
private IRouter mvcRoute;
puЫic
LegacyRoute(IServiceProvider services, params string[] targetUrls)
this.urls = targetUrls;
mvcRoute = services.GetRequiredService<MvcRouteHandler>();
puЫic
async Task RouteAsync(RouteContext context) {
string requestedUrl = context.HttpContext.Request.Path
.Value.TrimEnd('/');
if (urls.Contains(requestedUrl, StringComparer.OrdinalignoreCase))
context.RouteData.Values["controller"J = "Legacy";
coлtext.RouteData.Values["action"J = "GetLegacyUrl";
context.RouteData.Values["legacyUrl"J = requestedUrl;
await mvcRoute.RouteAsync(context);
puЫic
VirtualPathData GetVirtualPath(VirtualPathContext context) {
if (context. Values. ContainsKey ( "legacyUrl")) {
string url = context.Values["legacyUrl"] аз string;
if (urls.Contains(url)) {
return new VirtualPathData(this, url);
return null;
Система маршрутизации вызывает метод
который был определен в классе
дящий
URL,
объект
VirtualPathContext,
носительно
Startup,
GetVirtualPath ()
требующийся приложению. Аргумент метода
URL.
В табл.
16.5
каждого маршрута.
давая каждому шанс сгенерировать исхо­
GetVirtualPath () -
это
который предоставляет необходимую информацию от­
описаны свойства класса
VirtualPathContext.
503
Глава 16. Доnолнительные возможности маршрутизации
16.5. Свойства, определенные в классе VirtualPathContext
Таблица
Имя
Описание
RouteName
Это свойство возвращает имя маршрута
Values
Это свойство возвращает индексированный по имени словарь значе­
ний, которые могут применяться для переменных сегментов
ArnЬientValues
Это свойство возвращает словарь значений, которые полезны при гене­
рации URL, но не будут встраиваться в результат. В случае реализации
собственного класса маршрутизации такой словарь обычно пуст
Это свойство возвращает объект
HttpContext
HttpContext,
который предо­
ставляет информацию о запросе и ответе, находящемся в процессе
подготовки
В
Va 1 u е s
примере свойство
рассматриваемом
ния значения по имени
legacyUrl,
получе­
используется для
и если оно соответствует одному из
URL,
ко­
торые были сконфигурированы для поддержки маршрутом, тогда возвращается
объект
VirtualPathData,
снабжающий систему маршрутизации деталями
Аргументами конструктора класса
IRouter,
который генерирует
URL,
VirtualPathData
и сам
URL.
являются объект реализации
URL.
return new VirtualPathData(this, url);
В листинге
показано измененное представление
16.21
шивания исходящих
Листинг
16.21.
URL,
Генерирование исходящих
в файле
Resul t. cshtml
для запра­
которые нацелены на специальное представление.
URL
из специального класса маршрута
Resul t. csh tml из папки Views/ Shared
@model Result
@( Layout = null;
< ! DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Routing</title>
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.min.css" />
</head>
<body class="m-1
р-1">
<tаЫе class="taЫe taЫe-bordered taЬle-striped taЫe-sm">
<tr><th>Controller:</th><td>@Model.Controller</td ></tr>
<tr><th>Action:</th><td>@Model.Action</td></tr>
@foreach (string key in Model.Data.Keys) (
<tr><th>@key :</th><td>@Model.Data[key]</td></tr>
</tаЫе>
<а asp-route-legacyurl="/articles/Windows_З.1
class="Ьtn Ьtn-primary">
This is an out9oin9 URL
</а>
Overview.html"
Часть 11. Подробные сведения об инфраструктуре ASP.NET Соге MVC 2
504
<р>
URL: @Url.Action(null, null,
new { legacyurl = 11 /articles/Windows_З.l OVerview.html" })
</р>
</body>
</html>
В этом примере нет необходимости указывать вспомогательной функции дескрип­
тора контроллер и действие для исходящего маршрута, т.к. при генерации
не применяются, а потому атрибуты
asp-controller
опущены. Когда генерируется только
URL,
го метода
Url .Action ()
и
asp-action
URL
они
дескриптора а
первые два аргумента для вспомогательно­
устанавливаются в
null
по той же причине.
Если запустить приложение и просмотреть НТМL-разметку, полученную в ответ
на запрос стандартного
URL,
то легко заметить, что для создания
URL использовался
специальный класс маршрута:
<а class="Ьtn Ьtn-primary" href="/articles/windows_З.l_overview.html/">
This is an outgoing URL
</а>
<p>URL:
/articles/windows_З.1
overview.html/</p>
Завершающие косые черты, добавленные к
true
конфигурационного свойства
URL,
являются результатом установки в
AppendTrailingSlash
внутри файла
Startup. cs,
и важно. чтобы сопоставление входящего маршрута было способно соответствовать
URL
с добавленным символом косой черты.
URL, который вы видите в НТМL-ответе, имеет отличающийся формат, такой как
/? legacyur 1=%2Farticles %2FWindows _ 3. 1 _ Overv iew. html, то значит, что для
генерации URL специальный маршрут не применялся, а вместо него был вызван какой­
Совет. Если
то другой маршрут в приложении. Поскольку никакого контроллера или действия не было
указано, произошло нацеливание на действие
Index контроллера Home, и к строке за­
legacylJrl. В таком случае удостоверьтесь в установке
свойства IsBound в true внутри метода GetVirtualPath (),и проверьте, что в кон­
фигурации внутри файла Startup. cs указаны корректные URL для конструктора класса
LegacyRoute, а специальный маршрут определен перед всеми остальными маршрутами.
проса
URL
добавилось значение
Работа с областями
Инфраструктура ASP.NEТ Соге МVС поддерживает организацию веб-приложения
в виде областей, где каждая область представляет функциональный сегмент прило­
жения, такой как администрирование, выписка счетов-фактур, поддержка пользо­
вателей и т.д. Прием удобен в крупных проектах, в которых наличие единственно­
го набора папок для всех контроллеров, представлений и моделей может привести к
сложностям в управлении.
Каждая область
MVC
имеет собственную структуру папок, позволяя все хранить
отдельно. Такой подход делает более очевидным то, какие элементы проекта относят­
ся к каждой части функциональности приложения, и помогает множеству разработ­
чиков трудиться над проектом, не конфликтуя друг с другом. Области в значительной
степени поддерживаются системой маршрутизации и потому они рассматриваются
вместе с
URL
и маршрутами. В настоящем разделе будет показано, как настраивать
и пользоваться областями в проектах МVС.
505
Глава 16. Дополнительные возможности маршрутизации
Создание области
Создание области требует добавления папок в проект. Папка верхнего уровня на­
зывается
Areas.
Внутри нее для каждой необходимой области предусмотрена своя
папка. содержащая собственные подпапки
нируем создать область по имени
санных в табл.
ленные в табл.
Таблица
16.6.
16.6.
16.6.
Admin,
Controllers, Views
и
Models.
Мы пла­
что означает создание набора папок, опи­
Для подготовки примера проекта создадим все папки. перечис­
Папки, требуемые для подготовки областей
Имя
Описание
Areas
Эта папка будет содержать все области в приложении
Areas/Admin
Эта папка будет содержать классы и представления для
области
MVC
Admin
Areas/Admin/Controllers
Эта папка будет содержать контроллеры для области
Admin
Areas/Admin/Views
Эта папка будет содержать представления для области
Admin
Areas/Admin/Views/Home
Эта папка будет содержать представления для контроллера
Home в области Admin
Areas/Admin/Models
Эта папка будет содержать модели для области
Admin
Хотя каждая область применяется по отдельности, многие средства
лагаются на стандартные функциональные возможности С# или
.NET,
MVC
по­
такие как
пространства имен. Чтобы облегчить использование области, первым делом нуж­
но добавить файл импортирования представлений, который позволит работать
с моделями из области внутри представлений, не включая пространства имен и
получая преимущество от применения вспомогательных функций дескрипторов.
Создадим в папке
ни_ View!mports.
Листинг
16.22.
Areas/Admin/Views файл импортирования представлений по име­
cshtml и поместим в него операторы, приведенные в листинге 16.22.
Содержимое файла
из папки
_Viewimports. csh tml
Areas/Admin/Views
@using UrlsAndRoutes.Areas.Admin.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Создание маршрута для области
Чтобы задействовать области, потребуется добавить в файл
который содержит переменную сегмента
Листинг
16.23.
(листинг
UrlsAndRoutes
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Startup. cs
16.23).
Добавление маршрута для областей в файле
из папки
using
using
using
using
using
area
Startup. cs
маршрут,
506
Часть 11. Подробные сведения об инфраструктуре ASP.NET Соге MVC 2
using
using
using
using
using
using
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
Microsoft.AspNetCore.Routing.Constraints;
Microsoft.AspNetCore.Routing;
UrlsAndRoutes.Infrastructure;
namespace UrlsAndRoutes {
puЫic class Startup {
puЫic
void ConfigureServices(IServiceCollection services) {
services.Configure<RouteOptions>(options => {
options. ConstraintMap. Add ( "weekday", typeof (WeekDayConstraint) ) ;
options.LowercaseUrls = true;
options.AppendTrailingSlash = true;
} ) ;
services.AddMvc();
puЫic
void Configure(IApplicationBuilder
app.UseDeveloperExceptionPage();
app.UseStatusCodePages();
app.UseStaticFiles();
app.UseMvc(routes => {
арр,
IHostingEnvironment env) {
routes.МapRoute(
name : "areas" ,
template: "{area:exists}/{controller=Home}/{action=Index}");
routes.Routes.Add(new LegacyRoute(
app.ApplicationServices,
"/articles/Windows_З.l_Overview.html",
"/old/.NET 1.0 Class Library"));
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "out",
template: "outbound/{controller=Home}/{action=Index}");
}) ;
Переменная сегмента
area
используется для сопоставления с
URL,
которые наце­
лены на контроллеры в специфических областях. В листинге задействован стандар­
тный шаблон
URL,
но сегмент
area
можно добавлять в любой требующийся шаблон.
Маршрут, который добавляет поддержку областей, должен находиться перед менее
специфическими маршрутами для обеспечения корректного сопоставления с
Ограничение
exist:;;
URL.
применяется для гарантии того, что запросы соответствуют
только областям, которые были определены в приложении.
Заполнение области
Контроллеры, представления и модели в области можно создавать точно так
же, как в главной части приложения
кнопкой мыши на папке
MVC.
Чтобы создать модель, щелкнем правой
Areas/Admin/Models,
выберем в контекстном меню пункт
507
Глава 16. Дополнительные возможности маршрутизации
AddqClass
(Добавитьqf\Jшсс) и создадим файл
мое которого показано в листинге
Листинг
16.24.
Содержимое файла
IUiacca
по имени
Person. cs,
содержи­
16.24.
Person. cs
из папки
Areas/Admin/Models
namespace UrlsAndRoutes.Areas.Admin.Models
puЫic class Person {
puЫic string Name { get; set;
puЫic string Ci ty { get; set;
Для создания контроллера щелкнем правой кнопкой мыши на папке Areas/Admin/
Controllers. выберем в контекстном меню пункт AddqClass и создадим файл 1Uiacca
по имени HorneController. cs с определением контроллера из листинга 16.25.
Листинг
16.25.
Содержимое файла
из папки
HomeController. cs
Areas/Admin/Controllers
using Microsoft.AspNetCore.Mvc;
using UrlsAndRoutes.Areas.Admin.Models;
namespace UrlsAndRoutes.Areas.Admin.Controllers
[Area("Admin")]
class HomeController : Controller {
private Person[] data = new Person[] {
"Alice", Ci ty = "London" } ,
new Person { Name
"ВоЬ", City = "Paris" },
new Person { Name
"Joe", City = "New York"
new Person { Name
puЫic
};
puЫic
ViewResult Index() => View(data);
Новый контроллер в целом похож на стандартный кроме одного аспекта. Чтобы ас­
социировать контроллер с областью. к
1Uiaccy должен
быть применен атрибут
Area:
[Area ( "Admin") ]
puЬlic
class HomeController : Controller {
Без атрибута
Area
контроллеры не будут принадлежать области, даже если они не
определены в главной части приложения. Отсутствие атрибута
Area
может привести
к странным результатам. Это первое, что следует проверять. если при работе с облас­
тями получаются не те результаты, которые ожидались.
Совет. Если для настройки маршрутов используются атрибуты, как было описано в главе
15,
Route можно ссы­
латься на область, указанную с помощью атрибута Area: [ Route (" [ area] / арр/
[controller]/actions/[action]/{id:weekday?}")].
тогда с применением маркера
Наконец, добавим в папку
по имени
Index. cshtrnl,
[area]
в аргументе атрибута
Areas/Admin/Views/Horne
файл представления
содержимое которого показано в листинге
16.26.
Razor
508
Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC 2
Листинг
16.26.
Содержимое файла
Index. cshtml
из папки
Areas/Admin/Views/Home
@model Person[ ]
@( Layout = null;
< 1 DOCTYPE html>
<html>
<head>
<meta n a me="vi e wport" c on tent=" width=de v i c e -w idt h" / >
<t i tle >Ar eas< / tit le>
<link rel="styles heet" asp-href-in c l ude = "liЬ/boo tstrap /di st /css / *.m in .css" / >
</he ad>
<body class="m-1 р-1">
<tаЫе class="t a Ыe
taЫe-b order ed taЫe- s t ri p ed taЫe- s m" >
<tr><th>Name </th>< th>City< / th>< /tr>
@foreach (Pe rson р in Mo d e l ) {
<tr >< td >@p . Name < / td >< td>@p .C ity</ td >< / t r>
< / tаЫе>
</ body>
< / html>
Моделью для данного представления является массив объектов
Pe r s on . Благодаря
файлу импортирования представлений. содержимое которого бьvю приведено в листин­
ге
16.22,
есть возможность ссылаться на тип
Запуск приложения и запрос
Person
без указания проС'транства имен.
URL вида / Adrnin даст результат.
Влияние области на приложение
показанный на рис.
16.8.
MVC
Важно понимать влияние, которое области оказывают на остальные части приложения. Мы
создали область по имени Adrniп, но в главной части приложения имеется также контрол­
Adrnin . До создания области запрос /Admin направлялся бы действию Index конт ­
Adrnin в главной части приложения; теперь он будет нацелен на действие Inde x
контроллера Ноте в области Adrnin (корень области предоставляет стандартные значения
лер
роллера
для переменных сегментов
contr o l ler
и
action).
Изменение такого вида может стать
причиной неожиданного поведения, и наилучший способ применения областей предусмат­
ривает встраивание их использования в первоначальную схему именования контроллеров
для проекта. Если возникла потребность добавить области в готовое приложение, то вы
должны тщательно обдумать их влияние на имеющиеся маршруты.
Name
Clty
Alice
london
ВоЬ
Paris
Joe
NewYork
Рис.
16.8.
Использование области
Глава 16. Дополнительные возможности маршрутизации
509
Генерирование ссылок на действия в областях
Для создания ссылок, указывающих на действия в той же области, к которой от­
носится текущий запрос, никаких дополнительных шагов предпринимать не придет­
ся. Инфраструктура
MVC
обнаруживает, что текущий запрос относится к конкретной
области, и гарантирует, что средство генерации исходящих
URL
будет искать соот­
ветствие только среди маршрутов, определенных для данной области. Например, в
16.27
листинге
папки
демонстрируется добавление элемента а в файле
Index. cshtml из
Areas/Admin/Views/Home.
Листинг 1б.27. Добавление элемента а в файле
из папки
Index. csh tml
Areas/Admin/Views/Home
@model Person[J
@{ Layout = null;
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-wid th" />
<title>Areas</title>
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.min.css" />
</head>
<body class="m-1 р-1">
<tаЫе
class="taЫe taЫe-bordered taЫe-striped taЬle-sm">
<tr><th>Name</th><th>Cit y</th></tr>
@foreach (Person р in Model) {
<tr><td>@p.Name</td><td> @p.City</td></tr>
</tаЫе>
<а
asp-action="Index" asp-controller="Home">Lin k</a>
</body>
</html>
Запустив приложение и запросив
URL
вида
/ admin,
можно заметить, что ответ
содержит следующий элемент:
<а
href="/admin/">Link</a>
При генерации исходящей ссылки система маршрутизации выбрала маршрут
для области и учла стандартные значения, доступные для переменных сегментов
controller
и
action.
Чтобы создать ссылку на действие в другой области или в главной части прило­
жения, систему маршрутизации необходимо снабдить значением для сегмента
(листинг
Листинг
16.28).
16.28.
Нацеливание на другую область в файле
из папки
@model Person []
@{ Layout = null;
<!DOCTYPE html>
<html>
Areas/Admin/Views/Home
Index.cshtml
area
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
510
<head>
<meta name="viewport" content="width=device-width" />
<title>Areas</title>
<link rel="stylesheet" asp-href-include="lib/bootstrap/dist/css/*.min.css" />
</head>
<body class="m-1 р-1">
<tаЫе class="taЫe
taЫe-bordered taЫe-striped taЫe-sm">
<tr><th>Name</th><th>City</th></tr>
@foreach (Person р in Model) {
<tr><td>@p.Name</td><td>@p.City</td></tr>
</tаЫе>
<а
asp-action="Index" asp-controller="Home">Link</a>
<а
asp-action="Index" asp-controller="Home" asp-route-area="">Link</a>
</body>
</html>
Атрибут
asp-route-area
устанавливает значение для переменной сегмента
area.
В данном случае эта переменная устанавливается в пустую строку. что указывает на
главную часть приложения и дает следующий НТМL-элемент:
<а
href="/">Link</a>
Если вы располагаете в своих контроллерах множеством областей и хотите марш­
рутизировать на них. тогда вместо пустой строки используйте имя нужной области.
Полезные советы относительно схемы
URL
После всего сказанного у вас может все же остаться вопрос: с чего начинать про­
ектирование собственной схемы
URL?
Вы можете просто принять стандартную схему.
но организация собственной схемы обеспечивает ряд преимуществ. В последние годы
URL
проектное решение с
приложения применяется все более широко, и появилось
несколько важных принципов проектирования. Следуя этим принципам проектиро­
вания. вы улучшите удобство использования, совместимость и поисковый рейтинг
своих приложений.
Делайте
URL
чистыми и понятными человеку
Пользователи обращают внимание на
URL в
приложениях. Просто вспомните, ког­
да вы в последний раз пытались отправить кому-то
выглядит
URL для
URL
веб-сайта
Amazon.
Вот как
предыдущего англоязычного издания настоящей книги:
https://www.amazon.com/Pro-ASP-NET-Core-ADAМ-FREEMAN/dp/1484203984
Отправить по электронной почте такой
URL еще
реально. но попробуйте продикто­
вать его по телефону. Когда подобной работой приходится заниматься часто. в итоге
проще сообщить номер
на веб-сайте
URL
Amazon.
ISBN
книги и предложить самостоятельно найти ее страницу
Было бы неплохо получать доступ к странице книги с помощью
следующего вида:
http://www.amazon.com/books/pro-asp-net-core
Такой
URL
вполне можно было бы диктовать по телефону. и он не выглядит так.
будто при наборе почтового сообщения на клавиатуру что-то свалилось.
511
Глава 16. Дополнительные возможности маршрутизации
На заметку! В качестве пояснения: я глубоко уважаю компанию Amazoп, которая продает
моих книг больше, чем все остальные вместе взятые. Мне известен факт, что все сотруд­
ники Amazoп
- замечательно умные и приятные люди. Никто из них не окажется до такой
степени мелочным, что прекратит продажи моих книг из-за настолько несущественного,
как критика применяемого в Amazoп формата
URL. Я люблю Amazoп. Я преклоняюсь пе­
URL.
ред Amazoп. Я просто хочу, чтобы там привели в порядок свои
Ниже даются руководящие принципы построения дружественных к пользователям
URL.
•
Проектируйте
URL
так, чтобы они описывали предоставляемое содержимое, а
не детали реализации приложения. Используйте
не
•
/Articles/AnnualReport.
/Website v2/CachedContentServer/FromCache/AnnualReport.
а
Отдавайте предпочтение заголовкам содержимого перед идентификационными
номерами. Применяйте
/Articles/AnnualReport.
а не
/Articles/2392.
Если
вы должны использовать идентификационные номера (для различения элемен­
тов с одинаковыми заголовками или во избежание дополнительных запросов к
базе данных. необходимых для поиска элемента по его заголовку). тогда указы­
вайте и номер. и заголовок (например.
URL дольше
/Articles/2392/AnnualReport).
Такой
набирать, но он имеет больше смысла для человека и улучшает по­
исковый рейтинг. Приложение может просто игнорировать заголовок и отобра­
жать элемент, соответствующий идентификатору.
•
Не применяйте расширения имен файлов для НТМL-страниц (скажем,
или
как
. aspx
.mvc), но используйте их для специализированных типов файлов (таких
. jpg .. pdf и . zip). Веб-браузеры не придают значения расширениям имен
файлов. если тип
скажем, файлов
•
MIME
PDF
установлен корректно. но люди ожидают, что имена,
заканчиваются на
.pdf.
Создавайте осмысленную иерархию (например,
/Products/Menswear /Shirt.s/Red),
чтобы посетитель смог догадаться, как выглядит
•
URL
родительской категории.
Поддерживайте независимость от регистра символов. (Кто-то может пожелать
ввести
URL. указанный на печатной странице.) По умолчанию система
ASP.NET Core не чувствительна к регистру символов.
марш­
рутизации
•
Избегайте применения знаков, кодов и символьных последовательностей. Если
вам нужен разделитель слов, то используйте дефис (как в
Подчеркивания
не
являются
дружественными
кодированные пробелы выглядят либо странно
к
/my-great-article).
пользователю,
(/my+grea t +art icle),
а
URL-
либо плo­
xo (/my% 20great %20arti cl~.
•
Не изменяйте
URL. Неработающие ссылки равнозначны потерям в бизнесе.
URL продолжайте поддерживать старую схему URL насколько
После изменения
возможно долго посредством перенаправления.
•
Поддерживайте согласованность. Примите один формат
URL в
масштабах всего
приложения.
URL
должны быть короткими, легкими для набора. редактируемыми человеком и
постоянными. к тому же они обязаны отражать структуру сайта. Якоб Нильсен. гуру по
удобству использования, развивает эту тему по адресу
https: / /www. nngroup. сот/
articles/url-as-ui/. ТИм Бернерс-Ли, изобретатель веб-сети. предлагает анало­
гичные советы (https: / /www. w3. org/Provider/Style/URI).
512
Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC 2
GET и POST: выбор правильного запроса
Эмпирическое правило гласит, что запросы
GET
должны применяться для извлече­
ния информации, предназначенной только для чтения, а запросы
POST -
для любой
операции, которая изменяет состояние приложения. В терминах соответствия стан­
дартам запросы
GET
предназначены для безопасных взаимодействий (не имеющих
побочных эффектов помимо извлечения информации), а запросы
POST -
для небе­
зопасных взаимодействий (принятие решения либо изменение чего-нибудь). Такие
соглашения установлены консорциумом WЗС
(World Wlde Web
Coпsortlum) и описаны
по адресу:
http://www.w3.org/Protocols/rfc2616/rfc2616-sec 9.html
Запросы
GET являются адресуемыми: вся информация содержится в URL. а пото­
му подобные адреса можно добавлять в закладки и ссылаться на них в будущем.
Не используйте запросы
GET
для операций, которые изменяют состояние. Многие
разработчики веб-приложений научились этому на горьком опыте в
бьmо выпущено приложение
Google Web Accelerator.
2005
году. когда
Оно с упреждением выбирало все
содержимое. на которое ссылалась каждая страница. что в рамках протокола НТТР
вполне законно, поскольку запросы
GET
должны быть безопасными. К сожалению,
многие разработчики веб-приложений проигнорировали соглашения НТТР и разме­
щали в своих приложениях простые ссылки на операции типа "удалить элемент" или
"добавить в корзину". В результате получился хаос.
В одной компании были уверены, что их система управления содержимым стала
целью неоднократных атак со стороны злоумышленников, т.к. все содержимое пос­
тоянно удалялось. Позже в компании обнаружили, что поисковый агент наткнул­
ся на
URL
административной страницы и прошелся по всем ссылкам на удаление.
Аутентификация может защитить от таких действий со стороны пользователей, но не
способна обеспечить защиту от веб-акселераторов.
Резюме
В главе рассматривались дополнительные возможности системы маршрутизации.
Вы узнали, как генерировать исходящие ссылки и
URL,
а также научились настраи­
вать систему маршрутизации. По ходу дела вы ознакомились с концепцией областей
и с рекомендациями относительно того, как должна создаваться удобная и содержа­
тельная схема
URL.
В следующей главе мы перейдем к исследованию контроллеров и
действий, которые являются центральной частью инфраструктуры
ASP.NET Core
МVС.
Будет подробно описана их работа и показано, как их применять для получения на­
илучших результатов в приложении.
ГЛАВА
17
Контроллеры и действия
в приложение, обрабатывается контроллером.
к аждый запрос. который поступает
.NET,
Core
В инфраструктуре ASP.NEТ
МVС контроллеры являются классами
рые содержат логику, требующуюся для обработки запроса. В главе
3
кото­
объяснялось, что
роль контроллера заключается в инкапсуляции логики приложения. Это значит, что
контроллеры отвечают за обработку входящих запросов, вьшолнение операций над мо­
делью предметной области и выбор представлений для визуализации пользователю.
Контроллер волен обрабатывать запрос любым способом, какой посчитает подхо­
дящим, до тех пор, пока он не забирается в области ответственности, которые от­
носятся к модели и представлению. Таким образом, контроллеры не содержат и не
сохраняют данные, а также не генерируют пользовательские интерфейсы.
В настоящей главе мы рассмотрим реализацию контроллеров, а также различные
способы применения контроллеров для получения и генерации вывода. В табл.
17.1
приведена сводка, позволяющая поместить контроллеры в контекст.
Таблица
17. 1.
Помещение контроллеров в контекст
Вопрос
Ответ
Что это такое?
Контроллеры содержат логику для получения запросов, обновле­
ния состояния или модели приложения и выбора ответа, который
будет отправлен клиенту
Чем они полезны?
Контроллеры являются центральной частью проектов
MVC и со­
держат логику предметной области для веб-приложения
Как они используются?
Контроллеры - это классы С#, открытые методы которых вызывают­
ся для обработки НТТР-запроса. Методы могут брать на себя ответс­
твенность за выдачу ответа клиенту напрямую, но более распростра­
ненный подход предусматривает возвращение результата действия,
который сообщает
MVC, как ответ должен быть подготовлен
- новичок в области разработки приложений MVC, то
Существуют ли какие­
Если вы
то скрытые ловушки
можете легко создавать контроллеры, содержащие функциональ­
или ограничения?
ность, которая больше подходит для модели или представления.
Более специфичная проблема связана с тем, что любые открытые
Controller, инфра­
MVC считает контроллерами; это означает возможность
классы с именами, заканчивающимися на
структура
непредумышленной обработки НТТР-запросов в классах, которые
не планировались быть контроллерами
Существуют ли
альтернативы?
Нет, контроллеры
-
основная часть приложений
MVC
514
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
В табл.
Таблица
17.2
17.2.
приведена сводка по главе.
Сводка по главе
Задача
Решение
Определение контроллера
Листинг
Создайте открытый класс с именем,
Controller, или
класса Controller
заканчивающимся на
унаследуйте его от
Получение деталей НТТР-запроса
Используйте объекты контекста или оп-
Выдача результата из метода действия
17.717.9
ределите параметры методов действий
17.1017.13
Работайте напрямую с объектом контекста результата или создайте объект
17.1417.16
результата действия
Выдача НТМL-результата
Создайте результат представления
17.1717.24
Перенаправление клиента
Создайте результат перенаправления
17.2517.30
Возвращение содержимого клиенту
Создайте результат содержимого
17.3117.35
Возвращение кода СОСТОЯНИЯ НТТР
Создайте НПР-результат
17.36,
17.37
Подготовка проекта для примера
Для целей главы мы создадим новый проект типа
ControllersAndActions
с применением шаблона
Empty (Пустой) по имени
ASP.NET Core Web Applicatioп (.NET
Core) (Веб-приложение ASP.NET Соге (.NET Core)). Добавим в класс Startup операто­
ры, чтобы включить инфраструктуру MVC и другие компоненты промежуточного ПО
(листинг
17.1).
На заметку! Данная глава содержит модульные тесты для основных средств. Для краткости
проекты модульного тестирования в инструкции по созданию примера проекта не включе­
ны. Вы можете создавать тестовые проекты, следуя процессу, который описан в главе
или загрузить готовый проект
SportsStore
из хранилища
GitHub
7,
для настоящей книги
(https: / /gi thub. com/apress/pro-asp .net-core-mvc-2).
Листинг
17 .1.
Добавление инфраструктуры
ПО в файле
using
using
using
using
using
using
using
using
Startup. cs
MVC
и других компонентов промежуточного
из папки
ControllersAndActions
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.AspNetCore.Builder;
Microsoft.AspNetCore.Hosting;
Microsoft.AspNetCore.Http;
Microsoft.Extensions.Dependencyinjection;
Глава 17. Контроллеры и действия
515
namespace ControllersAndActions {
puЫic class Startup {
puЫic
void ConfigureServices(IServiceCollection services) {
services.AddМvc();
services.AddМeшoryCache();
services.AddSession();
puЫic
void Configure(IApplicationBuilder
арр,
IHostingEnvironment env) {
app.UseStatusCodePages();
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseSession();
app.UseМvcWithDefaultRoute();
)
Методы
AddMemoryCache () и AddSession () создают службы. необходимые для
UseSession () добавляет в конвейер компонент проме­
управления сеансом. Метод
жуточного ПО, который ассоциирует данные сеанса с запросами, и добавляет сооkiе­
наборы к ответам. гарантируя возможность идентификации будущих запросов. Метод
UseSession ()
должен вызываться перед вызовом метода
UseMvc
(),чтобы компо­
нент сеанса мог перехватывать запросы, прежде чем они достигнут промежуточного
ПО
MVC.
и модифицировать ответы после того, как они были сгенерированы. Другие
методы настраивают стандартные пакеты, которые рассматривались в главе
14.
Подготовка представлений
Основное внимание в главе уделяется контроллерам и их методам действий. поэ­
тому классы контроллеров будут определяться в главе повсеместно. В качестве под­
готовки будут определены представления, которые помогут демонстрировать работу
контроллеров и методов действий. Созданные в текущем разделе представления на­
ходятся в папке
Views/Shared,
что позволит их использовать в любых контроллерах,
которые будут определяться позже в главе. Создадим папку
нее файл представления Razoг по имени
ку. показанную в листинге
Листинг
17.2.
Resul t. cshtml
Views/Shared,
добавим в
и поместим в файл размет­
17.2.
Содержимое файла
Result. cshtшl
из папки
Views/Shared
@model string
@{ Layout = null;
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Controllers and Actions</title>
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.min.css" />
</head>
<body class="m-1 р-1">
Model Data: @Model
</body>
</html>
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
516
Моделью для данного представления является объект типа
лит отображать простые сообщения. Далее создадим в папке
имени
тинге
DictionaryResul t. csh tml
17.3.
string. который позво­
Views/Shared файл по
и поместим в него разметку, приведенную в лис­
В качестве модели для этого представления выбран словарь. который даст
возможность отображать более сложные данные, чем в предыдущем представлении.
Листинг
17 .3.
Содержимое файла
из папки
DictionaryResul t. csh tml
Views/Shared
@model IDictionary<string, string>
@( Layout = null; 1
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Controllers and Actions</title>
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.min.css" />
</head>
<body class="m-1 р-1 ">
<tаЫе class="taЫe taЬle-bordered taЫe-sm taЬle-striped">
<tr><th>Name</th><th>Value</th></tr>
@foreach (string key in Model.Keys} (
<tr><td>@key</td><td>@Model[key]</td></tr>
</tаЫе>
</body>
</html>
Затем создадим в папке
Views/Shared файл по имени SimpleForm. cshtml и оп­
ределим в нем представление, показанное в листинге
17.4.
Как следует из названия.
представление содержит простую НТМL-форму, которая будет получать данные от
пользователя.
Листинг
17.4.
Содержимое файла
SimpleForm. cshtml
из папки
Views/Shared
@( Layout = null;
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Controllers and Actions</title>
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/* .min.css" />
</head>
<body class="m-1 р-1">
<form method="post" asp-action="ReceiveForm">
<div class="form-group">
<label for="name">Name:</label>
<input class="form-control" name="name" />
</div>
<div class="form-group">
<label for="name">City:</label>
<input class="form-control" name="city" />
</div>
Глава 17. Контроллеры и действия
<button class="btn btn-prirnary
</button>
</forrn>
</body>
</htrnl>
517
type="subrnit">Subrnit
center-Ыock"
В представлениях используются встроенные вспомогательные функции дескрип­
торов для генерации
URL из
системы маршрутизации. Чтобы включить вспомогатель­
ные функции дескрипторов, создадим в папке
лений по имени_ Viewimports.
листинге
Листинг
cshtrnl
Views
файл импортирования представ­
и добавим в него выражение, приведенное в
17.5.
Содержимое файла_Viewimports.
17.5.
cshtml
из папки
Views
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Все представления. созданные в папке
Views/Shared,
зависят от пакета
имени Bootstгap. Для добавления Bootstгap в проект создадим файл
Bower Configuration File
(листинг 17.6).
применением шаблона элемента
и добавим пакет Bootstгap
Листинг
17 .6.
Добавление пакета в файле
CSS
по
bower. j son
с
(Файл конфигурации Воwег)
bower. j son из папки ControllersAndActions
"name": "asp.net",
"private": true,
"dependencies": {
"bootstrap": "4. О. 0-alpha. 6 11
Понятие контроллеров
Контроллеры
-
это классы С#. открытые методы которых (известные как методы
действий или просто действия) несут ответственность за обработку НТГР-запроса и
подготовку ответа, возвращаемого клиенту. Для определения. какой класс контрол­
лера и метод действия должны обработать запрос, инфраструктура МVС использует
систему маршрутизации. описанную в главах
15
и
16.
Затем она создает новый эк­
земпляр класса контроллера. вызывает метод действия и применяет результат метода
для выпуска ответа клиенту.
Инфраструктура
MVC
снабжает методы действий данными контекста, так что
они могут выяснить, каким образом обрабатывать запрос. Доступен широкий спектр
данных контекста, который описывает все, что касается текущего запроса. подготав­
ливаемого ответа, извлеченных системой маршрутизации данных и деталей идентич­
ности пользователя.
Когда
MVC
вызывает метод действия. ответ из метода описывает ответ, который
должен быть послан клиенту. Самый распространенный вид ответа создается путем
визуализации представления Razoг, поэтому метод действия использует свой ответ,
чтобы сообщить
MVC,
какое представление задействовать и какие данные модели
представления ему предоставить. Но имеются также другие виды ответов, и методы
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
518
действий могут делать все, что угодно, от требования у
MVC
отправки клиенту пере­
направления НТГР до отправки сложных объектов данных.
Таким образом, существуют три области функциональности, которые важны для
понимания контроллеров. Во-первых, нужно понимать особенности определения кон­
троллеров, чтобы инфраструктура
MVC
могла применять их для обработки запросов.
Контроллеры представляют собой просто классы С#. но создавать их можно разными
способами, и важно уяснить отличия между ними. Определение контроллеров рас­
сматривается в разделе "Создание контроллеров" далее в главе.
Во-вторых, следует понимать. каким образом МVС снабжает методы действий дан­
ными контекста. Получение необходимых данных контекста важно для эффективной
разработки веб-приложений, а инфраструктура
MVC
облегчает его, определяя набор
классов, которые используются с целью описания всего, что требуется тому или ино­
му методу действия. В разделе "Получение данных контекста" позже в главе объясня­
ется, как
MVC
описывает запросы и ответы.
Наконец, в-третьих, важно понимать, каким образом методы действий выпускают
ответ. Методы действий редко нуждаются в самостоятельной генерации НТГР-ответа,
и вы должны знать, каким образом инструктировать
MVC
о необходимости выпус­
ка требуемых ответов, что будет показано в разделе "Генерирование ответа" далее в
главе.
Создание контроллеров
Вы сталкивались с применением контроллеров почти во всех предшествующих
главах. Наступило время заглянуть "за кулисы" и посмотреть, как они определяются.
В последующих разделах будут описаны различные способы создания контроллеров
и приведены объяснения отличий между ними.
Создание контроллеров РОСО
Инфраструктура
MVC
поддерживает соглашение по конфигурации, а это значит. что
контроллеры в приложении МVС обнаруживаются автоматически вместо того, чтобы
определяться в конфигурационном файле. Базовый процесс обнаружения прост: любой
класс puЫic с именем, заканчивающимся на
Controller, является контроллером. а
- действием. Чтобы выяснить, как все ра­
ботает, добавим в проект папку Controllers и поместим в нее файл класса по имени
PocoController. cs. содержимое которого приведено в листинге 17.7.
любой определенный в нем метод puЫic
Совет. Хотя соглашение предусматривает помещение контроллеров в папку
они могут находиться где угодно в проекте и
Листинг
17.7.
Содержимое файла
MVC
PocoController.cs
из папки
namespace ControllersAndActions.Controllers
puЫic
class PocoController {
puЫic
string Index () => "This is
а
Controllers,
по-прежнему отыщет их.
РОСО
controller";
Controllers
Глава 17. Контроллеры и действия
Класс
PocoController
удовлетворяет простому критерию. который
MVC
519
ожида­
ет обнаружить в контроллере. Он определяет единственный метод p uЫic по имени
Index (), который будет использоваться как метод действия и который
string.
Класс PocoC o n tr oller является примером контроллера РОСО, где
возвращает
объект
чает
("простой старый объект
"plain olcl CLR object"
CLR")
РОСО озна­
и опирается на тот факт.
что контроллер реализован с применением стандартных средств
.NET без
какой-либо
прямой зависимости от АРl-интерфейса. предоставляемого инфраструктурой
ASP.NET
Core MVC.
Чтобы протестировать контроллер РОСО, запустим приложение и запросим
URL
вида /Рос о/ Index /. Система маршрутизации сопоставит запрос со стандартным шаб­
лоном
URL и
направит запрос методу
r\
locolhost62433/Poco;'lr
С
I ndex ()
класса
PocoControlle r
(рис.
17.1).
Х
(j) localhost ~4 3/Poco/lnd>:x
This is а РОСО controller
Рис.
Использование контроллера РОСО
17. 1.
Применение атрибутов для корректировки идентификации контроллеров
Поддержка контроллеров РОСО не всегда работает желательным образом. Общая проблема
заключается в том, что
MVC
будет идентифицировать в качестве контроллеров фиктивные
классы, созданные для модульного тестирования. Избежать указанной проблемы проще
всего, уделяя внимание именам классов и не допуская имен вроде
FakeContro l l er. Если
NonCont ro l l er, опреде­
ленный в пространстве имен Micr o s o ft .AspNetCore . Mvc , чтобы сообщить MVC о том,
что класс не является контроллером. Имеется также атрибут NonAction, который может
это невозможно, тогда к классу необходимо применять атрибут
применяться к методам, чтобы предотвратить их использование как методов действий.
В ряде проектов может отсутствовать возможность следования соглашению об именовании
для класса , который должен выступать в качестве контроллера РОСО. Инфраструктуре
MVC
можно указать, что класс является контроллером, даже когда он не удовлетворяет критерию
выбора РОСО, применив атрибут
имен
Contro ller,
Microsoft. AspNetCore. Mvc.
который также определен в пространстве
Использование АР/-интерфейса контроллеров инфраструктуры
Класс
PocoControlle r -
троллеров инфраструктурой
MVC
удобная демонстрация способа иде нтификации кон ­
MVC
и возможной реализации простых контроллеров.
Но чистые контроллеры РОСО. не имеющие зависимостей от пространств имен
Microsoft. Aspn e t Core.
средствам. которые
MVC
не особенно полезны, поскольку у них отсутствует доступ к
предлагает для обработки запросов.
Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC 2
520
Доступ к определенным частям АРI-интерфейса
MVC
может осуществляться за счет
создания новых экземпляров Юlассов из пространств имен
Mi c rosot"t . AspnetCore.
В качестве простого примера Ю1асс РОСО может затребовать у
MVC
визуализацию
ViewResu l t из своих методов действий,
ViewResu l t будет рассматриваться в разделе
представления Razoг, возвращая объект
как показано в листинге
17 .8.
(Класс
"Генерирование ответа" далее в главе.)
Листинг
17.8.
Использование АРl-интерфейса
из папки
using
using
using
MVC
в файле
PocoController. cs
Controllers
Мicrosoft. AspNetCore. Mvc;
Мicrosoft.AspNetCore.Mvc . ModelBinding;
Мicrosoft.AspNetCore.Mvc.ViewFeatures;
namespace ControllersAndActions.Controllers
puЫi c
class PocoController {
puhlic ViewResul t Index () => new ViewResul t ()
ViewName
"Result",
ViewData
new ViewDataDictionary(
new EmptyМodelМetadataProvider(),
new ModelStateDictionary()) {
Model = $"This is а РОСО controller"
=
=
}
};
Класс P ocoCo пtroller больше не является чистым контроллером РОСО. т.к. он
имеет прямые зависимости от АРI-интерфейса
MVC.
Однако. оставив в стороне чис ­
тоту, данный Ю1асс намного более полезен , чем Ю1асс из предыд.vщего примера. по ­
Razor. К сожалению,
ViewResul t, необходимо создать объекты
View DataDictionary. EmptyModelMet adataF'rov ide r и ModelSta teDict ionar y.
тому что он запрашивает у МVС визуализацию представления
код стал сложным. Чтобы создать объект
которые требуют доступа к трем разным пространствам имен. (Средства , к которым
относятся упомянутые типы, будут описаны в последующих главах.) Суть рассматри ­
ваемого приме ра
-
демонстрация того . что к средствам. предлагаемым
MVC.
можно
обращаться напрямую. даже если в результате получается некоторая путаница.
17.8 обеспечивают визуализацию
stri ng в качестве модели пред­
ставления. В результате запуска приложения и запрашивания URL вида / F'oco / I пdе х
будет получен ответ. приведенный на рис. 17.2.
Выделенные полужирным изменения в листинге
представления
Resul t. cshtml
D Conltol!~rs •nd ActJons
с применением типа
><
Model Data: Тf1is is а РОСО controller
Рис.
17.2.
Работа с АРl-интерфейсом
MVC
напрямую
Глава 17. Контроллеры и действия
Использование базового класса
521
Con troller
Предшествующие примеры проиллюстрировали. каким образом начать с контрол­
лера РОСО и обеспечить ему доступ к средствам
на то, как работает
MVC:
MVC. Такой
подход проливает свет
это полезное знание на тот случай. если вы обнаружите.
что невольно создаете контроллеры. но контроллеры РОСО неудобны в написании.
восприятии и сопровождении.
Более легкий способ создания контроллеров предусматривает наследование клас­
сов от класса
Microsoft.AspNetCore.Mvc.Control ler .
тоды и свойства, предоставляющие доступ к средствам
нере. Добавим в папку
Controller s
файл класса по имени
с определением контроллера. показанным в листинге
Листинг
17.9.
в котором определены ме­
MVC
в сжатой и удобной ма­
DerivedContro ller. cs
17.9.
Наследование от класса Controller в файле
DerivedController. cs из папки Controllers
using Microsoft.AspNetCore.Mvc;
namespace ControllersAndActions. Controllers
puЫic
class DerivedController : Controller
ViewResult Index() =>
View("Result", $"This is а derived controller");
puЫic
На рис. 17.3 приведен
/ Derived/Index.
результат запуска приложения и запрашивания
D Contto'~rs and Actions
С
URL
вида
Х
ф localhost »2433/Derived/lnd<:!x
Model Data: This is а derived controller
Рис.
17.3.
Контроллер в листинге
Использование базового класса
17.9 делает
(он требует у инфраструктуры
ставления
st.ring).
MVC
Controller
то же самое. что и контроллер в листинге
17.8
визуализировать представление с моделью пред­
но применение базового класса
Cont roller
означает возмож­
Vi ewRe s u 1 t,
требующийся для
ность получения результата более простым путем .
Основное изменение касается того. что объект
визуализации представления
Razor.
можно создать с использованием метода
Vi e w ()
вместо явного создания экземпляра этого класса (и необходимых ему других типов)
прямо в методе действия. Метод
а объект
ViewResul t
View ()
унаследован от базового класса
Cont roller.
по-прежнему создается тем же способом, просто не загромож­
дая метод действия. Наследование от класса
Controlle r
не изменяет манеру работы
контроллеров: оно всего лишь упрощает код , который пишется для решения общих
задач.
522
Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC 2
На заметку! Инфраструктура
MVC
создает новый экземпляр класса контроллера для каждо­
го запроса, который ей предлагается обработать. В итоге не придется синхронизировать
доступ к методам действий или свойствам и полям экземпляра. Разделяемые объекты,
в том числе базы данных и службы-одиночки, которые описаны в главе
18,
могут приме­
няться параллельно и должны быть соответствующим образом реализованы.
Получение данных контекста
Независимо от того. как определяются контроллеры. они редко будут существовать
в изоляции и обычно нуждаются в доступе к данным из входящего запроса, таким
как значения строки запроса. значения формы и параметры, извлеченные из
URL
системой маршрутизации. что все вместе называют данными контекста. Есть три
основных способа доступа к данным контекста:
•
извлечение данных из набора объектов контекста;
•
получение данных как параметра метода действия;
•
явное обращение к средству привязки моделей инфраструктуры.
Здесь мы рассмотрим подходы к получению входных данных для методов дейс­
твий, сосредоточившись на объектах контекста и параметрах методов действий.
Привязка моделей раскрывается в главе
26.
Получение данных из объектов контекста
Одно из главных преимуществ использования базового класса
Controller
для
создания контроллеров заключается в удобном доступе к набору объектов контекста.
которые описывают текущий запрос, подготавливаемый ответ и состояние приложе­
ния. В табл.
17.3
описаны наиболее полезные свойства контекста
Таблица 17.З. Полезные свойства класса
Controller.
Controller для данных контекста
Имя
Описание
Request
Это свойство возвращает объект
HttpRequest,
17.4)
который описывает за­
прос, полученный от клиента (табл.
Response
Это свойство возвращает объект
создания ответа клиенту (табл.
Нt
t pCon tex t
HttpResponse,
17.7)
Это свойство возвращает объект н t
tpCon tex t,
который применяется для
который является источником
многих объектов, возвращаемых другими свойствами, такими как
Response.
Reque s t
и
Оно также предоставляет информацию о доступных возможностях
НТТР и доступ к низкоуровневым средствам наподобие веб-сокетов
RouteData
Это свойство возвращает объект
RouteData,
выпускаемый системой марш­
рутизации, когда имеется совпадение с запросом, как описано в главах
ModelState
15 и 16
Это свойство возвращает объект ModelStateDictionary, который исполь­
зуется для проверки достоверности данных, отправленных клиентом (глава
User
Это свойство возвращает объект
ClaimsPrincipal,
27)
который описывает
пользователя, сделавшего запрос, как объясняется в главах
Многие контроллеры пишутся без применения свойств из табл.
29
17.3.
и
30
потому что
данные контекста доступны через средства. рассматриваемые в последующих главах.
523
Глава 17. Контроллеры и действия
которые больше гармонируют со стилем разработки приложений МVС. Например.
большинство контроллеров не нуждаются в использовании свойства
Request
для
получения деталей обрабатьшаемого НТТР-запроса, поскольку та же самая информация
доступна через процесс привязки модели, который обсуждается в главе
Но свойства, перечисленные в табл.
17
Download
Study collections