Академический Документы
Профессиональный Документы
Культура Документы
6-е издание
Pro
ASP.NET Core MVC
Sixth Edition
Adam Freeman
ASP.NET Core MVC
с примерами на С#
ДЛ Я П Р О Ф Е С С И о· Н А Л О В
6-е издание
Адам Фримен
Фримен, Адам.
Ф88 ASP.NET Core MVC с примерами на С# для профессионалов , 6-е изд. : Пер.
с англ. - СпБ. : ООО "Альфа-книга", 2017. - 992 с. : ил . - Парал. тит. англ .
ISBN 978-5-9908910-4-3 (рус .)
ББК 32.973.26-018.2.75
Научно-популярное издание
Адам Фримен
Об авторе 18
Об авторе
Адам Фримев - опытный специалист в области информационных технологий,
занимавший в едущие позиции во многих компаниях, последней из которых был гло
бальный банк, где он работал на должностях директора по внедрению технологий и
руководителя административной службы. После ухода из банка Адам уделяет все свое
время писательской деятельности и бегу на длинные дистанции .
О техническом рецензенте
Фабио Клау.цио Ферраччати - ведущий консультант и главный аналитик/раз
работчик, исполь зующий технологии Microsoft. Он работает в компании Brain Force
(www . Ыua r anc i o . com). Фабио является сертифицированным Microsoft разработчи
ком реш е ний для .NET (Microsoft Certified Solution Developer for .NET), сертифициро
ванным Microsoft разработчиком приложений для .NET (Microsoft Certified Application
Developer for .NET), сертифицированным Microsoft профессионалом (Microsoft Certifled
Professional), а также плодовитым автором и техническим рецензентом. За посл едние
десять лет он написал множество статей для итальянских и м еждународных журна
лов и выступал в качестве соавтора в более чем 10 книгах по разнообразным темам ,
связанным с 1щмпьютерами.
E-mail: info@dialektika.com
WWW: http : //www . dialektika . com
Введение
в инфраструктуру
ASP.NET Core МVС
д ля разработчиковASP.NET
веб-приложений , использующих платформу Microsoft,
инфраструктура Core MVC представляется как радикальное изме
нен и е . Особое значение придается чистой архитектуре, паттернам проектирова
ния и удобству тестирования , к тому же не предпринимается попытка скрывать,
каким образом работает веб-среда.
ASP.NETWeb Forms
Набор компонентов пользовательского интерфейса (страниц, кнопок и т.д.)
плюс объектно-ориентированная программная модель
графического пользовательского
интерфейса с сохранением состояния
ASP.NET
Способ размещения приложений .NET в llS (веб-сервер Microsoft),
позволяющий взаимодействовать с запросами и ответами НТТР
.NET
Мно гоязычная платформа управляемого кода
(совершенно новая на то время и по праву ставшая поворотным пунктом в развитии)
С технологией Web Forms не все было плохо, и в Microsoft приложили немало уси
лий по улучшению степени соответствия стандартам, упрощению процесса разра
ботки и даже заимствованию ряда возможностей из первоначальной инфраструкту
ры ASP.NET MVC Framework для их применения к Web Forms. Технология Web Forms
превосходна, когда необходимы быстрые результаты, и с ее помощью вполне реально
построить и запустить веб-приложение умеренной сложности всего за один день. Но
если не проявлять осторожность во время разработки , то обнаружится, что созданное
приложение трудно тестировать и сопровождать.
Архитектура MVC
Инфраструктура ASP.NET Core MVC следует паттерну под названием "модель
предсmавление-конmроллер" (model-view-controller - MVC), который управляет фор
мой веб-приложения ASP.NET и взаимодействиями между содержащимися в нем
компонентами.
Расширяемость
Инфр аструктуры ASP.NET Core и ASP.NET Core МVС построены в виде последова
тельности независимых компонентов, которые имеют четко определенные характе
Тестируемость
Естественное разнесение различных обязанностей приложения по независимым
друг от друга частям, которое поддерживается архитектурой ASP.NET Core MVC, поз
воляет с самого начала делать приложение легко сопровождаемым и удобным для
тестирования. Вдобавок каждый фрагмент платформы ASP.NET Core и инфраструк
туры ASP.NET Core МVС может быть изолирован и заменен в целях модульного тес
тирования , которое допускается выполнять с использованием любой популярной ин
фраструктуры тестирования с оп,рытым кодом, такой кан xUnit, рассматриваемой в
главе 7.
В этой книге вы увидите примеры написания ясных и простых модульных тестов
для контроллеров и действий MVC, которые предоставляют фиктивные либо имити
рованные реализации компонентов инфраструктуры для эмуляции любого сценария
с применением разнообразных стратегий тестирования и имитации. Даже если вам
никогда ранее не приходилось создавать модульные тесты, у вас будет все необходи
мое для успешного старта.
Современный АРl-интерфейс
Платформа Microsoft .NET развивалась с каждым крупным выпуском, поддержи
вая - и даже определяя - многие передовые аспекты современного программирова
ния . Инфраструктура ASP.NEТ Core MVC построена для платформы .NET Core, поэто
му ее АРI-интерфейс может в полной мере задействовать последние новшества языка
и исполняющей среды. знакомые программистам на С# , в том числе ключевое слово
awa i t, расширяющие методы, лямбда-выражения, анонимные и динамические типы,
а также язык интегрированных запросов (Language Integrated Query - LINQ) .
Многие методы и паттерны кодирования АРI-интерфейса ASP.NET Core MVC сле
дуют более четкой и выразительной композиции, чем это было возможно в ранних
версиях инфраструктуры . Не переживайте, если вы пока не в курсе последних функ
циональных возможностей языка С#: в главе 4 предоставлена сводка по самым важ
ным средствам С# для разработки приложений МVС .
Межплатформенная природа
Предшествующие версии ASP.NET были специфичными для Windows, требуя на
стольный компьютер с Windows для написания веб-приложений и сервер Windows
для их развертывания и выполнения . Компания Microsoft сделала инфраструктуру
ASP.NEТ Core межплатформенной, как в отношении разработки, так и в плане раз
вертывания . Продукт .NET Core доступен для различных платформ , включая Linux и
OS X/ macOS. и вероятно будет переноситься на другие платформы.
Глава 1. Основы ASP.NET Саге MVC 27
Большая часть разработки приложений ASP.NET Core MVC в ближайшем будущем,
скорее всего, будет выполняться с применением Visual Studio, но компания Microsoft
также создала межплатформенный инструмент разработки под названием Visual
Studio Code, появление которого означает, что разработка ASP.NEТ Core MVC больше
не ограничивается Windows.
https://github .com/aspnet
Резюме
В этой главе был объяснен контекст, в котором существует инфраструктура ASP.NET
Core MVC, и описано ее развитие от Web Forms и первоначальной инфраструктуры
ASP.NET MVC Framework. Кроме того, рассматривались преимущества применения
ASP.NEТ Core МVС и структура этой книги. В следующей главе вы увидите ASP.NET
Core MVC в действии, благодаря простой демонстрации средств, которые проявляют
все ее пр еимущества.
ГЛАВА 2
Ваше первое
приложение МVС
Совет. Среда Visual Studio поддерживает только Windows . Создавать приложения ASP.NET
Core MVC можн о и на других платформах, используя продукт Visual Studio Code, но он
не предоставляет все инструменты, требуемые для выполнения примеров в этой книге.
За подробностями обращайтесь в главу 13.
инструментами. в том числе Bower, который будет описан в главе 6. Во время уста
новки gi t удостоверьтесь, что указываете программе установки на необходимость до
бавления пути к инструменту в перем енную среды РА ТН (рис. 2. 1). Это позволит ср еде
Visual Studio найти новую версию gi t .
~~~-------------~-----------~
...., Git 2.9.0 Setup
------- -------------------
О UR Glt from Git Sash onty
1hlsls lhe sofost choke о. VO<X РАПi ~il notЬe moditied ot <>I. You"111 onlyЬe
.ы. tD.,.. lhe Git СО111М11d line tools rтom Git В."1.
О Use Git ;)hd optional UnЬc tools from the Windows Command Pron1pt
Вoth Git .00 lhe optlonal Ur<x tools ~il Ье odded to VO<X РАПi .
\Yaming: Th1s w\11 oVl!rrlde Wim!ows tooJs /Jke "fmd" and "юrt•. Only
use tl>ls optIOD if VoU Wld<!t'Stand the impical!on5.
Запустите Visual Studio, выберите в меню Tools (Сервис) пункт Options (Параметры)
и перейдите в раздел Projects and SolutionsqExternal Web Tools (Проекты и решения<=>
Внешние веб-инструменты), как показано на рис. 2.2. Снимите отм етку с флажка
$ (VS I NSTALLD I R) \We b \External \ gi t , чтобы блокировать з апуск встроенной в
Visual Studio версии g it, и проверьте , отмечен ли флажок$ (РА Т Н ), чтобы мог при
м еняться только что установленный инструмент gi t .
!
1
SeatckOpьc.-is(Ctrf•E)
~?~:~c·:~ons
.Р lootions of cttrn<tl toot~:
-------,
~@;JwG
1
:B~u.":~l.d;•:in~dR:~un:; ng'i
1
1 &') S(РАТН]
1 ! 0 S(VSJNSTдt lDIR)\Wt b\Exte 1мf\gil
i
1
1
:, 1!
А Prci)tcts • nd Solutions
G1:nu1I
.NЕТ Соц~ P1oje<ts
i
1 '·------------------------........)1
1 !>tt<Nf\'/ф 1ook'
VSOtf;щ lts
! Re5tttoDl!f.щtts j
VC •• Oi rcc l o rit.~ The p•th~ listed obtwewШ Ье s.t11rched when V'кu.гl Studio U1U 3rd·(nrtytcols such IJ
1 VC• ~ P1oj«t Sdtings G1un~ Gulp, 8owtr, cr npm.
•...::.~ P<Oj«~
, _
-···- -~· -·-~· ---·- ... ------ -· · - · - ------- .. -~· ··-··-
1 t> Rtcent ~"!_ЕТ Fr"m~;~ :б~1 - _: . Sort Ьу: \ Dtf-;~ij_--_-- -·-~. ") i~~ :::: ~мchi~7~tit!d "Тtmplal F:j_Ctrl ... E) ~~-!: .-;
i• lnstall<d
@j A5P.NПWe.bApp1icгtion{.NE1 fr~me.work) VisualC• Туре: Visual (#
! • Template~ Project ttmplat6 for crtating ASP.N П
Core "pplic<1tions for Windows, l inux and
... Visual(#
OS Х щing .NE1 Core.
w.ь @ A.SP.NП Core Wtb Applit•tion (.NП fr"mework) Vт~ua/ с~ ' AppfКAtion lnslghts
.NПCore О Add Appf1c<1t1on lnsights to projкt
Android Optimi:tt ptrformt!11Ct 4nd monitor
Cloud us<1Qt in vour li<.•e "PPlit"tion.
bll!ns!Ьil1ty
.os
Reporting
S1fvttlight
Help me unde.r~t""d Applkation lns19hts
Click h(r~ 10 по onl•"( ond f1t'!d 1wRIJ.tg. P11\'iicySt<11ll!.merit
f> Online
Name: P artylnvitм
1
location: ВloWft". 1
l Solutiori ntn,,e:
l-------~~,~=~~
P.-trty!nvite.~ 0 Cre.111e. dlltctory for.solutiol'\
О Add to Source Contro1
,~~~~~Od-L Сап~
Рис. 2.3. Шаблон проекта ASP.NET Core Web Applicatioп (.NET Core) в Visual Studio
Совет. При выборе шаблона проекта может возникать путаница из-за сильно похожих на
званий шаблонов. Шаблон ASP.NET Web Applicatioп (.NET Framework) (Веб-приложение
ASP.NET (.NET Framework)) предназначен для создания проектов с использованием унас
ледованных версий ASP.NET и MVC Framework, которые предшествовали ASP.NET Core .
Остальные два шаблона позволяют создавать приложения ASP.NET Core и отличаются
применяемой исполняющей средой, предлагая на выбор .NЕТ Framework или .NET Core.
Разница между ними объясняется в главе 6, но повсеместно в книге используется вари
ант .NET Core, поэтому для получения идентичных результатов при работе с примерами
приложений вы должны выбирать именно его.
32 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
·1· 11 1
@haпgt'AUt~
1{ _ J Authentication: No Authentlc~tion - - - - -1
------· - ·- - · - 1<;;, MicrcкoftAzu re 1
\G О Host iп thecloud j
~1~.,.-г:з uo ~ I
1
L ~
Для шаблона ASP.NET Core Web Application (.NET Core) доступны три вариант а ,
каждый из которых приводит к созданию проекта с отличающимся начальным содер
жимым. Для целей данной главы выберите вариант Web Application (Веб-приложение),
который настроит приложение MVC с заранее определенным содержимым , чтобы не
медленно приступить к разработке.
На заметку! Это единственная глава, в которой применяется шаблон проекта Web Application.
Мне не нравится пользоваться заранее определенными шаблонами , поскольку они пот
ворствуют интерпретации ряда важных средств наподобие аутентификации как черны х
ящиков . Моя цель в настоящей книге - предоставить вам достаточный объем знаний для
понимания и управления каждым аспектом приложения MVC, так что в оставшихся мате
риалах книги применяется шаблон Empty (Пустой). Текущая глава посвящена быстрому
началу процесса разработки , для чего хорошо подходит шаблон Web Application.
@ No Auth•ntication
О Windows Authentication
г-о~_:] \ Cьncel
Рис. 2.5. Выбор настроек аутентификации
щелкните на кнопке ОК. Среда Visual Studio l4J Solution 'Partylnvites' (1 project}
" ;,,.,. Solution Jtems
скомпилирует приложение, с помощью сервера
/;J global.json
приложений IIS Express запустит его и откро ..; ·~1 src
ет окно веб-браузера для запроса содержимого
приложения. Результат показан на рис. 2.7. ~ ,1- Properties
~ •·• Reference.s
Когда среда Visual Studio создает проект с при
менением шаблона Web Application, она добавля •·• Depe.ndencies
ет базовый код и содержимое, которое вы видите ~ ;.t; Controllers
после запуска приложения. В оставшейся части ~ ~ Vieo.vs
......
&.-l!l<m.Ul,}f)IП',j(~.$4dil
· ~-iti~
• .,. •
/INl"ll'.r"
l'ld-Jh •<J !~/: .w.n.ww
· \.lw~'!/.IU~·~
• -'4.Jpatkog'!'>uPlg tll>O~
· ~
0 (.l~t-"'~•wr.t
'""
• A(l('ltl<lrllpN~u""'JI~ • ri.,.y,4<\p(">(j..!l<>:nr~f'l>,1'.IO'.~
o ljjf\l('f~fl''"'l.ЦloJ""JI)( • r~.tdn"3f"\,"!'l'"°(!t,.'Q,l/N>~"'tл1
ptOd!,ГЬIК\Wtlr.'JIИ'I(
Совет. Вы вовсе не обязаны соблюдать указанное или большинство других соглашений MVC,
но рекомендуется его придерживаться - и не в последнюю очередь потому, что это по
_
l0<a!host:S7628 Х
Рис. 2.9.
-----------·-------
Вывод из метода действия
1
__,
Совет. Обратите внимание, что среда Visual Studio направляет браузер на порт 57628. Внутри
URL, который будет запрашивать ваш браузер, почти наверняка будет присутствовать
другой номер порта, т. к. Visual Studio выделяет произвольный порт при создании проекта .
Если вы заглянете в область уведомлений панели задач Windows, то найдете там значок
для llS Express. Этот значок представляет усеченную версию полного сервера приложе
ний llS, которая входит в состав Visual Studio и используется для доставки содержимого
и служб ASP.NET во время разработки. Развертывание проекта MVC в производственной
среде будет описано в главе 12 .
Понятие маршруто в
В дополнение к моделям , представлениям и контроллерам в приложениях MVC
применяется система маршрутизации ASP.NEТ, которая определяет, как URL отоб
ражаются на контроллеры и действия. Маршрут - это правило, которо е использу
ется для решения о том, как обрабатывать запрос. Когда среда Visual Studio создает
проект MVC, она добавляет ряд стандартных маршрутов , выступающих в качеств е
начальных. Можно запрашивать любой из следующих URL, и они будут направлены
.
на действие
•
/
/Ноте
Inde x класса H oтeC ont r o lle r :
• / H oтe/Index
Таким образом, когда брауз ер запрашивает http : //ваш- сайт/ или http: //ваш_
сайт/ Н от е , он получает вывод из метода I ndex () класса H oтeController. Можете
Глава 2. Ваше первое приложение MVC 37
опробовать зто самостоятельно, изменив URL в браузере. В настоящий момент он бу
дет выглядеть как http: //localhost : 57628/, но представляющая порт часть мо
жет быть другой . Если вы добавите к URL порцию /Home или /Home/Index и нажмете
клавишу <Enter>. то получите от приложения MVC тот же самый результат - строку
" Hello , wo rl d " .
Это хороший пример получения выгоды от соблюдения соглашений, поддержива
емых ASP.NET Core MVC. В данном случае соглашение заключается в том, что имеет
ся контроллер по имени HomeController. который будет служить стартовой точкой
приложения MVC. Стандартная конфигурация. 1<0торую V!sual Stud!o создает для но
вого проекта, предполагает. что мы будем следовать этому соглашению. И поскольку
мы действительно соблюдаем соглашение, мы автоматически получаем поддержку
всех URL из приведенного выше списка. Если не следовать соглашению, то конфигу
рацию пришлось бы модифицировать для указания на контроллер, созданный вза
мен стандартного. В рассматриваемом простом примере стандартной конфигурации
вполне достаточно.
Визуализация веб-страниц
Выводом предыдущего примера была не НТМL-разметка. а просто строка "Hel lo
Wor ld ". Чтобы сгенерировать НТМL-ответ на запрос браузера, понадобится создать
представление, которое сообщает MVC. каким образом генерировать ответ для запро
са, поступившего из браузера.
: 1
---- --··· • 1
1 An Linhandled exception оссшгеd while processing the reqL1est.
lnvaliclOpeгatio'lExcep<ion: Т11е viev1 'MyViev/ v1as 110t fou r.d. The foi/01·1ing !ocations
were searcf1ed:
.f /Views/Home/MyView.cshtinl
ii
1
i Niews/Si1ared/MyVie\\1.CSl-1t •nl
f Eпiun:-su,cessful
f> Online
№mt: MyVi~.csht1n l
Совет. В папке Vi e ws уже есть несколько файлов, которые были добавлены Visual Studio с
целью предоставления начальн ого содержим ого , показанного на рис. 2.7. Можете проиг
норировать эти файлы .
Глава 2. Ваше первое приложение MVC 39
Введите в поле Name (Имя) имя MyVi ew. cshtml и щелкните на кнопке Add
(Доб авить) для создания представления. Среда VisuaJ Studio создаст файл View s/
Home/MyView . c sh tml и откроет его для редактирования. Начальное содержимое
файла представления - это просто ряд комментариев и заполнитель. Замените его
содержимым, приведенным в листинге 2.4.
Совет. Довольно легко создать файл представления не в той папке. Если в итоге вы не по
лучили файл по имени MyView . cshtml в папке Views/Home , тогда удалите созданный
файл и попробуйте создать заново .
@{
Layout = nu ll;
@{
Layout null;
она автоматически выполнила его поиск. Соглашение предполагает, что имя файла
представления совпадает с именем метода действия, а файл представления хранится
в папке, названной по имени контроллера: /Views/ Home/MyView . cshtml .
Кроме строк и объектов ViewResul t методы действий могут возвращать другие ре
зультаты . Например, если мы возвращаем объект RedirectResul t, то браузер будет
перенаправлен на другой URL. Если мы возвращаем объект HttpUnauthorizedResul t ,
то вынуждаем пользователя войти в систему. Все вместе такие объекты называются
результатами действий. Система результатов действий позволяет инкапсулировать
и повторно использовать часто встречающиеся ответы в действиях. В главе 17 мы
рассмотрим их подробнее и продемонстрируем разные способы их применения.
using System;
using Mic r osof t. AspNetCore .Mvc ;
namespace Partyinvites . Contro ll ers
puЫic class HomeCon t roller : Con troller
puЫic ViewResu l t Index() {
int hour =
DateTime.Now.Hour;
ViewBag. Greeting =
hour < 12 ? "Good Morning" "Good Afternoon";
return View( "MyView " ) ;
Глава 2. Ваше первое приложение MVC 41
Данные для представления предоставляются во время присваивания значения
свойству ViewBag . Greeting. Свойство Greeting не существует вплоть до момента,
когда ему присваивается значение - это позволяет передавать данные из контроллера
@{
Layout = null;
<!DOCTYPE html>
<html>
<head>
<meta name =" viewpor t" content="width=device-width" />
<tit l e> I ndex</title>
</head>
<body>
<div>
@ViewBag.Greeting World (from the view)
</div>
</body>
</html>
Предварительная настройка
Представьте себе. что ваша подруга решила организовать вечеринку в канун ново
го года и попросила создать веб-приложение, которое позволяет приглашенным отве
тить на приглашение по электронной почте. Она высказала пожелание относительно
четырех основных средств, которые перечислены ниже:
• форма , которая может использоваться для ответа на приглаш е ние (repoпdez s'il
vous plalt- RSVP);
• проверка достоверности для формы RSVP, которая будет отображать страницу с
выражением благодарности за внимание;
<!DOCTYPE html>
<htm l >
<head>
<meta name="viewport" content= "w i d th=de v ice-w idth" />
<title>Index</title>
</head>
<body>
<div>
@ViewBag .Gr eeting World (from the view)
<p>We' re going to have an exci ting party . <br />
(То do: sell i t better. Add pictures or something.)
</р>
</div>
</body>
</html >
Глава 2. Ваше первое приложение MVC 43
Мы двигаемся в верном направлении. Если запустить приложение, выбрав в меню
Debug пункт Start Debugging. то отобразятся подробности о вечеринке - точнее, за
полнитель для подробностей, но идея должна быть понятной (рис. 2.14).
l
1 \Ve're goiдg to lшУе а11 exc iti.11g party.
(То do' "11 i1 bon« Add piono" 01 •onwd1iug._)______,
На заметку! Вы не сможете установить имя новой папки , если приложение все еще фун
кционирует. Выберите в меню Debug пункт Stop Debugging, щелкните правой кнопкой
мыши на элементе NewFolder , который добавился в окноSolution Explorer, выберите в
контекстном меню пун кт Rename (Переименовать) и измените имя на Mode ls.
Для создания файла масса щелкните правой кнопкой мыши на папке Models в окне
Solution Explorer и выберите в контекстном меню пункт AddqClass (ДобавитьqКласс) .
Введите GuestResponse . cs для имени нового класса и щелкните на кнопке Add
(Добавить). Приведите содержимое нового файла класса к виду , показанному в лис
тинге 2.8.
44 Часть 1. Введение в инфрастру ктуру ASP.NET Core MVC
Совет. Вы могли заметить, что свойство WillAttend имеет тип bool, допускающий null,
т.е. оно может принимать значение true, false или null . Обоснование этого будет
приведено в разделе "Добавление проверки достоверности" далее в главе.
Метод действия RsvpForm () вызывает метод View () без аргументов, что сообща
ет инфраструктуре МVС о необходимости визуализации стандартного пр едставления,
связанного с этим методом действия, которым будет представление с таким же име
нем, как у метода действия (RsvpForm . cshtml в данном случае).
Глава 2. Ваше первое приложение MVC 45
Щелкните правой кнопкой мыши на папке Views внутри Home и выберите в кон
текстном меню пункт Add<=:> New ltem (Добавить<=:> Новый элемент). Выберите шаб
лон MVC View Page (Страница представления MVC) из категории ASP.NET, укажи
те RsvpForm . cshtml в к ачестве имени нового файла и щелкните на кнопке Add
(Добавить), чтобы создать файл. Приведите содержимое нового файла в соответствие
с листингом 2.10.
Листинг 2.10. Содержимое файла RsvpForm. cshtml из папки Views/Home
1· RsvpForm х
г---·-----"·~-·--
~ С Lq:> loca l ho~t 5}628/Home/RsvpFo rm. _ __
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
@ViewBag.Greeting World (from the view)
<p>We're going to have an exciting party.<br />
(То do: sell it better. Add pictures or something.)
</р>
<аasp-action="RsvpForm">RSVP Now</a>
</div>
</body>
</html>
В листинге 2.11 добавлен элемент а. который имеет атрибут asp - action. Данный
атрибут является примером атрибута дескрuпторного вспомогательного класса, т.е .
инструкцией Razor, которая будет вьшолнена, когда представление визуализируется .
Атрибут asp-action - это инструкция по добавлению к элементу а атрибута href,
содержащего URL для метода действия. Работа дескрипторных вспомогательных клас
сов объясняется в главах 24, 25 и 26, а пока достаточно знать, что asp-action - про
стейший вид атрибута дескрипторного вспомогательного класса для элементов а. Он
указывает Razor на необходимость вставки URL для метода действия, определенного в
том же контроллере, для которого визуализируется текушее представление. Запустив
проект, можно увидеть ссылку, которую создал вспомогательный класс (рис. 2.16).
1 lndex
j Rsvpform Х
_____'? i9?~°.~~~~~-;~3__ _
Пu$ is 111~ RsYpF011n.cslitшl \ ' ie\\·
L ~--~- ~----
j'
Рис. 2.16. Ссылка на метод действия
Глава 2. Ваше первое приложение MVC 47
После запуска приложения наведите курсор мыши на ссылку RSVP Now (Ответить
на приглашение) в окне браузера. Вы заметите, что ссылка указывает на следующий
URL (возможно , вашему проекту Visual Studio назначит другой номер порта):
Построение формы
Теперь, когда строго типизированное представление создано и достижимо из представ
ления I ndex, займемся подгонкой содержимого файла RsvpForm . c s html, чтобы превра
тить его в НТМL-форму для редактирования объектов Gue stResponse (листинг 2.12).
Листинг 2.12. Создание представления в виде формы в файле RsvpForm. cshtml
@model Pa r tyinvites . Models . GuestRe sponse
@{
Layout = null ;
<р>
<form method= "post " act i on=" /Home /RsvpForm " >
Как и в случае атрибута дескрипторного вспомогательного класса, примененного
к элементу а, преимущество такого подхода заIUiючается в том, что вы можете изме
Форму можно увидеть, запустив приложение и щел кнув на ссылке RSVP Now
(рис. 2.17).
-------"·--·-·-· ·-----··------------·-"". ~ ~
С @------------------------
localhost:57628/ Home/RsvpForm
Уош 11щnе:
.""."---·-··----·--·;
["."---·--·-·-···"·--·-·--·-_J
Уонr eшail : L ______ ____ _ 1
,_" . __,
·---·--------------·
Рис. 2.17. Добавление НТМL-формы к приложени ю
Глава 2. Ваше первое приложение MVC 49
• Метод, который отвечает н.а НТТР-запросы GE T. Каждый раз, 1<огда кто-то щел
кает на ссьmке, браузер обычно выдает запрос GET. Эта версия действия будет
отвечать за отображение изначально пустой формы , когда ~по-нибудь впервые
посещает / Home/ RsvpFo rm.
• Метод, который отвечает на НТГР-запросы POST. По умолчанию формы, ви
зуализированные с помощью Ht ml. Be g in Form () ,отправляются браузером как
запросы POST. Эта версия действия будет отвечать за получение отправленных
данных и принятие решения о том, что с ними делать.
[HttpGet]
puЫ i c ViewRe s ul t RsvpFo rm () {
r et ur n Vi ew ( ) ;
[HttpPost]
puЬlic ViewResult RsvpForm(GuestResponse guestResponse)
//Что сделать: сохранить ответ от гостя
return View () ;
50 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
Класс Repos i tory и его члены объявлены статическими, чтобы облегчить сохра
нение и извлечение данных из разных мест приложения. Инфраструктура МVС пред
лагает более сложный подход к определению общей функциональности, называемый
внедрением зависимостей, который будет описан в главе 18, но для простого прило
ж е ния вроде рассматриваемого вполне достаточно и статического RЛасса.
Сохранение ответов
Теперь , когда есть куда сохранять данные, можно обновить метод действия, кото
рый получает НТГР-запросы POST (листинг 2.15).
using System;
using Microsoft . AspNetCore . Mvc ;
using Partyinvites .Mode l s;
namespace Partyinvites . Controlle rs
puЫic class HomeContro l ler : Controller
puЫic ViewResult Index() {
int hour = DateTime .Now. Hour ;
ViewBag . Greeting = h our < 12 ? "Good Morning" " Good Afternoon ";
return View ( " MyView " ) ;
[HttpGet]
puЫic ViewResult RsvpForm() {
return View () ;
[HttpPost]
puЫic ViewResult RsvpForm(GuestResponse g uest Response) {
Repository.AddResponse(guestResponse);
return View ( "Thanks", guestResponse) ;
Это может показаться едва заметной разницей, но по мере углубления знаний инфраструктуры
MVC вы увидите, что практика разработки в ней полностью отличается от традиционной инфра
структуры Web Forms и вы всегда осведомлены о том, как обрабатываются получаемые прило
жением запросы .
Вызов метода View () внутри метода действия RsvpForm () сообщает МVС о том,
что нужно визуализировать представление по имени Thanks и передать ему объект
GuestResponse. Чтобы создать это представление, щелкните правой кнопкой мыши
на папке Views/Home в окне Solution Explorer и выберите в контекстном меню пункт
Addc:::>New ltem (Добавить<=:> Новый элемент). Укажите шаблон MVC View Page (Страница
представления MVC) из категории ASP.NET, назначьте ему имя Thanks. cshtml и
щелкните на кнопке Add (Добавить) . Среда Visual Studio создаст файл Views/Home/
Thanks . cshtml и от1<роет его для редактирования. Поместите в файл содержимое,
приведенно е в листинге 2.16.
Листинг 2.16. Содержимое файла Thanks. cshtml из папки Views/Home
@model Partyinvites.Models.GuestResponse
@{
Layout = null ;
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content= "width=device - width " />
<title>Thanks</title>
</head>
<body>
<р>
1
[___ ---- ---- -------------- --------------- ------ _,
Рис. 2.18. Представление Tha n ks
Отображение ответов
В конце представления Th a nks. c sht ml мы добавили элемент а для создания ссыл
ки. которая позволяет отобразить список людей, собирающихся посетить вечеринку .
С применением атрибута дескрипторного вспомогательного класса a s p -acti o n со
здается URL, который нацелен на метод действия по имени Lis t Re s pon ses ():
using System;
using Microsoft.AspNetCore . Mvc ;
using Partyinvites.Models;
using System.Linq;
namespace Partyinvites . Controllers
puЫic class HomeController : Controller
puЫic ViewResult Index() {
int hour = DateTime .Now .H our;
ViewBag. Greeting = hour < 12 ? "Go od Morning " "Good Afternoon ";
return View ( " MyView " ) ;
[HttpGet]
puЫic ViewResult RsvpForm() {
return View();
[HttpPost]
puЫic ViewResult RsvpForm(GuestResponse guestResponse) {
Repository . AddResponse(guestResponse);
return View ( "Thanks", guestResponse) ;
Новый метод действия называется ListResponses ();он вызывает метод View (),
используя свойство Reposi tory. Responses в качестве аргумента. Именно так метод
действия предоставляет данные строго типизированному представлению . Коллекция
объектов GuestResponse фильтруется с применением LINQ, так что используются
только ответы с положительным решением об участии в вечеринке.
Метод действия ListResponses не указывает имя представления, которое долж
но применяться для отображения коллекции объектов GuestResponse, поэтому бу
дет задействовано соглашение об именовании и MVC инициирует поиск представ
ления по имени ListResponses. cshtml в папках Views/Home и Views/Shared .
Чтобы создать представление, щелкните правой кнопкой мыши на папке Views/
Home в окне Solution Explorer и выберите в контекстном меню пункт AddqNew ltem
(ДобавитьQ Новый элемент). Выберите шаблон MVC View Page (Страница представле
ния MVC) из категории ASP.NET, назначьте ему имя ListResponses. cshtml и щелк
ните на кнопке Add (Добавить). Приведите содержимое нового файла представления
в соответствие с листингом 2.18.
Глава 2. Ваше первое прило жение MVC 55
Листинг 2.18. Отображение принятых приглашений в файле ListResponses. cshtrnl
из папки Views/Home
@{
Layout = null;
</tbody>
</tаЫе>
</body>
</html>
1· ReJ: ponses х
E ш a il Phont>
Joe joe,rg. exiuнp le . co 111 555- 1234
Alice al i ce@e.xaшple . coш 555-5678
1
-----·--------·--------------·---__J
Рис. 2. 19. Отображение списка участников вечеринки
using System.Componentмodel.DataAnnotations;
using System;
using Microsoft.AspNetCore.Mvc;
using Partyinvites.Models;
using System.Linq;
namespace Partyinvi tes.Controllers
puЫic class HomeController : Controller
puЫic ViewResult Index() {
int hour = DateTime.Now.Hour;
ViewBag. Greeting = hour < 12 ? " Good Morning" "Good Afternoon";
return View ( "MyView " ) ;
[HttpGet]
puЬlic ViewResul t RsvpForm () {
return View () ;
[HttpPost]
puЫic ViewResult RsvpForm(GuestResponse guestResponse)
if (ModelState.IsValid) {
Repository.AddResponse(guestResponse) ;
return View("Thanks", guestResponse) ;
else {
11 Обнаружена ошибRа проверки достоверности.
return View();
R\Vl)Form х
1L·-·-- · - - · - - - - - - - - -:i
С ф lo.alhost:S7626/Horпe/Rs11pform '(( 1
1· ~ -_.----·-~ __"_ --=~·~-:..=--·---=-·::=--::-:--:---
1
Уощ pl1011e: с_-=~ ·
1
1(Jiiixn;1-RsvEJ
[_______:·==--·--·-·----------·-·--··- ----·--·-----J
Рис. 2.20. Отображение ошибок проверки достоверности
На заметку! Если вы работали с ASP.NET Web Forms, то знаете, что в Web Forms имеется
концепция серверных элементов управления, которые сохраняют состояние, сериализи
руя значения в скрытое поле формы по имени VIEWSTATE. Привязка модели MVC не
имеет никакого отношения к концепциям серверных элементов управления, обратным от
правкам или состоянию представления, принятым в Web Forms. Инфраструктура MVC не
внедряет скрытое поле VIEWSTATE в визуализированные НТМL-страницы. Взамен она
включает данные, устанавливая атрибуты value элементов управления input.
60 Часть 1. Введение в инфраструк туру ASP.NET Core MVC
P· j
r
~ . !:;~: Sort Ьy. Odault
1
.litnt·sidt
Code
1
HTML P•ge C/1cnt ·side
1А
1 (.tS(adir19 ~ty!t
sh<!d. (CS.S)
HT ML\tyledtfinot1oros
u~cd for1i<h
! t> Onli~
1 @j5 TyptS<ript Fi!e. Clttnt·sidt
1
rJ Sowc1 Confi9u11tion f 1lc Clltnt-1idc
1
f N.:iu1~ J
Q d"d-) o;,-;;-i j
L --- - __ J
Совет. Среда Visual Studio создает файл style . css в папке wwwroot/css при созда
нии проекта с использованием шаблона Web Application. В этой главе данный файл не
задействован.
Назначьте файлу имя styl es . css, щелкните на кнопке Add (Добавить), чтобы со
здать файл таблицы стилей, и приведите его содержимое к виду, показанному в лис
тинге 2.22.
<head>
<meta name="viewport " content= "width=device-width" />
<title>RsvpForm</title>
<link rel="stylesheet" href="/css/styles.css" />
</head>
Стилизация содержимого
Все цели приложения , касающиеся функциональности, достигнуты , но его общий
вид оставляет желать лучшего. Когда вы создаете проект с использованием шаблона
Web Application, как в текущем примере , Visual Studio устанавливает несколько рас
пространенных пакетов для разработки на стороне клиента. Хотя я не являюсь сто
Rsvpioim Х
С
,-- ---- -·-- - ·------·- -----·- . -
LФ locJ!lюst:S7628/НQ111e/RsvpForm
- -
. ~- ... --~ -. ___ ._.._."-;-::- ~- _.., ·-~~:-·- --···- - -::·-:·;::::-.:-- ... ·::--..::.::
• Plta8• 1.>ntft' ~·out· t>m.;iiJ addi'\'и
• Р1Е-а~.- e11ftr :;1·ощ· phoot' numbtr
• Pl•:a~• \pc.>('if·y " 'htther ~·ou 'U atttod
1
--- -"
!
Yo\U' eшail: 1
....- - - - - =:J 1
Your pl1oue: 1
1 [ suьn~li'RsiiP]
1
L____,___________.__________ J
Рис. 2.22. Автоматическая подсветка полей с ошибками проверки достоверности
На заметку! На момент написания этой книги текущей версией была Bootstrap 3, но версия 4
находилась на стадии разработки. В Microsoft могут принять решение обновить версию
Bootstrap, используемую шаблоном Web Application, в последующих выпусках Visual
Studio, что может привести к отображению содержимого по-другому. В других главах кни
ги это не должно стать проблемой, т.к. там будет показано, каким образом явно указать
версию пакета , чтобы получить ожидаемые результаты .
@{
Layout = null;
lndex х
С !. ф localhost:57628
1' .~
.
YourpJюne:
!
1
j
1
W1ll you attend? j
·-
Choose an opuon
1
1 1
!
1
1
1
___ j
L . -----~-·-~------·-----·-----··-·-- · - - - - -
<!DOCTYPE html>
<h tml>
<hea d >
<meta n ame= "viewp ort " content= " width=device - width " />
<title>Thanks</title>
<link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" />
</head>
66 Часть 1. Введение в и нфрас тр уктуру ASP.NET Core MVC
<body class="text-center">
<р>
- - - - - - - - - - - - - - - - - - - - - - - - - - - --J
@{
Layout = null ;
<!DOCTYPE html>
<html>
<head>
<meta name= "viewport " content= "widt h=device- widt h" />
<link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.css" />
<tit l e>Responses</t itl e>
</head>
<body>
<div class="panel-body">
<h2>Here is the l ist of people attending the party</h2>
Глава 2. Ваше nервое nриложение MVC 67
<tаЫе class="taЫe taЫe-sm taЫe-striped taЫe-Ьordered">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
</tr>
</ the ad>
<tbody>
@foreach (Partyinvites .Models . GuestResponse r in Mode l) {
<tr>
<td>@r.Name</td>
<td>@r . Email</td>
<td>@r . Phone</td>
</tr>
</tbody>
</tаЫе>
</div>
</body>
</html>
На рис . 2.26 показано, как теперь выглядит таблица участников . Добавление сти
л ей к данному представлению завершает пример приложения , которое теперь достиг
ло всех целей разработки и имеет намного более совершенный внешний вид.
i f- i
1[~ Here is the list of people attending the party
1
1
!
1 Name Email Phone
Резюме
В этой главе был создан новый проект МVС , который использовался для построе
ния простого приложения ввода данных МVС, что позволило получить первое пред
ставление об архитектуре ASP.NET Core MVC и применяемом подходе. Некоторые
о сн о вные ср едства (включая синтаксис Razor, маршрутизацию и тестирование) не
рассматривались, но мы вернемся к этим темам в последующих главах. В следующей
глав е будет описаны паттерны проектирования MVC, которые формируют основу эф
фективной разр аботки с помощью ASP.NET Core МVС .
ГЛАВА 3
Паттерн МVС,
проекты и соглашения
п режде чемCoreприступить
ASP.NEТ MVC,
к углубленному изучению деталей инфраструктуры
необходимо освоить паттерн проектирования МVС, лежащие
в его основе концепции и способ, которым они транслируются в проекты ASP.NET Core
MVC. Возможно, вы уже знакомы с некоторыми идеями и соглашениями, обсуждае
мыми в этой главе, особенно если вам приходилось заниматься разработкой сложных
приложений ASP.NET или С#. Если это не так, тогда внимательно читайте настоящую
главу. Хорошее понимание того, что положено в основу MVC, может помочь увязать
функциональные возможности инфраструктуры с контекстом материала, излагаемо
го в оставшихся главах книги.
Понятие моделей
Модели (Мв МVС) содержат данные, с которыми работают пользователи. Сущест
вуют два обширных типа моделей: модели представлений, которые выражают сами
данные, передаваемые из контроллера в представление. и модели предметной облас
ти, которые содержат данные в предметной области наряду с операциями. транс
формациями и правилами для создания, хранения и манипулирования данными, все
вместе называемыми логикой моделей.
Модели - это определение "вселенной", в которой функционирует приложение.
Например. в банковском приложении модель представляет все аспекты банковской
деятельности, поддерживаемые приложением, такие как расчетные счета, главная
бухгалтерская книга и кредитные лимиты для клиентов. равно как и операции, ко
торые могут применяться для манипулирования данными в модели. подобные вне
сению денежных средств и списанию их со счетов . Модель отвечает также за сохра
нение общего состояния и целостности данных - например, удостоверяясь, что все
транзакции внесены в главную книгу, а клиент не снимает со счета больше денеж
ных средств, чем имеет на то право, или больше. чем находится в распоряжении
самого банка.
Для каждого компонента в паттерне MVC будет описано, что он должен включать,
а что не должен.
Модель не должна:
в том, что целью паттерна MVC является отделение данны х от логики . Это заблуждение:
цель паттерна MVC - разделение приложения на три функциональных области, каждая
из которых может содержать и логику, и данные. Цель не в том, чтобы устранить логику
из модели. Наоборот, цель в том, чтобы гарантировать наличие в модели только логики,
предназначенной для создания и управления данными модели.
Понятие контроллеров
Контроллеры являются "соединительной тканью " паттерна MVC, исполняя роль
каналов между моделью данных и представлениями. Контроллеры определяют дейс
твия. предоставляющие бизнес-логику, которая оперирует на модели данных и обес
печивает представления данными, подлежащими отображению для пользователя.
Контроллер, построенный с применением паттерна MVC, должен:
Понятие представлений
Представления содержат логику, которая требуется для отображения данных поль
зователю или для сбора данных от пользователя. так что они могут быть обработаны
каким-то действием контроллера.
Представления должны:
Представления не должны:
метной области.
НТТР
Запрос ------------~ _• Постоянство
дено их сравнение с МVС . Одни паттерны будут близкими вариациями на тему MVC,
в то время как другие - совершенно отличаться.
терфейсом на том или ином этапе своей профессиональной деятельности - меня это
определенно касается. Если вы использовали Windows Forms или ASP.NET Web Forms,
то сказанное относится и к вам.
72 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
__ ._ Постоянство
Запрос ~ Интеллектуальный
(обычно с помощью
пользовательский
реляционной
Ответ интерфейс
- - базы данных)
Архитектура "модель-представление"
В приложении с интеллектуальным пользовательским интерфейсом проблемы со
провождения обычно возникают в области бизнес-логики. которая настолько рассеяна
по приложению, что внесение изменений или добавление новых функций становится
мучительным процессом. Улучшить ситуацию помогает архитектура "модель-пред
ставление'', которая выносит бизнес-логику в отдельную модель предметной облас
ти. Все данные, процессы и правила концентрируются в одной части приложения
(рис. 3.3).
Запрос
_ ._ Постоянство
Пользовательский 1-------1~
(обычно с помощt
интерфейс Модель
реляционной
Ответ (представление) - - базы данных)
Постоянство
Запрос Пользовательский 1 - - - - . - i Уровень - - •(обычно
интерфейс Модель доступа с помощью
ложением и паттерном МVС. Ра зница в том, что когда уровень пользовательского ин
терфейса напрямую связан с инфраструктурой графического пользовательского ин
терфейса типа "щелчок-событие" (такой как Windows Forms или ASP.NET Web Forms),
то выполнение автоматизированных модульных тестов становится практически не
Разновидности MVC
Основные принципы проектирования приложений МVС были уже описаны, осо
бенно те из них, которые применимы к реализации ASP.NET Core МVС. Другие реа
лизации интерпретируют аспекты паттерна MVC иначе, дополняя, подстраивая или
ка~<-то еще адаптируя его для соответствия области охвата и целям своих проектов .
В последующих разделах мы кратко рассмотрим две наиболее распространенных ва
риации на тему MVC. Понимание этих разновидностей не имеет особого значения
в случае работы с ASP.NET Саге MVC. Данная информация включена ради полноты
картины, потому что такие термины будут употребляться в большинстве обсуждений
паттернов проектирования ПО .
Паттерн "модель·nредставление-презентатор"
Создание проекта
Когда вы впервые создаете новый проект ASP.NET Core, то имеете три базовых от
правных точки. из которых можно выбирать: шаблон Empty (Пустой), шаблон Web API
и шаблон Web Application (Веб-приложение), как показано на рис. 3.5.
r---------------------.,
Stftct а tempfote:
~OK~,, ~J
~-5:_rvice --]
.l - Solutionlttm:
• At<Ounl
IJ tJ!r>b•l.J~Ofl , 5:1. ..,,,~ @1 Cofll11mEm.11l.c\htmt
t> J- P109ff11ts !!!) (ictvnaltoginConfirm!bon.c1h!ml с- b1(m1llog1nCo"Г.rmtti0nVir ...Mod!!I.«
•• Rtfrrtncн с.• for9otP1s~rcМrwModtl.н
8 Ь1:ttl\lllogin FJИu1c.cshtml
}' PIOp('lties ." -V.~001 ~for9ctPшwaid.cshtml
<• loginVie1.~.'\odrl.cs
tJ t.\of\<hStШn91.j1on !:> • ·• DrprndtN:iti В ForgotP1иwordCo r1firmation .c1'11ml
с• Rrgist rt'lreY<Modd.c s
..r.- Co nt10Jlt." 8 Locl:out.,shtml
с• Rt1t!Pauwo1dVitwМodrl.{~
~ -·1001 с• Acc"untCont1olltr.cs t• !.tndCodrV-IC!'WМodrl . п
В login.rshtml
• • Otptnckntit:s t" HomeControllrцs с- \lf1ify(ode\'~Modt1.ts
[!!) д.f9•Sttr.cshtml
Со Pн19rem.cs с• М.n19rCon11ollcr.c~ • ;,,..· M1rцg rV1rwModrh
@) P.tu,1P1mwo1d.c.111tml
.l rJp1ojcrtJи111 t • AddPhontNumbr!VitwModr Lu
n pro;нtJocli:.j~Qn
• "" D1t1 0 Rt~ttP,шwo1dConfirn1•tion.csh tml
• ....• м.g1111cns
В SrndCodt.uhtml
с- 01n9eP1нwo1dVlrwModrl .< 1
.D P•ojt(t_Rt•dm~html t> с- OOOOOOOOOOOOOO.Crt1trldf.nlit)·St
8 'hrifyCodc.c1html
с• C onfigurrTwo F1ct oJV1e1.~ModrJ.c 1
С• St1r11tp.H С" Applк.11tionObCon!txtModf.!Srit С" f1ctcrV1ewMQdrl.ci
• loo.· Homt
у wtb.c~fig с- Applic.t1ionDbContm.cs с- lnduVitwMode!.ts
/!) AЬouttthtml
ь illl! t-.tud~lt ео J. l..,..9d.vyi11}Vif.'WМ0Ucl.~ }
@j Cont1ct.0Nml
• Strvicн G) lndu.cshlml С* Rtm0Vtlog1nVi,...Modrl,CJ
Р l[m1i1Srndt1.o с• SdP•иwoнfVitwf"1odt~C1
;..· M1"1gr
t• ISmsSt"dr:цs С" Vt•ily?hontNumbr<V1ewModt!.c.t
@1 AddPhontN11mЬtt.cshtml
с• Meщ9tScnAct,.ci С" Applktlil)r!Ultf.U
121 Ctш19rPt~nчo1d.<Ui1ml
Jt 81 Vi~
0 lndu.иhl ml
lJ 1ppsrtting1j10" @l№rцgtlogi n,.<thtml
61 Ьundlteonfig .jso n В SftP•1swo1d. нhtml
с- P1ogr1m.c1 0 VtrifyPhc~umbtr.csh1ml
" IJ project.j1or1 • ....- Sh"rd
.!~' Projr:cLR"dmc.html е .tl)'O\ILt~htmt
e< S1artup .cs В .L oginP11rti•l.ohtm! /> • ir"•9н
f) wtt. conl19 В .V11l1d1t1onXript~Patti11Ltshtml /1 $ j'
[!) (rfor.cshtml • .... r1 ь
@l .Viewimportм~htnil " "1i boot1t1J p
@} .ViewScer1.c1html !:> ~ j qutty
1> О jqUt"'f·v.tГod1li11n·unoЫru1N't
!J _1dtrtncn.j~
f1viton .ico
На заметку! Все папки и файлы, описанные в табл . 3.1, находятся в папке s r c , которая
является местоположением, где Visual Studio создает проект ASP.NET Core MVC внутри
решения .
/Views/ _Viewimport s . cshtml Этот файл применяется для указания пространств имен,
которые будут включены в файлы представлений Razor, как
объясняется в главе 5. Он также используется для установ
ки дескрипторных вспомогательных классов (глава 23)
/Views / ViewStart . cshtml Этот файл позволяет указать стандартную компоновку для
механизма визуализации Razor, как описано в главе 5
/bower. j son По умолчанию этот файл скрыт. Он содержит список паке
тов , управляемых диспетчером пакетов Bower (глава 6)
/p r o j ect. j son В этом файле указаны базовые конфигурационные пара
метры для проекта , в том числе применяемые им пакеты
NuGet (глава 6)
/Prog r am . cs Этот класс конфигурирует платформу, на которой размеща
ется приложение (глава 14)
/Startup . cs Этот класс конфигурирует само приложение (глава 14)
/wwwroot Сюда помещается статическое содержимое, такое как фай
лы CSS и изображений. Кроме того, именно сюда диспет
чер пакетов Bower устанавливает пакеты JavaScript и CSS
(глава 6)
Совет. Все соглашения могут быть изменены путем замены стандартных компонентов MVC
собственными реализациями . В книге будут описаны различные способы делать это , что
поможет объяснить, как работают приложения MVC, но с рассматриваемыми здесь согла
шениями придется иметь дело в большинстве проектов.
80 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
вспомогательного класса, указывается первая часть имени (такая как Product), а ин
фраструктура MVC автоматически добавляет к этому имени слово Controller и на
чинает поиск класса контроллера.
Совет. Это поведение можно изменить, создав соглашение для моделей, как будет описано
в главе 31.
Совет. Обратите внимание, что часть Controller имени класса в имени папки внутри Views
не указывается, т.е. используется /Views/Product, а не /Views/ProductController.
Поначалу такой подход может показаться нелогичным, но он быстро войдет в привычку.
return View() ;
return View("MyOtherView");
@{
Layout = "-1 _ MyLayout. cshtml";
@{
La yout = null;
Резюме
В этой главе был представлен архитектурный паттерн МVС и его сравнение с не
Сl{Олькими другими паттернами, с которыми вы могли сталкиваться или слышать о
них ранее. В следующей главе объясняется структура проектов MVC в Visual Studio и
рассматриваются важнейшие средства языка С#, которые используются при разра
ботке веб-приложений МVС.
ГЛА В А 4
Важные
функциональные
возможности языка С #
! !; Recent ··1 - -
" S.Ort Ьу: ~efau!t_
·- -
__ ".
." .::::
! ~ - · ~::J
-· -· - -
~ ~,;_c!:'_J~sblled !:m!:!~ts.\~ tr/"fJ.
·-
i
~-·!r" !f\rt.Jllr:d
ij ASP.NП \Чf!Ь Application (.NЕТ Fran\~ork} VisualC: Туре: Vi1ui1I с;
iOS
Reporting
Silverlight
Help ntt undtrs~nd Appl1cttio.n lr1s!ghts
\ ~ Onliм Pri11;,cy Stat~.ment
1
Locatiorn \ Br~:;:J 1
Solutionnгme Li11n9 u:g~Fe0Jtur~ ., _ _ C.reatedl1eф;iryforsolutlon'
1 S•l«t • lemplate:
с - ~- ~--- -----~- -----------"'--- ----1 An empty p,oject template fo r cr9tin9 an ASP.NП Co re 1
1 j1 AS P.NEТ Core Tempiates 1 app!kэtion. This templi!!te does not have any content in
it.
~ 1 ~
'
111 ij
1 Web AP I \'leb ,. 1
I Application
1
1 1, 1 _________ l
j j l~~~~-~~~)-t~~:.~~~~
lf дрр S~~~--·:·:
1
'-'--·---~·-·j
1
L ----·--·---------------- - - - - ·---···-------------------DC1~ 1
1
__________ J
В разделе dependencies файла proj ect. j son перечислены сборки, которые тре
буются для про екта. Мы добавили сборку Microsoft. AspNetCore. Mvc, содержащую
классы МVС. Обратите внимание на добавление запятой в конце строки, предшес
твующей Microsoft . AspNetCore . Mvc. Файлы конфигурации JSON чувствительны
к корректному форматированию, а о добавлении запятой легко забыть и тем самым
вызвать ошибку.
Совет. Каждая сборка указывается с номером версии. Вы должны удостовериться , что все
сборки с указанными версиями нормально работают вместе . Во время редактирования
файла proj ect. j son среда Visual Studio предоставит списо к доступны х верси й сборок .
Простейший подход заключается в том, чтобы указанная вами версия для Microsoft .
AspNetCore . Mvc совпадала с версией существующих сборо к в разделе dependenc ies,
к оторый был добавлен Visual Studio, когда создавался проект.
Создание модели
Н ачн ем с создания простого класса модели, чтобы иметь какие-то данные , с кото
ры м и можно работать. Добавьте папку по имени Models и создайте в ней файл класса
Product . cs с определением, приведенным в листинге 4.3.
@{ Layout = null;
< ! DOC TYPE html >
<html>
<head>
<meta name =" v iewp o rt " co nt e nt= " width=devi ce - width " / >
<t itl e >La ng ua ge Features</ t it l e>
</head>
<body>
<u l >
@foreach (str ing s in Mode l ) {
<li >@s</ l i>
</ul>
</body>
</html >
Запустив пример приложения путем выбора пункта Start Debugg ing (Запустит ь от
ладку) в меню Debug (Отладка) , вы увидите вывод , предст авленный на рис . 4.3.
Глава 4. Важные функциональные возможности языка С# 87
D language features Х
____? ~athost:sз._29_0 _ __
. с~
"
• La11guage
• Feanю~s
_J
Рис. 4.3. Запуск пример приложения
С#
Language
Features
return View(results);
88 Часть 1. Введение в инфраструктуру ASP.NET Саге MVC
kayak.Related = lifejacket;
return new Product[] ( kayak, lifejacket, null };
Каждый объект Product имеет свойство Related, которое может ссылаться на дру
гой объект Product . В методе GetProducts () мы устанавливаем свойство Related
Глава 4. Ва ж ные фун к циональные возмо ж ности языка С# 89
для объекта Produ c t , представляющего каяк . В листинге 4.8 показано, как можно
со единить вместе nu ll-условные опер ации для навигации по свойствам объектов. не
вы з ывая исключ е ние .
namespace LanguageFeatures.Controllers {
puЬl i c class HomeController : Con tr oller
return View(results);
Использование автоматически
реализуемых свойств
В языке С# поддерживаются автоматически реализуемые свойства, которые мы
применяли при определении свойств класса Person в предыдущем разделе:
kayak.Related = l ifejacket ;
return new Product[] { kayak , l ifejacket , null } ;
но в листинге 4 . 12.
Листинг 4.12. Присваивание значения свойству только для чтения в файле Product. cs
resul ts . Add ( string. Format ( "Name: {О}, Price: { 1}, Related: { 2}",
name, price, relatedName) ) ;
return View(results) ;
Использование инициализаторов
объектов и коллекций
При создании объекта в статическом методе GetProducts () класса Product при
менялся иниц,иализатор объекта. который позволяет создавать объект и указывать
значения его свойств за один шаг, например:
Это еще одна форма "синтаксического сахара", делающая язык С# легче в исполь
зовании. Без такого средства пришлось бы вызывать конструктор Product и затем
применять вновь созданный объект для установки всех его свойств:
Инициализатор коллекции дает возм ожность ука з ать с одержимое массива как
часть е го конструирования , что неявно предоставля ет компилятору ра зме р м ассива
(листинг 4.15).
вы з ов а м етода. Код в листинге 4.15 дает тот же р е зультю~ что и код в листинге 4.14,
и если вы запустит е пример приложения, то получите в окне браузера следующий
вывод :
ВоЬ
Joe
Alice
96 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
};
return View("Index", products.Keys);
Total : $323 . 95
щих этот интерф ейс . В листинге 4.21 прив еден код кл асса ShoppingCart, модифици
рованный для реализации интерфейса I E nume r aЫ e<P r oduct>.
IEnumerator IEnumeraЬle.GetEnumerator()
return GetEnumerator();
using System.Collections.Generic;
namespac e Language Fea tures . Mo de l s
puЫic static c l a s s MyEx t ens i o nMethod s
puЫic static decimal TotalPrices (this IEnumeraЫe<Product> products)
decimal total = О ;
fo r each (Product pr od in pr od u c ts)
total += prod?.Price ?? О;
return total ;
тип первого параметра был изменен на I EnumeraЫe<Pro du ct>, а это значит, что
цикл fo r e a ch в теле метода работает непосредственно с объектами Prod uct . Переход
н а использование упомянутого интерфейса означает, что мы мож ем подсчитать об
щую стоимость объектов Produ ct , перечисляемых посредством любого интерфейса
IEnumeraЫe<Produc t > . что включает не только экз емпляры Shoppi ngCart , но также
массивы объе ктов Produ c t (листинг 4.23).
return total ;
Использование лямбда-выражений
Лямбда-выражения - это средство, которое служит причиной многочисленных
заблуждений и не в последнюю очередь из-за того, что упрощаемое им средство само
вызывает путаницу. Чтобы понять решаемую задачу, рассмотрим расширяющий ме
тод FilterByPrice (), который был определен в предыдущем разделе. Метод реали
зован так, что он может фильтровать объекты Product по цене, а это значит. что если
вы захотите фильтровать объекты по названию, то вам придется создать второй ме
тод наподобие приведенного в листинге 4.26.
return total ;
102 Часть 1. Введение в инфраструктуру ASP. NET Соге MVC
П ервый фильтр отбирает все товары с ценой $20 и выше , а второй фильтр - то
в ары с названиям и , н а чинающимися н а букву S. После запуска прим ер а п рил о жен ия
вы ув идите в окн е браузера сл едующий вывод :
bool FilterByPrice(Product р) {
return (p?.Price ?? 0) >= 20;
Kayak
Lifejacket
В окне браузера также будет присутствовать пустой элемент списка, потому что
метод GetProducts () включает в свой результат ссылку null, но в настоящем разде
ле главы это неважно.
namespace LanguageFeatures.Models {
puЫic class Product {
puЫic Product(bool stock = true)
InStock = stock ;
Использование автоматического
выведения типа и анонимных типов
Ключевое слово var языка С# позволяет определять локальную п еременную без
явного указания ее типа, как показано в л истинге 4.34. Такой прием называется
выведением типа или неявной типизацией.
Речь идет вовсе не о том, что переменная myVar ia Ыe не имеет типа; мы всего
лишь предложили компилятору самостоятельно вывести тип из кода. Компилятор ис
следует объявление массива и решает, что он является строковым. Выполнение при
мера дает следующий вывод:
Kayak
Li fejacket
Soccer ball
Kayak
Lifejacket
Soccer ball
Corner f lag
Компилятор С# генерирует класс на основе имени и типов параметров в инициа
лизаторе. Два анонимно типизированных объекта, которые имеют те же самые имена
и типы свойств, будут относиться к одному и тому же автоматически сгенерированно
му классу. В результате все объекты в массиве products получат один и тот же тип,
поскольку они определяют те же самые свойства.
Глава 4. Важ ные функциональные возмож ности языка С# 109
Всем объектам в массиве был назначен один и тот же тип, что можно увидеть ,
запустив пример. Имя типа не выглядит дружественным к пользователю, но оно и
не рассчитано на непосредственное использ ование, к тому же в вашем случае может
оказаться другим:
<>f~AnonymousType0'2
<>f~A nonymousType0 ' 2
<>f~AnonymousType0 ' 2
<>f~AnonymousTy pe0' 2
носительно того, как запросы планируются и выполняются. Для вьmолн ения работы
асинхронным образом используются два ключевых слова С# - async и await.
Для целей данного раздела в пример проекта понадобится добавить новую сбор1\у
.NET. чтобы можно было делать асинхронные НТТР-запросы. В листинге 4.37 показа
но добавление, произведенное в разделе dependenci es файла proj ect. j son.
"dependencies ":
"Microsoft . NETCore.App" :
" ve r sion ": " 1 . О . 0 ",
" type " : " platform "
}'
"M icrosoft . AspNetCore . Diagnostics ": " 1. О. О",
"Mi crosoft .AspNetCore.Serve r.I ISintegrat ion": "1 . 0 . О ",
"Mi crosoft.AspNetCore .Server.Kestrel ": "1. 0 . 0 ",
"Microsoft.Extens i ons . Logg ing .Conso l e ": "1. 0.О ",
"M icrosoft.AspNetCore . Mvc ": "1 . 0 .О",
"System.Net.Http": "4 .1. 0"
}'
После сохранения файла pro j ect. j son среда Visual Studio загрузит сборку System.
Net. Http и добавит ее в проект. В главе 6 процесс будет описан более подробно.
Совет. Когда мы употребляем понятия вроде фоновый режим, то опускаем массу деталей,
чтобы подчеркнуть только ключевые аспекты, которые важны для мираMVC . Поддержка
.NET для асинхронных методов и параллельного программирования в целом превосход
на, и ее рекомендуется внимательно изучить, если вы хотите создавать по-настоящему
Обратите внимание , что ключевое слово r e turn встречается два раза. Именно
эта часть вызывает путаницу . Первое применение ключевого слова return указыва
ет, что во звращается объект Ta s k<Http Re spons eMessage> , который при завершении
задачи возвратит (второе ключевое слово r e t u r n) длину из заголовка Conte n t Le ng th.
Заголовок ContentLength возвращает р е зультат long ? (тип long , допускающий зна
чения nul l), т.е . результатом метода Ge tPage Length () является Ta s k< l ong?> :
Ключевое слово awai t используется при вызове асинхронного метода. Оно сооб
щает компилятору С# о том, что необходимо подождать результата Task, который
возвращается методом GetAsync (), и затем заняться выполнением остальных опе
раторов в том же методе.
Тип результата метода действия Index () изменен на Task<ViewResul t>. Это со
общает MVC о том, что метод действия будет возвращать объект Tas k, который по
завершении выдаст объект ViewResul t, а тот предоставит детали представления,
подлежащего визуализации, и требующиеся ему данные . К определению метода было
добавлено ключевое слово async, что позволило использовать 1wючевое слово awai t
при вызове метода MyAsyncMethods. GetPathLength (). О продолжениях позаботят
ся инфраструктура MVC и платформа .NET, а результатом будет код, который легко
писать, читать и сопровождать. Запустив приложение, вы увидите вывод, похожий
на показанный ниже (хотя наверняка с отличающейся длиной, т.к. содержимое веб
сайта Apress часто изменяется):
Length : 62164
Получение имен
Во время разработки веб-приложений есть много задач, в которых необходимо
ссылаться на имя аргумента , переменной, метода или класса. Распространенными
примерами являются ситуации с генерированием исключения или созданием ошибки
проверки достоверности при обработке пользовательского ввода. Традиционный под
ход предполагал применение строкового значения с жестко закодированным именем
using Microsoft.AspNetCore.Mvc;
using System . Collections . Generic ;
using LanguageFeatures . Models ;
using System ;
using System . Linq ;
namespace LanguageFeatures.Contro l lers
puЬlic class HomeC ontroller Contro l ler
puЫic ViewResult Index() {
var products = new [] {
new { Name " Kayak ", Price = 275М ),
new { Name "Lifejacket ", Price = 48.95М } ,
new { Name "S occer ball" , Price 1 9 . SOM } ,
new { Name "Corner flag ", Price = 34.95М )
};
return View(products.Select(p =>
$" {nameof (p.Name)}: {p.Name}, {nameof (р. Price)}: {р. Price} "));
кользнуть от глаз.
Резюме
В настоящей главе был дан обзор основных языковых средств С#, которые дол
жен знать результативный программист приложений MVC. Язык С# является гибким
в достаточной мере для того , чтобы обычно существовало несколько способов р еше
ния любой задачи, но есть средства, с которыми вы будете чаще всего встречаться во
время разработки веб-приложений и видеть повсюду в при ме рах , рассматриваемых в
данной книге. В следующей главе будет представлен механизм визуализации Razor и
приведены объяснения , как его использовать для генерации динамического содержи
мого в веб-приложениях MVC.
ГЛАВА 5
Работа с Razor
Вопрос Ответ
Существуют ли какие Выражения Razor могут содержать почти любые операторы С#. Иногда
то скрытые ловушки может быть трудно решить, должна ли логика принадлежать представ
или ограничения? лению или контроллеру, что способно разрушить принцип разделения
обязанностей, который является центральным в паттерне MVC
Доступ к модели представления Используйте выражение @Model для опре 5.6, 5.15,
деления типа модели и выражение @rnodel 5.18
для доступа к объекту модели
ми модели представления
11
dependenci е s 11 : {
11
Microsoft . NETCore.App 11 :
11
version 11 : 11 1 . 0.0 11 ,
11
type 11 : 11 platform 11
},
11
Mi crosoft . AspNetCore.Diagnostics 11 :
11
1.0.0 11 ,
11
Microsoft .AspNetCore.Server.IISintegration 11 : 11 1 . 0 . 0 11 ,
11
Microsoft.AspNetCore . Server . Kestrel ": "1.0 . 0 ",
11
Microsoft .Ex tensions.Logging .Console 11 : " 1.0.0 11 ,
"Microsoft . AspNetCore.Mvc 11 :
11
1.0.0 11
},
Глава 5. Работа с Razor 117
После сохранения изменений , внесенных в pr oj ect . j son, среда Visual Studio
добавит в проект сборку Microsoft. AspNetCo re. Mvc . Далее мы включаем инфра
структуру MVC с ее стандартной конфигурацией в файле Startup . cs (листинг 5.2).
Определение модели
Затем мы созда ем папку Models и добавляем в нее файл класса по имени
Product . cs с приведенным в листинге 5.3 определением простого класса модели.
Создание контроллера
Стандартная конфигурация, установленная в файле Startup . cs , следует соглаше
нию MVC относительно отправки запросов контроллеру по имени Ноте по умолчанию.
Мы создали папку Controllers и добавили в нее файл класса HomeController. cs ,
поместив в него простое определение контроллера (листинг 5.4).
118 Часть 1. Введение в инфрастру ктуру ASP.NET Core MVC
Создание представления
Чтобы создать стандартное представление для метода действия Index () , мы со
здаем папку Views/ Home и добавляем в нее файл типа MVC View Page (Стр аница пр ед
ставления MVC) по имени Index . cs html, куда помещаем содержимое, показанное в
листинге 5.5.
[J lndex
1--~ С [ф localhost:60753
L o11tent \vill go 11е1·е - - - - - -
Листинг 5.6. Ссылка на свойство объекта модели представления в файле Index. cshtml
@mode l Razor.Models.Product
@{
Layout = null;
На заметку! Обратите внимание, что тип объекта модели представления объявляется с ис
пользованием @model (со строчной буквой m), а доступ к свойству Name производится с
применением @Model (с прописной буквой М). Поначалу зто может немного запутывать,
но со временем станет вполне привычным .
[j lndox х
~
-------·· ' . . ·-·-- -
Kayak
---· ·------.·~------·--·-·--··~-·---·-w-·--w-·--·---*-"•-...,•-•
Razor - 1:1 Х
"' ---~
,,, Price
-" ProductlD
,Ф . ToS.tring
Рис. 5.3. Среда Visual Studio предлагает список предполагаемых имен членов
на основе выражения @Model
. 1· , r.
FJ<head)
l <rneta nan;e ="vie1~port " conten t " "1~idtl1=device-widt h " />
l <title>I nd ex</title>
</head>
Э <Ьоdу>
@f'tode l . ~e~y,,
· l </body>
</html>
~...; ,..," ".~, ....
~
.м. ~
r
Рис. 5.4. Среда Visual Studio сообщает о проблеме с выражением @Mode l
ASP.NEТ
• Тур~: ASP.N~
MVC View l •yout Page
Clitnt·side
MVC Vie\v lmpc
Code
MVC у;..,, St4rt Page ASP.NEТ t
t> Online
J'!
·.
ASP . NП Ccnfiguration File ASP .NEТ
Листинг 5.8. Ссылка на класс модели без пространства имен в файле Index. cshtml
@model Product
@{
Layout = null ;
Совет. Выражение @using м ожн о также до бавлять в отдельные файлы представ л ений, ч то
позволит применять внутри них типы без указания пр о ст ранства имен.
Работа с компоновками
Ниже приведено еще одно важное выражение Razor из файла представления
Index. cshtml:
@{
Layout null;
Это пример блока кода Razor, который позволяет включать в представление опе
раторы С#. Блок кода открывается посредством @{ и закрывается с помощью }, а
содержащиеся в нем операторы оцениваются при визуализации представления .
Создание компоновки
Компоновки обычно совместно используются представлениями , применяемыми
множеством контроллеров, и хранятся в папке по имени Views/Shared, которая
входит в перечень местоположений, просматриваемых Razor в попытках найти файл.
Чтобы создать компоновку. создайте папку Views/Shared, щешшите на ней правой
кнопкой мыши и выберите в контекстном меню пункт AddФNew ltem (ДобавитьФНовый
элемент). Укажите шаблон MVC View Layout Page (Страница компоновки представле
ний МVС) в категории ASP.NET и введите _BasicLayo ut . cshtml в качестве имени
файла (рис. 5.6). Щелкните на кнопке Add (Добавить) для создания файла. (Подобно
файлам импортирования представлений имена файлов компоновок начинаются с
символа подчеркивания .)
В листинге 5.9 показано начальное содержимое файла _Basi c Layout . cshtml, до
бавленное средой Visual Studio при создании файла.
124 Часть! . Введение в инфраструктуру ASP.N ET Саге MVC
~·
. МVС View lmpor1s Poge ASP.NET
~·
fl; Ritzor Tag H ~ l pe r ASP.NET
"'
Cljclc here to go onlioe itnd find tempi<!t es.
_Bo sicl"yout.csl,tml
Здесь был добавлен элемент заголовка, а также стиль CSS для стилизации со
держимого элемента di v, в котором находится выражение @RenderBody (), прос
то ради прояснения, какое содержимое поступает из компоновки, а KaJ{Oe - из
представления.
Применение компоновки
Чтобы применить компоновку к представлению, понадобится установить значение
свойства Layou t и удалить НТМL-разметку, которая теперь будет предоставляться
компоновкой, такую как элементы html, head и body (листинг 5.11).
1 P1·oduct Information
Client~side
MVC View l"yout Page ASP.NEТ
. Тур е: ASP.N ET
MVC View St•rt Page
Code
~ Online
ji" MVC View lmports Page ASP.NEO
#
~·
t.; Razo r Т119 Helper ASP.NET
~·
,5
Middleware Class ASP.NEТ
~
~·
t.; Startup class ASP.NEТ
№me: _ViewStart.cshtml
@model Product
@{
ViewBag . Ti t le = " Pr oduct Name";
Внимание! Важно понимать разницу между отсутствием свойства Layout в файле пред
ставления и его установкой в nu ll. Если представление является самодостаточным, и
вы не хотите применять компоновку, то установите свойство Layout в n ul l. Если же
просто опустить свойство Layou t , то инфраструктура MVC будет считать, что компоновка
вам необходима, и она должна использовать значение, которое найдет в файле запуска
представления.
ViewBag. StockLevel = 2;
return View(myProduct);
Знание того, когда применять ViewBag, а когда расширять модель - вопрос опы
та и сложившихся предпочтений. Мой персональный стиль заключается в том, что
бы использовать объект ViewBag только для предоставления визуальных подсказок
о способе визуализации данных и не применять его для значений данных, которые
отображаются пользователю. Но зто просто то, что подходит лично мне. Если вы хо
тите использовать ViewBag для данных, отображаемых пользователю, тогда обращай
тесь к значениям с помощью выражения @ViewBag (листинг 5.17).
~ Product N"m~ Х
1 P1·oduct I11fo1·matio11
1
1 ::::::::~~е2 $275 00 1
<div data-productid="@Model.ProductID"
data-stocklevel="@ViewBag.StockLevel">
<p>Product Name : @Model . Name</p>
<p>Product Pr i ce : @($ "{ Model . Price : C2 }" )</p>
<p>Stock Level : @ViewBag . StockLevel</p>
</div>
Выраж е ния Razor пр и меняются для установки знач е ний некот орых атрибутов
данных в эл ементе di v.
Запустив прим е р приложения и прос м отрев НТМL-разм етку , отправл е нную бр ау
зе ру, вы увидите, что механизм Razor установил значения атрибутов :
< /р >
</ div>
Совет. Обратите внимание, что для использования внутри оператора sw i tch значение
свойства Vi ewBag . ProductCount должно быть приведено к типу int. Причина в том,
что Rаzоr-выра жение @sw i tch не может оценивать динамическое свойство - необходи
мо приведение к специфичному типу, чтобы было известно , как выполнять сравнения.
Внутри блока кода Razor в вывод представления можно включать НТМL- элементы и
значения данных , просто определяя НТМL-разметку и выражения Razor, например :
@: Out of Stock
С) ProdU<t N""' Х
1- С (~ l~~~~~t~60i_S3 -==--=·=-=--·=--==='---~
1 P1·oduct Info1·mation
1
1 Prodнct Nаше: Kayak
1 P1·od.нct PI"ice: $275.00
Рис.
l_- - --------------------------------
5. 1о. Применение оператора sw i tch в представлении Razor
Этот условный оператор выдает те же самые результаты, что и оператор swi tch ,
но мы просто хотели проде монстрировать применение условных операторов С# в
представлениях Razor. В главе 21, где представления рассматриваются более подроб
но, будут даны объясн ения, как все это работает.
@mode l Product[J
@{
ViewBag .T it l e "P roduct Name ";
<taЬle>
<thead>
<tr><th>Name</th><th>Price</th></tr>
</thead>
<tЬody>
@foreach (Product р in Model)
<tr>
<td>@p.Name</td>
<td>@($"{p.Price:C2}")</td>
</tr>
}
</tЬody>
</taЬle>
. С Гф lo-ca-111-os-t.60
- 75-=3=====
-- -- - ~-==---=----=:-:-_-:::::-::;::_-:::;:::::::-_-:-::::::::-:..-._-________- - - -
Product Info1·mation
Name Price
Kayak $275.00
Litej acket $48.95
Soccer ball $ 19.50
Согпе1· flag $34.95
----------------
Рис. 5.11. Применение Razor для прохода по массиву
Резюме
В этой главе был предложен обзор механизма визуализации Razor и показано, как
его использовать для генерации НТМL-разметки. Мы взглянули, каким образом ссы
латься на данные, передаваемые из контроллера, через объект модели представления
и объект Vi ewBag, а также продемонстрировали применение выражений Razor для
настройки ответов пользователю на основе значений данных. В оставшихся главах
книги вы увидите много разных примеров использования Razor, а в главе 21 найдете
подробное обсуждение функционирования механизма визуализации МVС. В следую
щей главе будут описаны некоторые средства, предлагаемые Visual Studio для работы
с про ектами ASP.NET Core MVC .
ГЛАВА 6
Работа с Visual Studio
в
этой главе рассматриваются основные средства, предоставляемые Visual Studio
для разработки проектов ASP.NET Core MVC . В табл. 6.1 приведена сводка по
главе.
Добавление к проекту пакетов Создайте файл bower. j son и добавь 6.7, 6.8
JavaScript или CSS те требующиеся пакеты в его раздел
dependencies
Просмотр результатов изменения Используйте модель итеративной 6.9-6.11
представления или класса разработки
приложения
"dependenc i es " : {
"Microsoft . NETCore . App":
" version ": 11
1 . 0.0 ",
"type ": "platform "
}'
"Mi c r osoft . AspNetCo r e . Diagnostics": "1. О . О ",
},
puЫic void Configu r e (IAppl i cati onBuilder ар р, IHost ingEnv i ro nment env ,
ILoggerFact ory l oggerFact ory) {
app.UseMvcWithDefaultRoute();
Созда н ие м одел и
Со здайте папку Mode l s и добавьте в нее файл класса по имени Product . cs с оп
редел е ни ем, показанным в листинге 6.3.
138 Часть 1. Введение в инфраструктуру ASP. NЕТ Саге MVC
Класс SimpleReposi tory хранит объекты модели в памяти, т.е. любые внесенные
в модель изменения утрачиваются, когда приложение останавлива ется или переза
Зд е сь имеется един ственный метод действия Index () ,который получает все объ
екты модели и передает их методу Vi ew () для визуализации стандартного представ
ле ния. Чтобы добавить это пр едставление, создайте папку Views/Home и поместите
в не е файл представления по имени I n dex . csh tml, содержимое которого приведено
в ли стинге 6 .6.
Листинг 6.6. Содержимое файла Index. cshtml из папки Views/Home
@model IE n u rne r aЫe< W o r k i ngW i t hVisu a l S tud i o.M od els. Prod u c t >
@{ Layout = null ;
< !DOCTYPE htrnl>
<html>
<head>
<meta name= "v iewport " c ont ent=" width=device - wi dth " />
<title>Working with Vis ual St udi o</t i t le >
</head>
<body>
<tаЫе>
<thead>
<tr><td>Narne</td><td >P r ice </td></ t r>
</thead>
<tbody>
@fo r each (var р i n Mode l )
<tr>
<td>@ p.Narne</td>
<td>@p . Pr i ce</td>
</tr>
</tbody>
</tаЫе>
</body>
</html>
--·- ----------------·1
С l.~~~~~ost:612~-------
-- ------ -------· ---···----·--~
----------~- ~- -~
Nаще Price
Kayak 275
Lifejacket 48.95
Soccer ba ll 19.50
Сошеr flag 34.95
В краткосрочной перспективе выбор исполняющей среды для проектов будет управляться ис
пользуемыми инструментами и библиотеками . У независимых поставщиков уйдет некоторое
время на обновление своего программного обеспечения для работы с .NET Core и доведение
его до уровней стабильности, требуемых для производственного применения. Если вы зависи
те от пакета либо инструмента, которому необходима полная платформа .NET Framework (или
у вас есть унаследованная кодовая база, обновить которую невозможно), тогда при создании
проектов ASP.NEТ вам придется использовать вариант ASP.NET Core Web Application (.NET
Framework). Вы по-преж нему можете применять все средства, описанные в настоящей книге, и
единственное отличие будет связано с исполняющей средой, которая запускает код.
Тем не менее, будущим ASP. NEТ является среда .NET Core. Это вовсе не означает, что вы должны
немедленно перевести на нее существующие проекты, но означает, что вам не следует создавать
новые зависимости от .NET Framework, если их можно избежать, и при выборе новых инструмен
тов и библиотек понадобится учитывать возможность поддержки ими .NET Core. Дополнительные
сведения о .NЕТ Core доступны по адресу https: / /docs .rnicrosoft. corn/en-us/
dotnet/articles/welcorne.
Глава 6. Работа с Visual Studio 141
Инструмент NuGet
Среда Visual Studio предоставляет графический инструмент для управления паке
тами .NET, который включается в проект. Чтобы открыть этот инструмент, выберите в
меню Tools~NuGet Package Manager (Сервис~Диспетчер пакетов NuGet) пункт Manage
NuGet Packages for Solution (Управл ение пакетами NuGet для решения) . Инструмент
NuGet откроется и отобразит список уже установленных пакетов (рис. 6.2).
Vщlon(,) · 1
j
5
di.1gn"'1k~ 1nfo11Ntion. Jrn:lvdes devtloptr U<tp!!On P'!i" m1ddJtw~te, tнq}tiOl'I h•n~.
. 10
' 0
P1ojtct :_---~-:i
Ut\Vlor1cincJт'MhVi~u 1.0.О 1
1
18
~
Mlcrosoft.AJpNe1Core.S.rvtr.llSlntegration.Tools ЬуМЮ> v!Л.O·prlE'М"NHin11
llS 1rittg1.1tion yublish t.:io~ for .NfТ Cort ClL Cant11n' tht dctntl·puhll,h·
------- __ :J1
lllt1~•1t 111 comn11rn:1 lc1 pЩ:lnking wdi 1ppl1c.1Ucrn !о bt hoottd ustng ltS. lм t.llled: 1 .!1.О
~ д tti cf .NET lJll"~ INI olt' mduded ln the dt!1i.rlt .NП Cort f IK411on modrL dyntmit .... 11ь1rtei 1r.d wtb AP! ~. ASP . NH (Ot( мvс "
Другие разделы файла proj ect. j son будут описаны в главе 14, но если вы вни
мательно изучите список пакетов, отображаемый инструментом NuGet, то заметите,
что он соответствует элементам раздела dependencies, которые для примера проек
та выглядят следующим образом:
11
dependen cies 11
: {
"version "1.0.0",
11
:
"type": "platform"
}'
"Microsoft . AspNetCore . Diagnostics ": "1.0.0 11
,
Microsoft . AspNetCore.Mvc
11
1.0.0" 11
:
11
} '
Каждый пакет указывается с использованием его имени и обязательного номера
версии. Некоторые пакеты, такие как Microsoft . NetCore . Арр в примере про екта,
имеют дополнительную конфигурационную информацию, как объясняется в главе 14.
Ср еда Visual Studio отслеживает содержимое файла proj ect . j son, т.е. вы можете до
бавлять или удалять пакеты , редактируя файл напрямую, что и делается повсеместно
в книге , т.к. это помогает гарантировать получение ожидаемых результатов , если вы
прорабатываете примеры.
Когда вы применяете NuGet для добавления в проект какого-то пакета, он автома
тически устанавливается наряду со всеми пакетами, от которых зависит. Исследовать
пакеты NuGet и их зависимости можно, открыв в окне Solution Explorer элемент
References (Ссылки), который содержит записи для всех пакетов NuGet в файле
proj ect . j son. В результате развертывания записи пакета отображаются пакеты, от
которых он зависит (рис . 6.3).
Solution Explorer • CJ х
Инструмент Bower
Пакет клиентской стороны включает содержимое, которое отправляется клиенту ,
такое как файлы JavaScript, стили CSS либо изображения . Для управления этими
пакетами можно привычно пользоваться инструментом NuGet, но ASP.NEТ Core MVC
полагается на новый инструмент под названием Bower. Инструмент с открытым ко
дом Bower был разработан за пределами Microsoft и мира .NET и широко применялся
в разработке веб-приложений, отличных от ASP.NET. В действительности Bower стал
настолько успешным , что некоторые популярные пакеты клиентской стороны рас
пространялись только через Bower.
Sort Ь у: Def•ult
ASP.NET • Туре: Client-side
TypeScript JSON Configur•tion File Client-sido
(lient-side
Bower Configuration
Code and .bo1.;verrc .
~ Online
n рП1 Co11figuration File С lie n\ · •ide
гJs
JSXFile Client-side
<iiil>J
(lick here to go opline •nd find !empla\б.
Совет. По умолчанию Visual Studio скрывает файл b owe r . j s on; чтобы его было видно, нуж
но щелкнуть на кнопке Show All Files (Показать все файлы) в верхней части окна Solution
Explorer.
Совет. Хранилище пакетов Bower находится по адресу h ttp: //bower . io/ sea r ch, где
можно искать пакеты для добавления в свои проекты.
NuGet • Solution •
Schema: http://json.schem astore.org/ bower
в{ - - -
\ "narne" : "asp . net" ,
"p1· ivate" : t г ue ,
1
8 "dependen<ies" : {
/ "bootstrap" : " ~
f} } ! f..-3.-3.б----./ т~e lot~t ~i~Ы•v•rsion-;r th• ~.~k•g•
" ш л з.З . б
~ - 3.З . б
Таблица 6.2. Ра спространенные форматы для номеров версий в файле Ьower. j son
Формат Описание
3.3.6 Выраже ние номера версии напрямую приведет к установке пакета с точно
совпадающим номером версии, например , 3.3. 6
>3 . 3 . 6 Снабжение номера версии префиксом > или >= позволяет инструменту
>=3 . 3 . 6 Bower устанавливать любую версию пакета, которая больше либо больше или
равна заданной версии
<3.3.6 Снабжение номера версии префиксом < или <= позволяет инструменту
<=3 . 3 .6 Bower устанавливать любую версию пакета, которая меньше либо меньше
или равна заданной версии
Совет. Для примеров в настоящей книге мы создали и отредактировали файл bower . j son
напрямую . Редактировать файл очень просто , к тому ж е это способствует получению
предсказуемых результатов в случае проработки примеров. Среда Visual Studio также
предоставляет графический инструмент для управления пакетами Bower, который можно
открыть, щелкнув правой кнопкой мыши на проекте WorkingWithVisualStudio в окне Solution
Explorer (родительский элемент файла b ower. j son ) и выбрав в контекстном меню пункт
Manage Bower Packages (Управление пакетами Bower) .
1> bootstrap
t> jquery
"'1:1 х
Перед самым выпуском ASP.NET Саге 1.0 в Microsoft внесли изменение в ядро и упомянутые инс
трументы больше не задействуются автоматически в шаблонах проектов MVC. Одна из наиболее
распространенных задач , где использовался инструмент Gulp , теперь выполняется расширением
Visual Studio, которое описано в разделе " Подготовка файлов JavaScript и CSS для развертыва
ния " далее в главе .
Среда Visual Studio по-прежнему поддерживает инструменты NPM и Gulp, и они все еще могут
применяться для проектов, которые имеют дело со сложным компонентом клиентской стороны .
Это может быть полезно, поскольку существуют удобные инструменты и пакеты , которые доступ
ны толь ко через NPM и могут настраиваться исключительно с использованием Gulp. За деталями
обращайтесь в мою книгу Pro Client Development for ASP.NEТ Саге MVC Developers.
Глава 6. Работа с Visual Studio 147
Итеративная разработка
Разработка веб-приложений часто может быть итеративным процессом, когда вы
вносите небольшие изменения в представления или классы и запускаете приложение,
чтобы протестировать результат. Инфраструктура МVС и среда Visual Studio на пару
поддерживают такой итеративный подход, делая наблюдение за влиянием изменений
быстрым и легким.
@rnodel IEnurneraЬle<WorkingWithVisualStudio.Models.Product>
@{ Layout = null; }
< ! DOCTYPE html>
<htrnl>
<head>
<meta name =" v i ewport " content="width=device- width " />
<title>Wo rking with Vi s ual Studio</title>
</head>
<body>
<hЗ>Products</hЗ>
<tаЫе>
<thead>
<tr><td>Name</td><td> Pri ce</ td> </ tr>
</thead>
<tbody>
@foreach (var р in Model)
<tr>
<td>@p .Name</td>
<td>@($"{p.Price:C2}")</td>
</tr>
}
</tbody>
</tаЫе>
</body>
</html>
С !. <D localhost.:61207 j : 1
- - - - --==-----==-------===--·--::::::::......·-·-!
*
P1·od11cts !
Nаше Price
Lifejacket S48.95
Soccer ball S19.50
С ошеr flag $34.95 1
1
L --- ·---·--__)
1
11 С [Ф localhost:6120:__ _ _ _ _ _ _ _~ :
----· ---·· - ---------- -- -----·---·------··-- -~-
P1·oducts
Nаше Price
1
Lifejacket $48.95
Soccer ball $19.50
Соше.1' flag S34.95
'------------------------
Рис . 6.9.
_J
Автоматическая компиляция классов
Средство автоматической компиляции удобно, когда все идет по плану . Его недо
статок в том, что ошибки этапа компиляции и времени выполнения отображаются в
браузере, а не в Visual Studio, затрудняя выяснение причины возникновения пробле
мы . В качестве примера в листинге 6.11 демонстрируется добавление ссылки null в
коллекцию объектов моделей внутри хранилища.
Средство InteШSense среды Visual Studio будет подсвечивать места в коде, где при
сутствуют проблемы с синтаксисом. но проблема вроде ссьmки null не будет обнару
жена вплоть до запуска приложения. Перезагрузка страницы в браузере приведет к
тому, что класс SimpleReposi tory скомпилируется, а приложение запустится зано
во. Когда инфраструктура МVС создает экземпляр класса контроллера для обработки
НТГР-запроса от браузера, конструктор HomeController создаст экземпляр класса
SimpleReposi tory, 1шторый в свою очередь попытается обработать ссылку null,
добавленную в листинге 6.11.
Ссылка null приведет к возникновению проблемы, но сущность проблемы не будет
очевидной, потому что браузер не отобразит какое-то полезное сообщение (а браузер
Chrome вообще не выведет никаких сообщений. отобразив взамен пустую вкладку).
f/ cv~№xt
~l o·,el·J ext
L
1r, Index . cs ht ml
··--·--···---:!
17 . @foгeac h (va 1· р in Hodel) (
-~-~~·---·· -------·
Рис . 6.1 О. Страница исключения разработчи ка
Использование отладчика
Среда Visual Studio также поддерживает выполнение приложения MVC с примене
нием отладчика, который позволяет останавливать приложение для инспектирования
его состояния и пути прохождения запроса по коду. Это требует другого стиля раз
работки , поскольку модификации классов С# не будут применены до тех пор, пока
приложение не запустится заново (хотя изменения, вносимые в представления Razor,
по-прежнему вступают в силу автоматически) .
152 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
Ho meCont r oПtr.cs ~ Х
~\Vo;.kin9WrthVi$u11IStu~~~p-p,Version_:l~ \4/01ki~~i!_~ViwalStud_i~~~~~m~~~!.~.----·~-----·-,.:
B u ~ing ~lic"osoft.д!ipfle-t'Core.Мvc ; li'.
using Worki n gWithVis ualStudio .М<Юtl::o ; r,--
. ....,.....,.._.,,,...~--:-""::..,,......,,.., __~ -~.-.----;i
l using Syste•.li nq; /-1.'..-~~~.!!;~ «-~c,epttono<tut~-· _.·_. ---~---21
j &:ctpdon thtown: "Systtm.Nul\Refe1ence.f.'щ1ption' in 1
1 89 n1и1esp\1Ьli
pace Wo гkingWithVi:иJ 1йStudi o . Cont rollers {
c class 11-)!l'eCм·uol.t~r : Contrclle { /
,/ l Wo ikingW! tliViм'5 tud'ю.dlr
1
J /' / Additi"onal lnfo~tion: OЬjr.ct rr.ferencl! oot sd to an inst.sn<.t of an objC!.ct.'.
puЬlic !1\tticnRc!.iJlt Index(} · -· -·- --"-------. ...,.~- --~ - -- - ..... ··-- ·• -·
•> Vitw( 51ro:rlt'1'to;>cs 1 tor}·· Sh!l':'C!~R!Po~J.~y.Products ! ~~~:~~-
· - - - - - - - - - - - - ----··-··--
. lihere(p •> P. · Pric~ < S!) ); / f.bШ.!.1?..~~!f!1i!'.!A.!.ti~..o..ЧLts.ti.t!'...Yl!.~.!to!! HM!~9Jht..~.~~:;.!bl ~
1
t} 1
. Ustt lhe • nt O\'м kt)'lf'.Old tc ctt<!tt dn cbjкt 1 nst •nt~
•
'1
lJ_~_!t'~~~~~lp fo~-:~~~.~~~-·---···-·----~···--··-=-~
1
возобновить выполнение.
Глава 6. Работа с Visual Studio 153
Чтобы создать точку останова. щелкните правой кнопкой мыши на операто
ре кода и выберите в контекстном меню пункт Breakpointqln se rt Breakpoint (Точка
остановаqВставить точку останова). В целях демонстрации поместите точку останова
на метод AddProduct (} в классе SimpleReposi tory (рис. 6.12).
new
Product { tlame .- "Li fe jacket" 1 Pric e • 48.95М },
11ew P r od tн:t { N am~ • "Soc.ce1· ball" , Price'"' 19.50!'1 } ,
P rodщ:t { fJame • ' ' C oгner flag" , Ргiсе '"' 34.9SМ }
1
~ 1
};
1 foreach (var р in initi.s lit ems ) {
AddProduct(p) ;
ll,
puЫic П1\ul!'. eг aЫ~ < Product > Products "'> products . Vii!l lues ;
100 8.'7 ..
Рис. 6.12. Создание точки о стано ва
В этот момент вы можете применять пункты меню Debug среды Visual Studio или
элементы управления в верхней части окна для управления выполнением приложения
либо использовать разнообразные представления отладчика, доступные через меню
DebugqWindows (ОтладкаqОкна), чтобы инспектировать состояние приложения.
Simpl•Repos~ory. cs
IOJ" w~rkingWit hVis~11IStu dio .. NETCoreApp.Version " ~ Wo~ki~gWitl1VisuolStudio.Modei~:SimpleReposi . ...
nl!\'l Pl"'Qduct { Name • "Corner flag " , Price • 34.9S.Ч }
};
foreaich (vl!lr р in initi alitems ) {
+ AddProd uc1:( p); '"s-,,_-p----{W-or-ki-
n9-l~-it-
hV-is-u•-IS-tu-d-io-.M-o-de-ls-.P-ro-d-uc-.t}
1
pr-od ucts .Add( "Err ci r " 1 rшl l) ; 1" p.Name Р '" "Koyak"
#11 p.P rice 275
G~ р {WorkingWithVisualStudio.Models.Product}
JI p.Name Р " "lifejacket"
/1 p.Price 48.9S
Рис. 6.15. Наблюдение за изменением состояния
с применением за к реплен н ых значений
locals • 1:1 х
; Туре_
1
Name Value __ • _ ··-
" oi this {WorkingWithVisualStudio.Models.SimpleRepository) WorkingWithVisualStudio.Models.SimpleRepos
" 1' Producl< Count = 2 Syste m.Colfecticns.Generic .I EnumeraЫe<Work
~ oi 10) {WorkingWithVisualStudio.Models.Product) WorkingWitl1VisualStudio.Models.Product
~ oi [1] {\"lorkingWithVisualStudio.Mod<Ols.Product} Wo rkingWithVisualStudio.Models.Product
~ oi Raw Vie\v
~ !M!!fi!!.ijЩi!!Wi Sy;tem.Colfectrons.Generic.D1ct1щшy<5tring, i
~ "1: Static members
{WorkingWithVisualStudio.Models.Product}
" " р WorkingWit hVisualStudio.Model<.Product
1' Nan10 · soccer Ьа11 · Q. • string
1' Price 19.SO decimal
Nаше Price
Lifejackcr $48. 95
Socce1· bal\ $19.50
Листинг 6.14. Добавление сборки Browser Link в файле proj ect. j son
"dependencies ": {
"Microsoft .N ETCore.App ":
"version ": " 1 . 0 . О ",
" type ": "platform "
}'
"Microsoft . AspNetCore.Diagnostics ": " 1 . 0 . 0 ",
Глава 6. Работа с Visual Studio 157
"Microsoft .AspNetCore.Server . IISintegration ": "1 . 0 . О" ,
"Microsoft . AspNetCore . Server. Kes trel " 1 . О. О
11
:
11
,
<tr><td>Lifejacket</td><td>$48 . 95</td></tr>
<tr><td>Soccer ball</td><td>$19 . 50</td></tr>
<tr><td>Corner flag</td><td>$34 . 95</td></tr>
</tbody>
</tаЫе>
< ! - - Visual Studio Browser Link -->
<script type="application/json"
id=" browserLink initializationData">
- -
{"requestid":"9eOOcбfB05Bf436981Be7ba315c9bdde",
"requestмappingFromServer" : false}
</script>
<script type="text/javascript"
src="http://localhost:56147/e7bB5fe070c5419Ba04ld57c363cee40/browserLink"
async="async"></script>
< ! -- End Browser Link -->
</ body>
</html >
Среда Visual Studio добавляет в НТМL-разметку , посылаемую бр аузеру, п а ру эле
ментов script , которые применяются для открытия долговечного НТГР-подключения
к серверу приложений, чтобы ср еда Visual Studio могла обеспечить принудител ьную
перезагрузку страницы браузером. (Если вы не видите элементы script, тогда удос
товерьтесь в том, что отмечен пункт ЕnаЫе Browser Link (Включить Browser Link) в
меню, показанн ом на рис. 6. 18.) В листинге 6.16 приведе но изменение, внесенное в
представл ение Index, которое проиллюстрирует результат использования средства
Browser Link.
р t:! х
.1ninistrator)
"ools Test Дnalyze Window Help Adam Freeman • 11
Any CPU . ~ ' • llS Express • ·~~ • '[ ·ff ~-;j[__~ i:f!.L'~"~J~_!_.:!.:i! .:L .
....____. G Refresh Linked Browsers ~ Ctrl+Alt.-Enter .
. S~@} BromerlinkOashboard
, ~ ЕnаЫе Browser Link
1" ЕnаЫе CSS Auto·Sync
"
- -- -"...,.---...
= Solution ltems
61 global.json
</tbody>
</tаЫе>
</body>
</html>
llSExpr~s
./ 115 Express
WorkingWithVi.sualStudio
[---· --···---]
......----
Set as Default
.;,.~~
Opera lnternet Browser
Ptogram:
Среда Visual Studio ищет общие браузеры во время процесса установки, но с по
мощью кнопки Add (Добавить) вы можете указывать браузеры, которые не были об
наружены автоматически. Вы можете также настроить инструменты тестирования от
независимых поставщиков наподобие Browser Stack, которые запускают браузеры на
расположенных в облаке виртуальных машинах, так что вам не придется управлять
крупной матрицей операционных систем и браузеров во время тестирования.
На рис 6.20 выбраны три браузера: Chrome, Internet Explorer и Edge. Щелчок на
кнопке Browse (Обзор) приводит к запуску всех трех браузеров и заrрузки в них URL
примера приложения (рис . 6.21).
Чтобы посмотреть , какими браузерами управляет средство Browser Link, выберите
пункт Browser Link Dashboard (Инструментальная панель Browser Link) в меню средс
тва Browser Link; откроется окно Browser Link Dashboard (Инструментальная панель
Browser Link) , представленное на рис. 6.22. Инструментальная панель показывает
URL, отображаемый каждым браузером , и позволяет обновлять каждый браузер по
отдельности.
Глава 6. Работа с Visual Studio 161
I ~· -) () 1 ~
l0<.1 ••::_ •i} ..O:..~~~~ait1~~~~~18~==~:::.~-=--=---=-~ S
P1·oducts 1
R•questTime: 17:5 1:26 Pi·odurts
Name Price
1 Nasne Price 1 ReqнestT11110 : 1 7 : 5 1 .26
Lifejacket !:48.95 11
"dependencies ": {
"Mic ros oft . NETCore . App ":
"version ": " 1 . 0 . 0 ",
" type ": "platform "
}'
"Microsoft . AspNetCore . Diagnostics ": " 1 .0.0",
"Microsoft . AspNetCore . Server.IISintegration ": " 1.0.0 ",
"Microsoft . AspNetCore . Server . Kestrel ": " 1 . 0 . 0 ",
"Microsoft . Extensions . Logging . Consol e ": " 1 . 0 .О",
"Microsoft . AspNetCore . Mvc ": " 1 . О . О ",
"Microsoft . VisualStudio . Web .B rowserLink . Loader": " 14 . 0 . 0 ",
"Microsoft . AspNetCore.StaticFiles": "1.0.0"
}'
ASP.NEТ
Cli,e.nt-~e
Code
~ Online
N.ame first.css
hЗ {
font - size: 18 pt ;
font - family: sans - serif ;
tаЫе , td {
border : 2рх solid Ыасk;
border - collapse : collapse ;
padding : 5 рх ;
font - family: sans - serif ;
Повторите процесс для создания в папке wwwroot/css еще одной таблицы стилей
по имени second. css с соде ржимым , приведенным в листинге 6.20.
р {
font - family: sans -se rif ;
font - size: 10 pt ;
color: darkgreen ;
background-color : antiquewhite ;
border : lpx solid Ыасk ;
padding : 2рх ;
ASP.NEl
~client·sid~
" Туре: Client·side f'
А script file co ntai ning JlJvllScript со
(ode
~ Onlinf
Нам необходим еще один файл JavaScript. Создайте в папке wwroot/js файл по
имени fourth. j s с содержимым , показанным в листинге 6.22.
Листинг 6.22. Содержимое файла fourth. j s из папки wwwroot/ j s
document.addEventListener("DOMContentLoaded", function () {
var element = document . createElement ( "р");
element. tex tContent = "This is the element fr om the fourth. j s file ";
document . querySelector( "body " ) . appendChi l d(element) ;
}) ;
Обновление представления
Последний подготовительный шаг связан с обновлением представления
Index. cshtml для использования новых таблиц стил ей CSS и файлов JavaScrlpt
(листинг 6.23).
Листинг 6.23. Добавление элементов script и link в файле Index. csh tml
@model IE numeraЬle<WorkingWithVisua l Studio . Models . Produc t >
@{ Layout = null; )
Глава 6. Работа с Visual Studio 165
< !DOCTYPE html>
<html>
<head>
<meta name="viewport" content= "width=device - width" />
<title>Working with Visual Studio</title>
<link rel="stylesheet" href="css/first.css" />
<link rel= "stylesheet" href="css/second.css" />
<script src="js/third . js"></script>
<script src="js/fourth. js"></script>
</head>
<body>
<h3>Products </h3>
<p>Request Time : @DateTime . Now . ToString("HH:mm:ss")</p>
< tаЫе>
<thead>
<tr><td>Name</td><td>Price</td></tr>
</thead>
<tbody>
@foreach (var р in Model)
<tr>
<td>@p . Name</td>
<td>@($ " {p.Price : C2} ")</td>
</tr>
}
</tbody>
</tаЫе>
</body>
</html>
____
---· ___с_ СФ:~~~:;~~~~=~-~-::..---::::~~-=--=====----_-::::' ~-----~~- :=-~~--
!
\ Products
l /Request Time: 17'29:48
Name Price
Lifejacket $48.95
Soccer ball $19.50
~
~ Templates Web E.ssentinls 2015.3 Rating: ., (42 Votes)
• Tools Adds many us~ u l features to \/i<J ual Studio for we b More lnformat1on '
!Seatc ~ults develop•"· Requires Visu•I Studio 2015 Report Exten:-.ion to Mкrosoft
" 1
~
~ Samples Gallery
1 ~,,,
Web Exton.slon Pack
1
Р Updotes (2) The e a ~1est way to set up Visual Studto for tl't ultimtte
we:b dNelopment experienc e.
~
Web An;ilyzer
Provid•s static analysis dir~ctly in Visual Studio for
~
JavdScript, Type:Sc ript, JSX. CSS .Jnd more
-··-··
..:...~....._._.".:....._:....:;...~".-; .... ~~--·....;,._.' -·~·~·---,.,.:_ ... __
_;..;.,'
Close
................-:.._'~..... "-..;.·-~.:;·-· :.;_,-..:.~:..~-~-· ~-----'
' '
Рис. 6.26 . Поиск расширения Visual Studio
В листинге 6.24 показано представление I ndex . csht ml, в котором элементы link
для отдельных файлов заменены одним таким элементом , запрашивающим пакетиро
ванные и минифицированные файлы.
<tаЫе>
<thead>
<tr><td>Name</td ><td >P ri ce</ td></ tr>
</thead>
<tbody >
@f o r each (var р in Model)
<tr>
<td>@ p.Name</td>
<td>@($ "{p. Price: C2 }" )</ td>
</tr>
</tbody>
</tаЫе>
</body>
</html>
{
"outputFil eName ": " wwwroot/ j s/bundl e .j s ",
" inputFiles ": [
" wwwroo t / j s/t hird .js",
" wwwroot/ j s / fo u r t h .j s "
Name Price
Lifejacket $48.95
Soccer ball $19.50
Corner flag $34.95
Резюме
В этой главе была рассмотрена структура проектов МVС, описаны две доступные
исполняющие среды .NET и обсуждены средства, которые Visual Studio предлагает для
разработки веб-приложений, включая автоматическую компиляцию классов, средство
Browser Liпk, а также пакетирование и минификацию. В следующей главе объясняет
ся. как проекты ASP.NET Core МVС подвергаются модульному тестированию .
ГЛАВА 7
Модульное тестирование
приложений МVС
всего реализовать то, что необходимо. Я считаю, что одно лишь размышление о том, что
нужно тестировать, способствует появлению идей относительно потенциальных проблем,
причем до того, как придется иметь дело с действительными ошибками и дефектами.
браузера (ли стинг 7.2). Действия следуют тому же шаблону, который применялся в
главе 2 и подробно обсуждается в главе l 7.
<t r>
<td >@p .Name</td>
<t d >@($ " {p .Pri ce :C 2} " )</td>
</ t r>
}
</tb o dy >
</ t а Ые >
<div class="text-center">
<а class="Ьtn Ьtn-primary" asp-action="AddProduct">
Add New Product
</а>
</div>
</body >
</ html>
Совет. Не забывайте, что в этом примере объекты хранятся только в памяти, т.е . любые со
здаваемые товары при перезапуске приложения будут утеряны.
Вопрос Ответ
Что это такое? xUnit.net - это инфраструктура модульного тестирования, которая мо
жет применяться для тестирования приложений ASP.NET Core MVC
лось в действительности
Изменилась ли она Инфраструктура ASP.NET Core MVC делает легким проведение модуль
по сравнению с ного тестирования , но вовсе не требует и не навязывает его использо
версией MVC 5? вание, а также не регламентирует применение конкретных инструмен
тесты в том же самом проекте или даже в том же самом файле класса. Я описываю здесь
распространенный подход , которому следую сам, но если он не кажется вам правильным,
то вы должны поэкспериментировать с различными стилями тестирования, пока не под
WorkingWithVisualStudio . Tests
Совет. В Visual Studio имеется шаблон проекта Unit Test (Модульный тест), однако он не
настроен для применения с .NET Саге и не поддерживает средства, подобные файлу
proj ect . j son.
Туре: Vis~
дprojectt/
i
11
t; 1NindO\ЧS
\.\/еЬ
~ЕТС?.~
1@} Sii Co n1ole App lication (.NЕТ Core) Visual (# Core class;-
1
ASP.NEТ Core Web Application (. NЕТ Co re) Visual (:<
1 Android
Cloud
1.1;
1 E:<tensibllity
1
1 iOS
1 Repor1ing
1
Silv~rlight
1
1 Test
i
1 WCF
1 ~ Online
! м,mе
1
f . Lo<:atio,ru
В нимание! Будьте внимательны, чтобы внести изменения в файл p r o j ect . j son внутри
прое кта модульного тестирования, а не проекта главного приложения.
Эта конфигурация сообщает Visual Studio о том, что требуются три пакета. Пакет
Microsoft . NETCore . Арр пр едоставля ет АРl-инт е рфейс .NET Core. Пакет xuni t
соде рж ит инфр а ст руктуру тестирования , а пакет dotnettest - x un i t обеспечива -
178 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
виде пр едварительно й верси и, и при чтении этой главы вы можете обнаружить более
ПОЗДНЮЮ версию .
Когда вы сохраните изменения , вн е сенные в файл proj ect . j son , среда Visual
Studio з агрузит и устан овит NuGеt-пакеты xUnit.net вм есте с их зави с имостями . Вс е
это мож е т потребовать определенного времени, поскольку зависимостей очень много .
Процесс создания проектов модульного тестирования для приложений ASP.NET Core
МVС в будущих выпусках Visual Studio, скорее всего, будет упроще н , по этому описан
ные здесь дополнительные шаги больше не понадобятся.
[Fact]
puЫic void CanChangeProductPrice()
11 Организация
var р = new Product { Name = "Test ", Price lOOM };
11 Действие
р . Price = 200М ;
11 Утверждение
Assert . Equal(lOOM , p.Price) ;
Совет. Атрибут Fact и класс Asset определены в пространстве имен Xuni t , для которого
дол жен быть предусмотрен оператор using в каждом тестовом классе.
180 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
Имя Описание
Test Explorer
Test Explorer • !:! х
Searcn
П: . . а · Sea.:_:h Р·
Sun1mary
Last Te:st Ru n Failed (Тotel Run Timl! О 00:04)
О Foiled
1 Ttst
О 1 Test Po"<d
Совет. Если вы не видите модульные тесты в окне Test Explorer, тогда постройте решение.
Компиляция запускает процесс, с помощью которого обнаруживаются модульные тесты.
Выполните тесты, щелкнув на пункте Run All (Выполнить все) в окне Test Explorer.
Ср еда Visual Studio прим енит xUnit.net для прогона тестов в проекте и отобразит ре
зул ьтаты . Как отм е чалось, тест CanChangeProduct Pr i ce () содержит ошибку. кото
рая приводит к тому , что те ст не проходит. Проблема связан а с аргументом метода
Assert . Equa l () , из-за чего прои сходит сравнение с исходным значением свойс
тва Price , а не со значением , на которое оно изменилось. В листинге 7.8 проблема
устранена.
Совет. Когда тест не проходит, всегда полезно проверить правильность самого теста, прежде
чем просматривать компонент, на который он нацелен, особенно если тест только что на
писан или недавно модифицировался.
[Fac t]
p uЫi c vo id Ca nCh a ng e Pro du c tPri c e ( )
11 Ор га н иза ц и я
va r р = n ew Pr oduct { Name = " Tes t", Pr i ce l OOM };
11 Де йс т ви е
p .P rice = 200 М;
11 У т вержд е н и е
Assert.Equal(200M, p.Price);
При наличии большого количества тестов выполнение их всех может занять неко
торое время. Таким образом, чтобы можно было работать быстро и итеративно, в окне
Test Explorer предлагаются различные варианты для выбора подмножества тестов,
подлежащих прогону. Самым полезным подмножеством является набор тестов , кото
рые не прошли (рис. 7.5). Запустите исправленный тест снова, и в окн е Test Explorer
будет показано , что не прошедшие тесты отсутствуют.
'Test Explorer
._,
Summary
St1mmary
l a5t Test Run Failed otJI Run iin~e 0:00:04)
l ast T~ t Ru n Passe:d (Tot.al Run Time 0:00:02)
f.З; 1 Test Fi!ifed
$ 1 TostPaшd О 1 TestPaщd
сопровождения.
Теперь, когда можно легко делать сравнения, давайте рассмотрим проблему зави
симостей между компонентами в приложении.
Добавим в проект WorkingWithVisualStudio. Tests новый файл класса по имени
HomeControllerTests . cs и поместим в него определение модульного теста, пред
ставленное в листинге 7.10.
[Fact]
puЫic void IndexActionModelisCornplete()
11 Ор гани за ци я
var contro l ler = new HorneController();
11 Действие
var rnode l = (controller.Index() as ViewResult)? . ViewData.Model
as IE nurneraЫe <Pro duct > ;
11 Утверждение
Assert . Equa l (S irnple Repository . SharedRepos itory . Products , rnodel ,
Cornparer . Get<Prod uct>( (pl, р2) => pl.Narne == p2 .Narne
&& pl. Price == р2. Price)) ;
Модульный тест в листинге 7.10 проверяет, что метод действия Index () переда
ет представлению все объекты в хранилище. (Пока что не обращайте внимания на
раздел действия: класс ViewResul t и роль, которую он играет в приложениях MVC,
будут объясняться в главе 17. В настоящий момент достаточно знать. что здесь полу
чаются данные модели, возвращаемые методом действия Index () .)
Запустив тест, вы увидите, что он не проходит, указывая на отличие между набо
ром объектов в хранилище и набором объектов, 1шторые возвратил метод Index () .
Но когда дело доходит до выявления причины, из-за чего т ест не прошел, возникает
проблема: предполагается, что тест действует на контроллере Home, однако класс кон
троллера зависит от класса SimpleRepository. Это затрудняет выяснение. отра зил
ли тест проблему с 1шассом, на 1\Оторый он нацелен, или же проблему в другой части
приложения.
Пример приложения достаточно прост, чтобы можно было легко выявить пробле
му, всего лишь взглянув на код классов HomeCont r o lle r и SimpleRepository. В ре
альном приложении визуальный осмотр не настолько прост, т.к. цепочка зависимос
тей способна затруднить понимание того, что привело к отказу в прохождении теста.
Обычно хранилище будет полагаться на какую-то разновидность системы постоян
ного хранения. подобную базе данных, а также библиотеку, которая предоставляет
к ней доступ. Модульный тест может взаимодействовать со всей цепочкой сложных
компонентов, любой из которых может вызвать проблему.
Модульные тесты эффективны, когда они нацелены на небольшие части приложе
ния, такие как отдельный метод или 1шасс. Нам необходима возможность изоляции
контроллера Home от остальной части приложения, чтобы можно было ограничить
область действия теста и исключить влияние со стороны хранилища .
Изолирование компонента
Ключом к изолированию компонентов является использование интерфейсов С# .
Чтобы отделить контроллер от хранилища, добавьте в папку Mode l s новый файл
класса по имени IReposi tory . cs с определением интерфейса, показанным в лис
тинге 7.1 1.
Глава 7. Модульное тестирование приложений MVC 185
Листинг 7.11. Содержимое файла IReposi tory. cs из папки Models
us i ng System . Collec t ions . Gener i c ;
namespace WorkingWithVisualStudio.Models
puЬlic i nterface IRe pository {
IEnurneraЫe<Product> Products { get ; }
void AddProduct(Product р);
Совет. Инфраструктура ASP.NET Core MVC поддерживает более элегантный подход к реше
нию этой проблемы, который называется внедрением зависимостей и описан в главе 18.
Внедрение зависимостей зачастую вызывает путаницу, поэтому в настоящей главе ком
поненты изолируются более простым и ручным способом.
[Fact]
puЫic void IndexAct i onMode l isComple te ( )
11 Организация
var control l er = new HomeControl l e r () ;
controller.Repository = new ModelCompleteFakeRepository();
11 Действие
va r mode l = (contr olle r . I nde x ( ) as ViewRe sult) ? . Vi e wData .Model
as IE nu meraЫe<Pro du ct> ;
11 Утверждение
Assert.Equal(controller.Repository.Products, model,
Compare r. Get<Prod uct> ( (pl, р2) => pl . Name == р2 . Name
&& pl.Price == p2 . Pr i ce)) ;
[Fact]
puЫic void IndexActionModelisComplete()
11 Организация
var controller = new HomeController() ;
controller . Repository = new ModelCompleteFakeRepository() ;
11 Действие
var model = (controller.Index() as ViewResult)? . ViewData.Model
as IEnumeraЫe<Product> ;
//Утверждение
Assert .Equal(controller .Repo sitory.Products , model ,
Comparer.Get<Product>( (pl, р2) => pl . Name == p2.Name
&& pl.Price == p2 . Price)) ;
};
puЬlic void AddProduct(Product р)
//ничего не делать - для теста не требуется
[Fact]
puЬlic void IndexActionМodelisCompletePricesUnderSO()
/ / Организация
var controller = new HomeController();
controller.Repository = newModelCompleteFakeRepositoryPricesUnderSO();
//Действие
var model = (controller.Index() as ViewResult)?.ViewData.Model
as IEnumeraЫe<Product>;
//Утверждение
Assert.Equal(controller.Repository.Products, model,
Comparer. Get<Product> ( (pl, р2) => pl. Name == р2 . Name
&& pl. Price == р2. Price) ) ;
Глава 7. Модульное тестирование прилож ений MVC 189
Мы определили фиктивное хранилище , которое содержит только объекты Product
со значениями свойства Price меньше 50, и применили его в новом тесте. Запустив
этот тест, вы увидите, что он проходит, подкрепляя идею о том , что пробл ема связана
с применением метода Where () в методе действия Index () .
В реальном проекте выяснение причины отказа теста означает необходимость со
гласования цели теста со спецификацией для прилож ения. Вполне может оказаться,
что метод Index () обязан фильтровать объекты Product по свойству Price , в случае
чего тест нуждается в пересмотре. Это распространенный итог, и тест, который не
прошел, вовсе не всегда указывает на присутствие реальной проблемы в приложении.
С другой стороны, если метод действия Index () не должен фильтровать объекты мо
дели, тогда потребуется внести корректирующую правку (листинг 7.16).
[HttpGet]
puЫic IActionResult AddProduct() => View( new Product() ) ;
[HttpPostJ
puЫic IActionResult AddProduct(Product р) {
Repository . AddProduct(p) ;
return RedirectToAction( " Index ");
190 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
Запустив тесты снова, вы увидите, что все они проходят (рис . 7.6).
Р·
Summary
l ast Test Run Passed (Тotal Run Time 0:00:04)
О 4 Tesls Passed
Может показаться, что мы проделали слишком много работы для устранения та
кой простой проблемы, однако возможность протестировать отдельный компонент
жизненно важна при построении реального приложения. Достижение точки, где вы
идентифицировали проблему и написали тесты для проверки исправления, возможна
только тогда, когда вы способны эффективно изолировать компоненты.
[Theory]
[InlineData(275, 48.95, 19.50, 24.95)]
[InlineData(5, 48.95, 19.50, 24.95)]
puЫic void IndexActionМodelisComplete(decimal pricel, decimal price2,
decimal priceЗ, decimal price4)
11 Организация
var controller = new HomeController();
controller.Repository = new ModelCompleteFakeRepository
Products = new Product [] {
new Product {Name "Pl", Price = pricel } ,
new Product {Name "Р2", Price = price2 } ,
new Product {Name РЗ , Price = priceЗ } ,
11 11
} ;
11 Действие
var model = (control ler.Index() as ViewResult)? . ViewData.Model
as IEnurneraЫe<Product> ;
11 Утверждение
Assert . Equal(controller . Repository .Products , model ,
Comparer .Get <Product>( (pl, р2) => pl.Name == p2.Name
&& pl . Price == р2 . Price));
Р·
Рис. 7.7. Параметризованные тесты в окне Test Explorer среды Visual Studio
Совет. Если вы хотите включить тестовые данные в тот же самый класс, который определяет
модульные тесты, тогда можете использовать атрибут MemberData вместо ClassData.
Атрибут MemЬerData конфигурируется с применением строки, указывающей имя стати
ческого метода, который будет предоставлять реализацию IEnumeraЫe<object [ ] >,
где каждый массив объектов в последовательности является набором аргументов для тес
тового метода.
[T h eory ]
[ClassData(typeof(ProductTestData))]
puЫic void IndexActionМodelisComplete (Product [] products) {
11 Организация
var controller = new HomeController();
controller . Repository = new ModelCompleteFakeRepository
Products = products
};
11 Действие
var model = (contro ll er . Index() as ViewResu l t)? . ViewData . Model
as IEnumeraЫe<Product> ;
11 Утверждение
Assert.Equal(controller . Repository . Products , model ,
Comparer . Get<Product>( (pl , р2) => pl . Name = = p2 . Name
&& pl . Price = = p2.Price)) ;
На заметку! Если вы посмотрите на список тестов в окне Test Explorer, то увидите, что для
тестов IndexActi onModelisComp lete предусмотрена единственная запись, хотя
класс ProductTestData предоставляет два набора тестовых данны х . Так происходит в
ситуации , когда объекты тестовых данных не удается сериализировать, и проблему можно
решить, применив к тестовым объектам атрибут SerializaЫe.
[Theory]
[ClassData(typeof(ProductTestData))]
puЫic void IndexActionModelisComplete(Product[J products) {
11 Организация
var controller = new HomeController();
controller . Repository = new ModelCompleteFakeRepository
Products = products
) ;
11 Действие
var model = (control ler.Inde x() as Vi ewResult)? . ViewData . Model
as IEnumeraЫe<Product> ;
11 Утверждение
Assert . Equal(control l er . Repository . Products , model,
Comparer . Get<Product>( (pl , р2) => pl.Name == p2 . Name
&& pl . Price == p2 .Price)) ;
[Fact]
puЬlic void RepositoryPropertyCalledOnce() {
/ / Организация
var repo = new PropertyOnce FakeRepository();
var controller = new HomeController { Repository = repo };
196 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
/ / Действие
var resul t = controller. Index () ;
/ / Утверждение
Assert.Equal(l, repo.PropertyCounter);
}
Вопрос Ответ
Что это такое? Moq - это программный пакет для создания фиктивных реали
заций компонентов в приложении
1 ~ En\rironment
1 ~ Projocts ond Solution<
1 t> Sошсе Control 0 api.nuget.org
1 ~ Т oxt Ed~or https:/iopi.nuget.org/v3/ indexJ<on
t• Debugging
~ Pelfo1n11nco Т ools
1 ~ D•tabase Т ools
Graphks Di~gnostic_'5
1
1>
А NuGet Packoge M•noger
. Generol
1 P.aclc_гge-S9urCe:s-
~ SQl Server Т ools
Machine·wide pockoge
J
~ Te>:t Templating
t> Tools for Apache Cordovв Mic rosoft and . NЕТ
1 0
~ Web https://v1Vм.nu9et.org/api/v2/cural<d·feeds/n1ic rosoftdotneV
1> W~b Forms Designer 0 Microsoft Vtsual Studio Offline Packages
~ Web Performanco Т est Т ools C:\Proarom Files (xSб)\Microsoft SOКs\NuGetPockoaes\
11 1> Windows Forms D esig neг
~ 'Norkflow Designer !':{omo: /nuget.org
l----------------------------~~~ё;~_:~_,
Рис. 7 .8. Конфигурирование источников пакетов NuGet
Поле Значение
Совет. Применение версии Moq от Microsoft является краткосрочной мерой; вам не придется
ее использовать после того, как в результате основных усилий по разработке добавится
помержка .NET Core. Когда это произойдет, вы сможете следовать инструкциям по уста
новке Moq, доступным по адресу h t tp : / / g i thub. com/moq/moq4.
198 Часть 1. Введение в инфраструктуру ASP.NEТ Саге MVC
[Theory ]
[Cl assDa t a( t ypeo f(P ro du ct Te stDa t a ) ) ]
puЫic void In d e xActionModelisComplete(Produ ct[] p r oducts) {
11 Орга н изация
var rnock = new Mock<IReposi tory> () ;
Глава 7. Модульное тестирование прило же ний MVC 199
mock.SetupGet(m => m.Products) .Returns(products) ;
var controller = new HomeController { Repository = mock.Object };
11 Действие
var model = (controller.Index() as ViewResult)? . ViewData . Model
as IEnumeraЬle<Produc t>;
11 Утверждение
Assert.Equal(controller.Repository . Products , model,
Comparer . Get<Product>( (pl, р2) => pl . Name == p2 . Name
&& p l. Price == р2. Price)) ;
[Fact)
puЫic void Reposito ryPropertyCalledOnce()
11 Организация
var mock = new Mock<IRepository>();
mock.SetupGet(m => m.Products)
. Returns (new [] { new Product { Name = "Pl", Price = 100 } } ) ;
var controller = new HomeController { Repository = mock.Object };
11 Действие
var result = contro ller.Index();
11 Утверждение
mock.VerifyGet(m => m.Products, Times.Once);
Созданный объект Mock будет имитировать интерфейс IReposi tory. Далее оп
ределяется функциональность, которая требуется для теста. В отличие от обычной
реализации интерфейса классом для имитированного объекта конфигурируется толь
ко поведение, нужное для теста. В первом имитированном хранилище понадобится
реализовать свойство Products, чтобы оно возвращало набор объектов Product, ко
торый передается тестовому методу через атрибут ClassData:
который будет возвращаться, I<огда читается значение свойства. Для второго имити
рованного хранилища применяется тот же самый подход. но указывается фиксиро
ванное значение:
Метод Veri f yGet () относится к тем методам класса Mock , которые инспекти
руют состояние имитированного объекта, когда тест завершен. В этом случае ме
тод VerifyGet () позволяет проверить, сколько раз читалось свойство Products .
Значение Times . Once указывает, что метод VerifyGet () должен генерировать ис
ключение, если свойство читалось не в точности один раз, и это приведет к тому, что
тест не пройдет. (Методы класса Assert , обычно используемые в тестах, генерируют
исключение, когда тест не проходит, и потому при работе с имитированными объ ек
тами метод VerifyGet (} можно применять для замены метода класса Assert .)
Резюме
Большая часть этой главы была сконцентрирована на модульном тестировании,
которое может оказаться мощным инструментом для повышения качества кода.
в предшествующихMVC,
главах мы создавали очень простое приложение МVС. Был
описан паттерн основные средства языка С#, а также инструменты, не
обходимые профессиональным разработчикам приложенийMVC. Наступило время
собрать все вместе и построить несложное, но реалистичное приложение электрон
ной коммерции.
Наше приложение под названием SportsStore будет следовать классическому под
ходу, который повсеместно используется в онлайновых магазинах . Мы создадим он
лайновый каталог товаров, который потребители могут просматривать по категори
ям и страницам, корзину для покупок, куда пользователи могут добавлять и удалять
товары, и форму оплаты, где потребители м огут вводить сведения, связанные с до
ставкой. Кроме того, мы создадим административную область, которая включает в
себя средства создания, чтения, обновления и удаления (create, read, update, delete -
CRUD) для управления каталогом товаров, и защитим ее так, чтобы изменения могли
вносить только зарегистрированны е администраторы.
Цел ь этой и последУющих глав - дать вам возможность увидеть, на что похожа
реальная разработка приложений MVC, за счет создания примера приложения, кото
рый максимально приближен к реальности. Разумеет ся, мы будем ориентироваться
на ASP.NET Core MVC, поэтому интеграция с внешними системами, такими как база
данных, предельно упрощена, а определенные части приложения, например, обработ
ка платежей , вообще отброшены.
Построение всех уровней необходимой инфраструктуры может показаться не
сколько медленным, но первоначальные трудозатраты при разработке приложения
MVC окупаются, обеспечивая удобный в сопровожде нии, расширяемый и хорошо
структурированный код с великолепной поддержкой модУльного тестирования.
Модульное тестирование
средствами MVC.
202 Часть 1. Введение в инфраструктуру ASP.NET Саге MVC
Я понимаю, что мое мнение не является единственно верным. Если вы не хотите прибе
гать к модульному тестированию, то меня это вполне устроит. Таким образом, когда что
то относится исключительно к тестированию, оно будет помещаться во врезку, подобную
настоящей. Если модульное тестирование вас не интересует, то можете смело пропускать
эти врезки, и приложение SportsStore будет работать не менее успешно. Чтобы восполь
зоваться преимуществами технологии ASP.NET Саге MVC, вовсе не обязательно проводить
какое-либо модульное тестирование, хотя поддержка тестирования, конечно же, является
основной причиной перехода на ASP.NET Саге MVC.
На заметку! В Microsoft заявили , что в следующей версии Visual Studio изменят инстру
ментарий, применяемый для создания приложений ASP.NET Core MVC. Проверяйте
веб-сайт издательства на предмет обновлений, которые появятся после выпуска новых
инструментов.
Начало работы
Если вы планируете писать код приложения SportsStore на своем компьютере
во время изучения материала этой части книги, то вам придется установить Visual
Studio и удостовериться в том, что установлен вариант LocalDB, который требуется
для постоянного хранения данных.
На заметку! Если вы просто хотите работать с проектом, не воссоздавая его, тогда може
те загрузить готовый проект SportsStore как часть загружаемого кода примеров для на
стоящей книги, который доступен на веб-сайте издательства. Разумеется, вы вовсе не
обязаны повторять все действия. Я старался делать снимки экрана и листинги кода мак
симально простыми в отслеживании на тот случай, если вы читаете эту книгу в поезде ,
кафе или где-то еще.
"'1emp14tts
4
m ASP.NEТ V/eb Applicdtion (.NfТ FramбVork) Visu.:il (# ТУР": ;
Proj«
"' Vi~ual С '*
"Windo~
i.'!•l>•
11 @ ASP.NEТ Core Wtb Applkation (.NЕТ frameowork}
Corea: ,
ondO~
9Appl~
.NfТ Core 0 Ad~
дndroid Optt
Cloud uУф
f!. Online
SportsS-tore
~ c:\p_r~~ct~ ~.
Solution name: ,Sport sSl:ore
S•l•d • l•mplat<:
1
г~;;:; C:reT;,;; i"- - - ---·-··------1 An empty project template for crtating "" ASP.NП Со1е 1
~pp l i c.5tfo n . This; template does not h~e any content in J
li
j1
~
Web API
m
App~::i;on
1
j
~ 1
1 i 1
1! 1 1
1i ~ г~~~~-~~~c;titJ~J 1
1
[
--- -----· ------
' ] j_ C..nceJ 1 j
Рис. 8.2. Выбор шаблона проекта
) '
" buildOptions ":
" emitEntryPoint ": true ,
" preserveCompilationContext ": true
} '
" runtimeOptions ": {
" configProperties ":
" System . GC . Server " : true
}'
" puЬlishOptions ":
" include ": ["wwwroot ", " web .config " ]
) '
" scr ip ts ": {
" postpuЫish ": [ " dotnet puЫish - iis -- puЫish - folder
% puЬlish : OutputPath % -- framework % puЫish : FullTargetFramework % "
}
Microsoft . AspNetCore. Mvc Этот пакет содержит инфраструктуру ASP. NET Core
MVC и предоставляет доступ к основным средс
твам , таким как контроллеры и представления Razor
Microsoft .AspNetCore. StaticFiles Этот пакет обеспечивает поддержку для обслужи
вания статических файлов, таких как файлы изоб
ражений , JavaScript и CSS, из папки wwwroot
Microsoft . AspNetCore. Razor . Tools Этот пакет предлагает инструментальную подде
ржку для представлений Razor, в том числе средс
тво lntelliSense для встроенных дескриnторных
вспомогательных классов, которые применяются в
Конфигурирование приложения
Приложение ASP.NET Core МVС полагается на несколько конфигурационных фай
лов . Имея установл енные пакеты NuGet, понадобится отредактировать класс Startup,
чтобы сообщить ASP.NET о том. что они должны использоваться (листинг 8 .2).
206 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC
Таблица 8.3. Начальные методы для настройки средств, вызываемые в классе Startup
Метод Описание
На заметку! Класс Startup - важное средство ASP.NEТ Саге . Он будет подробно описан
в главе 14.
Se.шhlnS1all.
~
1
AS!'.NEТ
Cl1enHide
l!il" MVC l•yovt P•ge
V.<w ASP.NEТ • Туре: ASP~
MVCView~
Code r:s:
12.1 MVC View Page Stм ASP.NE1
'1 Online
1 ~
~·
~
~ z.c r Т119 H r lpe1 ASP.NEТ f
1
~· Middlew11re (iass AS P.N П
(
1 "
1
fq
"1
!ti
vГJ ASP.NEТ Conliguration File д\Р.NП
c-~---v~~-~=:_~~------------- J
Рис . 8.3. Создание файла импортирования представлений
Щелкните правой кнопкой мыши на папке test в окне Solution Explorer и выбери
те в контекстном меню пункт Add~New Project (Добавить~Новый проект). Выберите
шаблон Class Library (.NET Core) (Библиотека классов (.NET Core)) из категории
lnstalled~Visual C#~.NET Core (УстановленныеqVisuаl C#q .NET Core), как показано
на рис. 8.4, и установите имя проекта в SportsStore. Tests .
iOS
Reporting
Silverlight
Test
WCF
t- Online
На заметку! Пакет rnoq. ne tcore, который применяется в листинге 8.4, требует изменения
конфигурации Visual Studio, как было описано в разделе "Добавление инфраструктуры
имитации" главы 7. Если вы не прорабатывали примеры из главы 7, то должны внести это
изменение в конфигурацию прямо сейчас.
Листинг 8.4. Содержимое файла proj ect. j son из проекта модульного тестирования
"frameworks ": {
"ne t coreappl.O":
"imports": ["dot net5.6", "portaЫe-net 4 5+win8"]
• iJ Sport<Store
те другие элементы или элементы расположены ~ }' Propert;e.
не в тех позициях, то возникнут проблемы, поэ ~ •·8 Referenc6
0 1 ~V\W/fOOt
тому уделите время проверке, что все элементы • ·• Depмdtncie:-;;
,_ Controllers
присутствуют и находятся на своих местах.
~ Models
Выбрав в меню Debug (Отладка) пункт Start " ~ Vit'\'JS
(21 _Vie"нtmports.c-;h tn1I
Debugging (Запустить отладку) или Start Without с• Progrмц s
Debugging (Запустить без отладки) , если вы пред ~ 6J prcject.json
,!) Project_Readme.html
почитаете итеративный стиль разработки, опи
с• Startup.cs
санный в главе 6, вы увидите страницу ошибки v') v1eb.config
!i.. tt.st
(рис. 8.6). Сообщение об ошибке отображается
.jf
" ~S Sport.sStore.Tms
из-за того, что в настоящий момент в приложе flc /' Propertie\
i. •·1 Referencб
нии нет контроллеров для обработки запросов; с- Cl г~s 1 .cs.
----~~ : 1
Создание хранилища
Нам нужен какой-то способ получения объектов Pr oduct из базы данных. Как
объяснялось в главе 3, модель включает в себя логику для сохранения и извлечения
данных из постоянного хранилища. Пока можно не беспокоиться о том, как будет
реализовано постоянство данных, но необходимо начать проце сс определения ин
терфейса для него . Добавьте в папку Model s новый файл интерфейса С# по имени
I ProductRepository . cs и поместите в него определение, показанное в листинге 8.6.
Этот инте рфейс использует I E n urneraЫe < T > , чтобы позволить вызывающе
му коду получать последовательность объектов Product , нич его не сообщая о том,
как или где хранятся либо извлекаются данные. Класс, зависящий от интерфейса
IPr odu ctRepos i tory , может получать объекты Pro du ct, ничего не зная о том, от
куда они поступают или каким образом класс реализации будет их доставлять. В про
цессе разработки мы еще будем возвращаться к интерфейсу IProdu c tReposito r y,
чтобы добавлять в него нужные средства .
твующие изменения где-то в другом месте. Такой подход трактует части приложения
как службы, которые предоставляют функциональные средства, используемые другими
частями приложения. Класс, предоставляющий службу, впоследствии может быть мо
дифицирован или заменен, не требуя внесения изменений в классы , которые его задейс
твуют. Более подробно это объясняется в главе 18, а в случае приложения SportsStore
необходимо создать слу;н:бу хранилища, которая позволит контроллерам получать ре
ализующие интерфейс I ProductRep os i tory объекты, не зная, какой класс применя
ется. В итоге появится возможность начать разработку приложения с использованием
простого класса FakeProductRepository, созданного в предыдущем разделе, и позже
заменить его реальным хранилищем, не внося изменения во все классы, которым ну;кен
Тем не менее, это еще одна ситуация, когда ваш стиль разработки может отличаться от мое
го , и вы вполне можете предпочесть работу со средством формирования шаблонов. В таком
случае можете включить его, сделав ряд добавлений в файле p r oj ect . j son. Во-первых,
в разделе dependencies потребуется указать два новых пакета:
"type": "build"
},
"version 11 : 11 1 . О . O-preview2-final 11 ,
"imports 11 : [
11
portaЫe-net45+win8+dnxcore50 11
,
11
portaЫe - net45+win8"
},
После сохранения изменений и установки пакетов средой Visual Studio вы увидите новые
пункты в контекстных меню, открываемых по щелчку правой кнопкой мыши на папках в окне
Solution Explorer. Выбор этих пунктов меню будет приводить к появлению диалоговых окон,
позволяющих выбирать сценарии , которые должны применяться для создания контроллера
или представления.
Добавление контролле ра
Чтобы создать п ервый контролл ер в приложении, добавьте в папку Con t r ol lers
файл класса по им ени ProductController . c s с опр едел е нием, показанным в лис
тинге 8.9.
Вызов метода View () подобного рода (без указания имени представления) указы
вает инфраструктуре MVC о том, что нужно визуализировать стандартное представ
ление для метода действия. Передача методу View () экземпляра List<Product>
(списка объектов Product) снабжает инфраструктуру данными, которыми необходи
мо заполнить объект Model в строго типизированном представлении.
Глава 8. SportsStore: реальное приложение 215
Далее пон адобится сконфигурировать прилож ение, чтобы файл _Layout . cshtml
прим енялся по ум олчанию . Это делается добавлением в папку Vie ws файла с
шаблоном MVC View Start Page (Ф айл запуска представления MVC) и именем
ViewS tar t . cshtml.
Стандартное содержимое, добавляемое Visual Studio (листинг 8.12), выбирает ком
поновку по им ени _La yout . csh tml , которая соответствует файлу из листинга 8. 11 .
Выражение @rnodel в начале файла указывает, что представл ение будет получать
от метода действия последовательность объектов Product в качестве данных модели.
С помощью выражения @foreach осуществляется проход по этой последовательнос
ти и генерация простого набора НТМL-элементов для 1шждого полученного объекта
Product .
Представлению не известно, откуда поступили объекты Product, как они были
получены или охватывают ли они все товары, известные приложению. Взамен пред
ставление имеет дело только с тем , как отображать детали каждого объекта Product
с применением НТМL-элементов, что согласуется с принципом разделения обязаннос
тей, который был описан в главе 3.
Совет. Обратите внимание, что в листинге8.14 имя контроллера указано как Pr oduct , а не
ProductController , являющееся именем класса. Это часть соглашения об именова
нии MVC, в рамках которого имена классов обычно заканчиваются словом Controller,
но при ссылке на класс данная часть имени опускается. Соглашение об именовании и его
влияние объясняются в главе 31.
Запуск приложения
Все основные компоненты в наличии. Мы имеем контроллер с методом действия,
который MVC будет применять, когда запрашивается стандартный URL для прил о
жения. Инфраструктура MVC создаст экземпляр класса FakeRepository, после чего
будет использовать его для создания нового объекта контроллера, обрабатывающего
запрос. Фиктивное хранилище снабдит контроллер простыми тестовыми данными,
которые его метод действия передаст представлению Razor, так что НТМL-ответ для
браузера будет включать детали каждого товара. При генерации НТМL-ответа инфра
структура МVС объединит данные из представления, выбранные методом действия.
с содержимым разделяемой компоновки, порождая завершенный НТМL-документ,
который браузер в состоянии разобрать и отобразить. Запустив приложение, можно
увидеть результат, показанный на рис. 8. 7.
Это типовой шаблон разработки для инфраструктуры ASP.NET Core MVC.
Начальные затраты времени на необходимую настройку являются обязательными.
но затем базовые средства приложения будут собир аться очень быстро.
218 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
1·. SportsStor~ Х
f- ' С [Ф lo~host:бOOOO
1--------===----==::::::=::=.:::=--_-:::::::::::.:.. -~
Football
S25.00
JI Su1·f boa1·d
Sl79.00
l
R1шnlng slloes
$95.00
- --------- -------------------·------------
Рис. 8.7. Просмотр основной функциональности приложения
Совет. Если при установке Visual Studio вы не выбрали LocalDB , то придется сделать это
сейчас. Средство представляет собой часть инструментов для работы с данными или же
его можно установить как часть SQL Server.
Листинг 8.15. Добавление Entity Framework Core в файле proj ect. j son
внутри проекта SportsStore
"dependencies ":
"Microsoft . NETCore . App ":
"version ": "1.0 . 0 ",
"type ": "platform "
) '
"Microsoft . AspNetCore.Diagnostics ": "1. 0 . 0 ",
"Microsoft.AspNetCore . Server . I ISintegration": " 1 . 0 . 0 ",
"Microsoft . AspNetCore .Server. Kestrel ": " 1.0 . 0 ",
"Microsoft . Extensions . Logging . Con s ole ": "1. 0.0 ",
"Microsoft . AspNetCore . Razor . Tools" : (
"version ": "l.0. 0- preview2 -final ",
"type ": "build"
) '
"Microsoft . AspNetCore . StaticFiles ": " 1. О. О ",
"Microsoft . AspNetCore . Mvc ": "1. 0 .0",
"Мicrosoft.EntityFrameworkCore. SqlServer" : "1. О. 0",
"Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final"
) '
"tools ":
"Microsoft . AspNetCore . Razor . Tools": "1. 0 . 0-preview2-final ",
"Micro sof t .AspNetCo re.Server. IISintegration.Tools ": "1. 0 .0-preview2- final",
"Мicrosoft . Enti tyFrameworkCore. Tools": {
"version": "1.0.0-preview2-final",
"imports": [ "portaЫe-net45+win8+dnxcore50", "portaЬle-net45+win8" ]
) '
220 Часть 1. Введение в инфраструктуру ASP.NET Саге MVC
После сохранения файла proj ect. j son среда Visual Studio загрузит и установит
инфраструктуру EF Core, а также добавит ее в проект.
листинге 8.18.
Статический метод Ens urePopul ated () получает аргумент типа IApp licat i o n
Bu i lder, который является нлассом, используемым в методе Con figure () нласса
Startup при регистрации классов промежуточного программного обеспечения для
обработки Н1ТР-запросов; именно здесь будет обеспечиваться наличие содержимого
в базе данных.
Метод The EnsurePopu l a t ed () получает объект Appl ic a ti o n DbCon t ex t пос
редством интерфейса IApp lic a ti onBu i lder и применяет его для проверки , при
сутствуют ли в базе данных какие-нибудь объекты Produc t. Если объектов нет, то
база данных наполняется с использованием коллекции объектов Product и метода
AddRange () ,после чего сохраняется с помощью метода Sav eCha n ge s () .
Конфигурирование приложения
Дал ее понадобит ся прочитать строку подключения и сконфигурировать приложе
ние для е е исполь з ования при подключении к базе данных. Для чтения строки под
ключения и з файла appsettings . j son требуется еще один пакет NuGet. В листин
ге 8.21 показано из менение раздела dependencies внутри файле proj ect . j son .
"dependencies ":
"Microsoft . NETCore . App ":
"version ": "1. 0 . 0 ",
" type ": "platform "
}'
"Microsoft . AspNetCore . Diagnostics ": "1. О . О ",
"Microsoft . AspNetCore . Serv er .I ISi nt egrat i on ": " 1 . 0 .0",
"Microsoft . AspNetCore . Se r ve r. Kestre l": " 1 . О . О ",
"Microsoft . Extensions . Loggi ng . Console ": " 1 . 0 . 0 ",
"Microsof t. AspNetCore . Razo r. Tools ": {
"version ": " l.0 . 0- preview2 - final ",
" type " : "build "
}'
"Microsoft . AspNetCore. Sta t icFi l es " : "1. О. О ",
"Microsoft . AspNetCore. Mvc": " 1 . О . О ",
"Microsoft . EntityFramewo r kCore . SqlServe r": " 1.0 . 0 ",
"Microsoft . EntityFrameworkCore . Tools ": " l . 0 . 0- preview2 - final ",
"Мicrosoft.Extensions.Configuration.Json": "1.0.0"
}'
namespace SportsStore {
puЫic clas s Startup {
IConfigurationRoot Configuration;
puЬ1ic Startup(IHostingEnvironment env) {
Configuration = new ConfigurationBuilder()
224 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json") .Build();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["Data:SportStoreProducts:ConnectionString"]));
Add-Migration Initial
Когда команда завершит свое выполнение, вы увидите в окне Solution Exp lorer сре
ды Visual Studio папку Migrations. Именно в ней инфраструктура Entity Framework
Core хранит классы миграции. Одно из имен файлов будет выглядеть как длинное
число с _ Ini tial. cs после него, и это класс, который будет применяться для созда
ния начальной схемы базы данных. Просмотрев содержимое такого файла , вы увиди
те, как класс модели Product использовался для создания схемы.
Запустите приведенную ниже команду, чтобы создать базу данных и выполнить
команды миграции:
Update-Database
Создание базы данных займет какое-то время, но после завершения работы ко
манды вы сможете увидеть результат, запустив приложение. Когда браузер запраши
вает стандартный URL для приложения, !{онфигурация приложения сообщает МVС о
необходимости создания контроллера Product для обработки запроса. Создание кон
троллера Product означает вызов конструктора класса ProductController, которо
му требуется объект, реализующий интерфейс I Р roduc t Repo s i tor у, и новая конфи
гурация указывает MVC о том, что для этого должен быть создан и применен объект
EFProductReposi tory. Объект EFProductReposi tory обращается к функциональ
ности EF Core, которая загружает реляционные данные из SQL Server и преобразует
226 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
1 ·. \po.-ts\to« х
1 ~
С @i-1oc_a_lh-os-t.600-0-=0= = =- - - · - - -
!· -- . =-- --=::-=-=-=-----=---===--··---
Kayak
А b~ar оп<
'1
1 for p<rso11
1 5275.00
LlfejAckN
Prot~ C t1\·~ n11d f(ls luo111\ЬI~
1
1
1 S4S.95
1
\ SoccпBnll
'
L ElfЛ:<"1oe_ro ~Oя.z.~_fI04..!~~i~1t _________________________ ~-- ·--- "'
Такой подход с применением Entity Framework Core для представления базы дан
ных SQL Server в виде последовательности объектов моделей отличается простотой
и легкостью, позволяя сосредоточить все внимание на инфраструктуре ASP.NET Core
MVC. Я опустил множество деталей , связанных с функционированием EF Core, и
большое количество доступных конфигурационных параметров . Мне нравится инф
раструктура Entity Framework Core, и я рекомендую уделить время на ее изучение.
Хорошей отправной точкой послужит веб-сайт Microsoft для Entity Framework Core,
находящийся по адресу http: / /ef. readthedocs. io .
controller . PageSize = З;
11 Действие
IEnumeraЫe<Product> result
controller .Li st(2) . ViewData . Mode l as IEnumeraЫe<Product> ;
11 Утверждение
Product[] prodArray = result.ToArray();
Assert . True(prodArray .Length == 2);
As sert. Equal ( " Р4 ", prodArray [ О ] . Name) ;
Assert . Equal( " PS ", prodArray[l] . Name) ;
using System ;
namespace SportsStore . Models . ViewModels
puЫic class Paginginfo {
puЫic int Totalitems { get; set ; }
Глава 8. SportsStore : реальное прило жение 229
puЫic int ItemsPerPage { get ; s et ; }
pu Ыi cint Curre n tPage ( g et ; s e t ; }
puЫic int TotalPages =>
(int)Math . Cei l ing((decima l )To t alitems / I t e msPe r Page) ;
ный класс . Создайте в проекте Sport sS tore папку I nfrastr u cture и добавьте в нее
файл класса по им ени PageLinkTagHel p er . cs с определением, показанным в лис
тинг е 8.25. Де скрипторны е вспомогательные классы являются крупной частью инф
раструктуры ASP.NET Core MVC, и в главах 23-25 объясняется , как они работают и
каким образом их создавать.
[ViewContext]
[HtmlAttributeNotBound]
puЬlic ViewContext ViewContex t { get ; set ;
puЫic Paginginfo PageMode l { get ; set ; }
puЫic string PageAction { get ; set ; }
puЫic override v oid Process( Ta gHelp e r Co n t e xt co n text ,
TagHelperOutput output) (
IUrlHelper urlHelper = ur l HelperFactory.GetUrlHelper(ViewCon t ext) ;
TagBuilder res ul t = new TagBu il der( " div " ) ;
for (in t i = 1 ; i <= PageMode l . To t alPages ; i ++ ) {
TagBuilder tag = new TagBuilder ( " а " ) ;
tag . Attributes [ "href"] = ur l He l per . Action ( PageAction , new { page = i } ) ;
tag . In n erHtml . Append (i. ToS t ri n g() );
result . InnerHtml . AppendHtml(tag) ;
[Fact]
puЬlic void Can_Generate Page Links()
11 Организация
var urlHelper = new Mock< IUrlH e lper>() ;
Глава 8. SportsStore : реальное прило ж ение 231
urlHelper.SetupSequence(x => x . Action(It . IsAny<UrlActionContext>()))
. Returns( 11 Test/Pagel 11 )
. Returns( 11 Test/Page2 11 )
. Returns( 11
Test/PageЗ 11
) ;
var urlHelperFactory = new Mock<IUrlHelperFactory>() ;
urlHelperFactory . Setup(f =>
f . GetUrlHelper(It . IsAny<ActionContext>() ))
. Returns(urlHelper.Object) ;
PageL i nkTagHelper helper =
new PageLinkTagHelper(urlHelperFactory . Object)
PageModel = new Paginginf o {
CurrentPage = 2 ,
Totalitems = 28 ,
I t emsPerPage = 10
},
11
PageAction = Test 11
};
TagHelperContext ctx = new TagH e lperContext(
new TagHelperAttributeList() ,
new Dictionary<object , object>(), 1111 ) ;
var content = new Mock<TagHelperContent>() ;
TagHelperOutput output = new TagHe l perOutput( 11 div 11 ,
new TagHelperAttributeList() ,
(cache , encoder) => Task.FromResult(content.Object));
11 Действие
helper . Process(ctx , output) ;
11 Утверждение
Assert . Equal(@ 11 <a href= 11 " Test/Pagel "" >l</a> 11
+ @ <а href= 1111 Test/Page2 1111 >2</a> 11
11
+ @ 11
<а href= "" Test/Page3 1111
>З</a> ",
output . Content . GetCon t ent() ) ;
Сложность этого теста связана с созданием объектов, которые требуются для создания и
применения дескрипторного вспомогательного класса . Дескрипторные вспомогательные
классы используют объекты IUr lHelperFactory для генерации URL, которые указывают
на разные части приложения, и для создания реализаций этого интерфейса и связанного
с ним интерфейса IUrlHelper , предоставляющего тестовые данные , используется инф
раструктура Moq.
файла нельзя , если только строка , с которой производится сравнение, не разнесена ана
логичным образом . Например, литерал, применяемый в тестовом методе, был размещен в
нескольки х строках из-за недостаточной ширины печатной страницы . Символ новой строки
не добавлялся, иначе тест не прошел бы.
232 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC
ного класса модели предст авления. Добавьте в папку Mode l s/ViewMo d e l s про е кта
Sp ortsS t o r e ф айл класса по имени P r oductsListViewModel . cs с содержимым .
приведенным в листинг е 8.27.
Теперь можно обновить метод действия Li s t () кл асса Produc t Con troller так.
чтобы он использовал ю~асс Pr odu cts Li s tVi ewMod el для снабжения представления
сведениями о товарах, отображаем ых на ст раницах. и информацией о разбиении на
страницы (листинг 8.28).
[Fact]
puЫic void Can_Send_ Pagination_View_Model() {
11 Организация
Mock<IProductRepository> mock = new Mock<IProduc t Repository>() ;
mock.Setup(m => m. Products) . Returns(new Product[] {
new Product {ProductID 1 , Name " Pl " ) ,
new Product {Produc t ID 2 , Name " Р2 " } ,
new Product {ProductID 3 , Name " РЗ "),
11 Организация
ProductControl l er controlle r =
new ProductCon tr oller(mock . Object) { PageSize 3 );
11 Действие
ProductsListV i ewModel result =
controller . List(2) . ViewData.Model as Products ListViewModel ;
11 Утверждение
Paginginfo pageinfo = result . Pagingin f o ;
Assert . Equal(2 , pageinfo . CurrentPage) ;
Assert . Equal(З , pageinfo .I temsPerPage) ;
Assert . Equal(5 , pageinfo . Tota l items) ;
Assert . Equal(2 , pageinfo.TotalPages) ;
Потребуется также модифицировать ранее созданный модульный тест для проверки раз
биен и я на страницы, содер жащийся в методе Can_Paginate () .Он полагается на метод
действия List () , возвращающий объе кт ViewRe s ul t , свойством Model которого явля
ется последовательность объе ктов Product, но мы поместили эти данные внутрь еще од
ного типа м одели представления. Вот переделанный тест:
[E'act ]
puЫic void Can_Paginate() {
11 Организация
Mock<IProductRepository> mock = new Mock<IProduc t Repository>() ;
mock . Setup(m => m. Products) . Re t urns(new Product[J {
new Product {Product I D 1 , Name " Pl " ) ,
new Product { ProductID = 2 , Name = "Р 2 "),
234 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
Учитывая объем дублированного кода в двух тестовых методах, обычно следовало бы со
здать общий метод начальной установки. Однако, поскольку модульные тесты описаны в
индивидуальных врезках вроде этой, их код представлен по отдельности, чтобы с каждым
тестом можно было ознакомиться независимо от других.
Выражение @mode l было изменено для указания механизму Razor на то, что те
перь мы работаем с другим типом данных. Цикл foreach был обновлен, чтобы источ
ником данных стало свойство Products объекта модели.
1 Produ ш )( 1 Produщ Х
1
1
\ +- -' С
f- -----
D localhost:60000/'pag
+- -"
Products
_ - - -· · - - - - - -
Х ...
+- .., С С1 localhost:бOOOO/?page•З
С D locall1os t 60000/>page• - - - - - - - - - - - - - - - - - -
,..--------------
'{;;1
=·
1
K oyok ----~- --'-----"'-'--- B ll11g- Bli11g Кlug
!
1 А boat fot онео pcr~o11 !
S t a dillШ
Gold-pla.tcd duiшoш1-snIOdcd Кшg
1 • .ОО I Flat-packcd ЗS.000-~c<ct !iiti'ldшш
5275 SI,200.00
1 1 S79,500.00
Li fфcke l lli
1 Tbluki11g Со р
j Protectt\'C' анd t"n11>!1ioщ1ЬJ 1
Iщр1·0 \·е bri'\bl cfficict1cy Ь
1 S4S.9S
j 1
Sl б.00
Soccr1· Bo ll 1
UnsleodY Ch зi1·
FIFA-npproп•d siz tшd \\'tigltt 1 .
Stc rC'tly g iп· ущ1r
1
9
Sl .SO j S29.95
1 Cш·nei· F l "s 1
Hu шn n Cl1
)ii'IY111?, fiC'ld а prof~.<.:s tOJl~I to\j
А fш1 gаш or tl1e fa1шly
1 575.00
Если вы ранее работали с ASP.NET, то вам может показаться , что такой большой объем ра
боты привел к довольно скромным результатам. Пришлось написать немало кода лишь для
того, чтобы получить список товаров с разбиением на страницы. Если бы мы прибегли к
услугам инфраструктуры Web Forms, то такого же результата можно было бы достичь с при
менением готового элемента управления Gr i dVi ew или Li s tVi ew из ASP.NET Web Forms,
привязав его напрямую к таблице Pr oducts базы данных.
То, что было сделано в главе, выглядит не слишком впечатляюще, но серьезно отличается
от процесса перетаскивания какого-то элемента управления на поверхность визуального
Улучшение URL
Ссылки на страницы работают, но для передачи информации серверу они по-пре
жнему используют строку запроса, подобную следующей:
http : //localhost/?page=2
Чтобы получить бол е е привлекательные URL, необходимо создать схему. которая
следует шаблону компонуемых URL. Компонуемый URL - это URL, имеющий с мысл
для пользователя, такой как показанный ниже:
http : // l ocalhost/Page2
Инфрастру1пура МVС позволяет легко изменять схему URL в приложении, потому
что применяет средство мapшpymuзa4uuASP. NET, которо е отвечает за обработку URL
для выяснения, на какую часть приложения они указывают. Понадобится лишь доба
вить новый маршрут при регистрации промежуточного программного обеспеч е ния
МVС в методе Configure () кл асса Star t up (листинг 8.31).
емых для гене рации ссылок на страницы . Не беспокойтесь, если маршрутизация пока
Sta(liШll
1 ~"0.00 " ~·
....,. !11! .... .,.,,,....- ~
~..-..;...,..., _ " ' -_ _ _,,,.
Стилизация содержимого
Мы построили значительную часть инфраструктуры и базовые средства прило
жения готовы к сборке всего воедино, но пока совершенно не уделяли внимание его
внешнему виду. Хотя дан ная книга не посвящена веб-дизайну или CSS, внешний вид
приложения SportsStore настолько примитивен, что это умаляет его технические до
стоинства. В настоящем разделе мы постараемся исправить ситуацию. Мы собираем
ся реализовать классическую компоновку, содержащую два столбца и заголовок, как
показано на рис. 8.11 .
238 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC
Home • Product 1
·• Watersports • Product 2
• Socce.r •
• , Chess (main body)
•... .,,·
После сохранения внесенных в файл bower. j son изменений среда Visual Studio
применяет инструмент Bower для загрузки пакета Bootstrap в папку wwwroot/liЬ/
bootstrap. Пакет Bootstrap зависит от пакетаjQuегу. который также автоматически
добавится в проект.
<strong>@p.Name</strong>
<span class="pull-right laЬel laЬel-primary">
@р. Price. ToString ("с")
</span>
</hЗ>
<span class="lead">@p.Description</span>
</div>
240 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
Нам необходимо стилизовать кнопки, которые генерирует класс PageLin kTagHe 1ре r ,
но жестко встраивать классы Bootstrap в код нежелательно , т.к. это затруднит пов
торное использование дескрипторного вспомогательного класса где-то в другом месте
[ViewContext]
[HtmlAttributeNotBound]
puЬlic ViewContext ViewCo n text { get ; set ;
!) Spo~•Stolf х
с Cc?..~~.:i~·_:~~Qip;;,~~![P,1g~~--=~-=.=-===·===·=:~~-~J
SPOПTS STOHE .
1 ll 2
l· ~Щ···-~~~~~~~~!~':~. '
Рис. 8.12. Приложение SportsStore с улучшенным дизайном
Резюме
В настоящей главе была построена основная инфраструктура для приложения
SportsStore. Пока что она не содержит достаточного набора функциональных средств,
чтобы их прямо сейчас можно было продемонстрировать пользователю , но "за кули
сами" уже имеются зачатки модели предметной области с хранилищем товаров , кото
рое поддерживается SQL Server и Entity Framework Core. В приложении присутствует
единственный контроллер ProductController, который может создавать разбитые
на страницы списки товаров, а также настроена ясная и дружественная к пользо ва
С Spo1tsStore Х
В этой главе могло показаться , что для получения весьма незначительных выгод
пришлось провести излишне трудоемкую начальную подготовку, но в следующей
главе равновесие будет восстановлено. Теперь, когда фундаментальная структура на
месте, можно двигаться дальше и добавлять все средства, ориентированные на поль
зователя: навигацию по категориям, корзину для покупок и начальную форму для
оплаты.
ГЛАВА 9
SportsStore: навигация
категории.
[Fact]
puЫic void Can_Paginate()
11 Организация
Mock<IProductRepository> mock = new Mock<IProductRepository>() ;
mock.Setup(m => m.Products) .Returns(new Product[]
new Product {ProductID = 1, Name Pl} ,
new Product {ProductID 2, Name Р2} ,
new Product {ProductID 3 , Name РЗ},
new Product {ProductID 4 , Name Р4} ,
new Product {ProductID 5 , Name Р5)
}) ;
Указывая null для category, мы получаем все объекты Product, которые контрол
лер извлекает из хранилища, что воспроизводит ситуацию перед добавлением ново
го параметра. Такого же рода изменение необходимо внести также в тестовый метод
Can_Send_Pagination_View_Model():
[ Fact]
p u Ыic void Can Send Pagination View Model() {
11 Организация
Mock<IProductRepository> mock = new Mock<IProductRepository>();
mock . Setup(m => m.Products) .Returns(new Product[ ] {
new Product {ProductID 1 , Name Pl} ,
new Product {ProductID 2 , Name Р2},
new Product {ProductID З , Name РЗ} ,
new Product {ProductID 4 , Name Р4} ,
new Product {ProductID 5 , Name Р5}
}) ;
11 Организация
ProductController controller =
new ProductController(mock . Object) { PageSize 3 };
11 Действие
ProductsListViewModel result =
controller.List(null, 2) .ViewData.Model as ProductsListViewModel;
Глава 9. SportsStore: навигация 247
11 Утверждение
Paginginfo pageinfo = result. Paging l nfo ;
Assert . Equal(2, pageinfo . CurrentPage) ;
Assert.Equal(З , pageinfo.ItemsPerPage) ;
Assert . Equal(5 , pageinfo .T otalitem s ) ;
Assert . Equal(2 , pageinfo .T ota l Pages) ;
Put something
useful here Soccer Ball
later FIFA-approved size and VJeight
Corner Flags
Give your playing field а professlonal
touch
Stadium
Flat-packed 35,000-seat stadium
11 " j
[ Fact ]
puЬlic void Can Filter Products() {
11 Ор ган изация
- создание имитирова н ного хранилища
Mock<IProductRepository> mock = new Mock<IProductRepository>() ;
mock . Setup(m => m.Products) .Returns(new Product[] {
new Product {ProductID 1 , Name Pl , Catego r y = Catl} ,
new Produ ct {ProductID 2 , Name Р2, Category Cat2} ,
new Product {Pr oductID 3, Na me РЗ, Category = Catl},
new Product {ProductID 4, Name Р4 , Category = Cat2} ,
new Product {ProductID 5 , Name Р5 , Category = СаtЗ}
}) ;
Этот тест создает имитированное хранилище, содержащее объекты Product , которые от
носятся к диапазону категорий . С использованием метода действия List () запрашивается
одна специфическая категория, а результаты проверяются на предмет наличия корректных
объектов в пр авильном поряд ке.
routes.MapRoute(
name: null,
template: Page{page:int},
defaul ts: new { controller = Product, action = List, page =1 }
) ;
routes.МapRoute(
name: null,
template: {category},
defaults: new { controller = Product, action = List, page =1 }
) ;
routes .MapRoute (
name: null,
template: ,
defaul.ts : new { controller = Product, action = List, page =1 }
) ;
routes.МapRoute(name: null, template: {controller}/{action}/{id?});
) ) ;
SeedData . EnsurePopu l ated(app) ;
[ViewContext]
[HtmlAttributeNotBound]
puЫic ViewContext ViewContext { get; set;
генерации URL. В представление был добавлен только один атрибут, но в словарь до
бавился бы любой атрибут с тем же самым префиксом.
SPORTS STORE 1
были соответствующим образом очищены . Вот модульный тест, который определяется в но
вом файле класса по имени Na vig ationMenuViewComponen t Te sts. cs внутри проекта
SportsSto r e .Tests :
using Sys t em . Collections . Generic ;
using System . Linq ;
using Microsoft.AspNetCo r e . Mvc . ViewCompon e nts ;
using Moq ;
using Spo r t sS tore . Component s ;
us i ng Sport s Store . Models ;
using Xun i t ;
namespace SportsStore . Te st s
puЬlic class Navigation MenuV i ewCompon ent Tests
[Fact]
p uЫic void Сап Sel e c t Cate g orie s () (
11 О рган иза ция
Mock< I ProductRepos i to r y> mock = n ew Mock<IProductRep os i to r y>() ;
mock . Setup(m => m. Products) .Returns(new Product[ ] {
new Pr oduct {Produ ctID 1 , Name Pl , Category Apples },
new Product {Product I D 2 , Name Р2 , Category Apples} ,
new Product {Produc t ID 3 , Name РЗ , Category Pl ums} ,
new Pr odu c t (P r o ductI D 4 , Name Р 4 , Categor y Oran g es} ,
) ) ;
NavigationMenuViewComponent target =
new Navi gationMenuVi ewComponent(mock . Object) ;
11 Действие - п олучени е набора кате г орий
st rin g [ ] resu l .ts = ( ( IEnumeraЫe< st ring>) ( targe t. Invoke ()
as Vi ewViewCompon en t Res u lt) .V i e wDa t a . Model ) . ToA r ray() ;
11 У тве рж д е н ие
Asse rt. True(EnumeraЫe . Sequence Equ al(new s t ri n g [] { App l es ,
Or anges , Plums } , r e s u lts )) ;
Создание представления
Как будет объясняться в главе 22, при работе с представлениями, которые выби
раются ком понентами представлений , механизм Razor использует разнообразные со
гл аш е ния . И стандартное имя представл е ния, и м естоположения , в которых ищется
256 Часть 1. Введение в инфраструктуру ASP.NET Саге MVC
@model IEnumeraЫe<string>
CJ Spo1tsStor~ Х
~ ".~ С 1t _
<D_loca
_ll10st:60000/Soccer/Pa9e1
_ _ _ _ __ _ _ _ _ ,
SРОПП.~ SЮПЕ
Home 1
1
Chess Soccer Ball
FIFA-approved size and weight 1
Soccer
1
Wa!ersports 1
1
1
.; ...J
using Microsoft.AspNetCore.Mvc ;
using System.Linq;
using SportsStore.Models ;
namespace SportsStore . Components
puЫic class NavigationMenuViewComponent : ViewComponent {
private IProductRepository repository;
puЬlic NavigationMenuViewComponent(IProductRepository repo)
repository = repo ;
Для выполнения проверки того , что компонент представления корректно добавил детали о
выбранной категории, в модульном тесте можно прочитать значение свойства ViewBag,
которое доступно через класс ViewViewComponentResult, описанный в главе 22 . Ниже
показан модульный тест, добавленный в класс NavigatioMenuViewComponentTests:
[Fact]
puЬlic void Indicates Selected Category()
11 Организация
string categoryToSelect = App les;
Mock<IProductRepository> mock = new Mock<IProductRepository>();
mock . Setup(m => m. Products) . Returns(new Product(] {
new Product {ProductID 1 , Name Pl , Category Apples} ,
new Product {ProductID = 4 , Name = Р2 , Category = Oranges} ,
}) ;
NavigationMenuViewComponent targe t =
new NavigationMenuViewComponent(mock . Ob j ect) ;
target . Vi ewComponentContext = new ViewComponentContext
ViewCon te xt = new ViewContext {
RouteData = new RouteData()
}
};
target .RouteData . Values(category] = categoryToSelect;
11 Действие
string result = (string) (target.Invoke() as
ViewViewComponentResult) . ViewData(SelectedCategory] ;
11 Утверждение
Assert .Equ al(categoryToSe le ct , result};
Теперь , когда доступна информация о том, какая категория выбрана, можно обно
вить пр едставление, выбираемое компонентом представления, чтобы задействовать
эту информацию, и и зм енить классы CSS, которые используются для стили зации
ссылок, сделав представление текущей категории отличающимся от остальных кате
горий . В листинге 9 .11 приведено изменение, внесенное в файл Defaul t . cshtml.
Глава 9. SportsStore: навигация 259
Листинг 9.11. Подсветка текущей категории в файле Defaul t. cshtml
D Sport1Stort Х
SPOf1fS SIORE
Home
Thinking Сар
Soccer
lmprove brain ef!lclency Ьу 75%
Waterspol1s
1
~Ch~--"'-~~j
Рис. 9.4. Подсвечивание выбранной категории
IJ SportsStore Х
f- С ~.;;;;IOOO/Chess/P•g".2
1.3
SРОПТS STOHE:
Home
Chess • '
Soccer
Wale r~ports 1
1
i
_:_ ___- - -' 1
Рис. 9.5. Отображение некорректных ссылок на страницы, когда выбрана какая-то категория
С) Spott~S1 o rt Х
r: -~- ---· --- - - - - · - - - - · - - -··-··-------]
J ~ • С l-~~~~lh~:_~:::_~c!'.':~~"2.:~--- ----------·-----~-
Sf 'Olrl"!-> ~ТОНЕ
Нота
Тhinking Сар
Soccor lmprove brain efficiency Ьу 75%
Watorsports
[Fact]
puЬlic void Generate_Category_Specific_Product_ Count() {
11 Организация
Mock<IProductRep os itory> mock = new Mock<IProductRepository>() ;
mock . Setup(m => m.Products) .Returns(new Product[] (
new Product {ProductID 1, Name Pl , Category Catl},
new Product {ProductID 2 , Name Р2 , Category = Cat2},
new Product {ProductID 3 , Name РЗ, Category = Catl},
new Product {ProductID 4 , Name Р4, Category = Cat2},
new Product {ProductID = 5 , Name PS , Category = СаtЗ)
}) ;
11 Действие
int? resl GetModel(target .Li st(Catl) )?.Paginginfo.Totalitems;
int? res2 = GetModel(target .List(Cat2) )?.Paginginfo.Totalitems;
int? resЗ = GetModel(target.List(CatЗ) )?.Paginginfo.Totalitems ;
int? resAll = GetModel(target.List(null))? . Paginginfo.Totalitems;
11 Утверждение
Assert.Equal(2 , resl);
Assert.Equal(2, res2) ;
Assert .Equal(l, resЗ);
Assert .Equal(5 , resAll);
Обратите внимание, что в модульном тесте также вызывается метод Lis t () без указания
категории, чтобы удостовериться в правильности подсчета общего количества товаров .
...ii(-----11Продолжить покупку 1
Класс Cart использует класс CartLine, который определен в том же самом файле
и представляет товар, выбранный пол ьзователем, а также приобретаемое его коли
чество. Мы определили методы для добавления элемента в корзину, удаления элемен
та из корзины, вычисления общей стоимости элементов в корзине и очистки ко рзины
путем удаления всех элементов. Мы также предоставили свойство, которое позволяет
обратиться к содержимому корзины с применением IEnumeraЬle<CartLine>. Вс е
это легк о реализуется с помощью кода С# и небольшой доли к ода LINQ.
Класс Cart относительно прост, но в нем присутствует ряд важны х линий поведения , кото
рые долж ны корректно работать. Неправильно фун к ционирующая корзина нарушит работу
всего приложения SportsStore . Мы разобьем средства на части и протестируем их по отде
льности. Для раз ме щения тестов в проекте SportsStore . Tests создается новый файл
по имени CartTests . cs .
Первая линия поведения относится к добавлению элемента в корзину. При первоначаль
ном добавлении в корзину объекта Product должен быть добавлен новый экземпляр
CartLine. Ни же представлен тестовый метод вместе с определением класса модульного
тестирования.
264 Часть 1. Введение в инфраструктуру ASP. NEТ Core MVC
Но если пользователь уже добавлял объе кт Produ ct в корзину, тогда необходимо уве
личить количество в соответствующем экземпляре CartLine , а не создавать новый . Вот
модульный тест:
[Fact]
puЫic void Can Add Quantity For Existing Lines() {
11 Организация- создание нескольких тестовых товаров
Product pl = new Product { ProductID = 1, Name Pl };
Product р2 = new Product { Prod uctID = 2 , Name = Р2 };
11 Организация - создание новой корзины
Cart target = new Cart() ;
11 Действие
target.Additem(pl , 1 ) ;
target . Additem(p2 , 1) ;
target.Add i tem(pl , 10) ;
CartLine [] res ults = target .Lines
. OrderBy(c => c.Product.ProductI D) .ToAr ray() ;
11 Утверждение
Assert . Equal(2 , results.Length);
Assert . Equal(ll , results[O] .Quantity) ;
Assert . Equal(l , results[l ] . Quantity) ;
Нам также необходимо проверить, что пользователи имеют возмо ж ность менять свое
решение и удалять товары из корзины. Эта линия поведения реализуется методом
RemoveLine () . Модульный тест выглядит следующим образом :
Глава 9. SportsStore: навигация 265
[Fact]
puЫic voi d Can_ Remove Line () {
//Организация - создание нескольких тестовых товаров
Product pl new Product { ProductID 1 , Name Pl ) ;
Product р2 = new Product { Product I D = 2 , Name Р2 ) ;
Product рЗ = new Product { ProductID = 3 , Name РЗ ) ;
Далее проверяется лин ия поведения , связанная с возмож ностью вычисления общей стои
м ости элементов в корзине . Вот модульный тест:
[ Fact ]
puЫic void Calculate_Cart_ Total() {
11 - создание нескольких тест овых
Организация товаров
Product pl = new Product { ProductID = 1, Name Pl , Pri ce lOOM );
Product р2 = new Produc t { ProductID = 2 , Name = Р2 , Price 50М );
/ / Организация - создание новой корзины
Cart target = new Cart();
11 Действие
target.Additem(pl , 1);
target.Additem(p2 , 1);
target.Additem(pl , 3) ;
decimal result = target . ComputeTo t a l Va lue () ;
11 Утверждение
Assert.Equa l (450M , result) ;
[Fact ]
puЫic void Can_ Clear Contents() {
11 Организация- создание нескольких тест овых товаров
Product pl new Product { ProductID 1 , Name Pl , Pri ce lOOM } ;
Product р2 = new Product { ProductI D = 2 , Name = Р2 , Price 50М );
266 Часть 1. Введение в инфраструктуру ASP.NET Саге MVC
Временами , как в данном случае , код для тестирования функциональности класса получает
ся намного длиннее и сложнее, чем код самого класса . Не допускайте, чтобы это приводило
к отказу от написания модульных тестов. Дефекты в простых классах, особенно в тех, кото
рые играют настолько важную роль, как Cart в приложении SportsStore, могут оказывать
разрушительное воздействие.
@using SportsStore.Models
@using SportsStore.Models . ViewMode ls
@using SportsStore.Infrastructure
@addTagHelper * , Microsoft . AspNetCore.Mvc . TagHelpers
@addTagHelper SportsStore.Infrastructure.* , SportsStore
Глава 9. SportsStore: навигация 267
В листинге 9.16 показано частичное представление, описывающее каждый товар,
которое обновлено для включения кнопки Add То Cart (Добавить в корзину).
<strong>@Model . Name</strong>
<span class=pull-right label label - primary>
@Model.Price.ToString(c)
</span>
</hЗ>
<form id=@Model.ProductID asp-action=AddToCart
asp-controller=Cart rnethod=post>
<input type=hidden asp-for=ProductID />
<input type=hidden name=returnUrl
value=@ViewContext.HttpContext.Request.PathAndQuery() />
<span class=lead>
@Model.Description
<button type=suЬmit class=btn btn-success btn-srn pull-right>
Add То Cart
</button>
</span>
</forrn>
</div>
На заметку! Обратите внимание, что атрибут method элемента form установлен в post,
что инструктирует браузер относительно отправки данных формы с использованием
НТТР-метода POST . Вы можете это изменить и заставить форму применять НТТР -м етод
GET, но дол ж ны соблюдать осторожность . Спецификация НТТР требует, чтобы запросы
GET были идемпотентными, т.е . они не должны приводить к изменениям, а добавление
товара в корзину определенно считается изменением. Более подробно эта тема будет
обсуждаться в главе 16, включая объяснение того, что может произойти, если проигнори
ровать требование идемпотентности запросов GET.
ASP.NET предлагает целый ряд разных способов хранения состояния сеанса, в том
числе хранение его в памяти, что мы и будем применять. Преимуществом такого под
хода является простота , но данные сеанса будут утеряны, когда приложение останав
ливается или перезапускается.
Листинг 9.17. Добавление пакетов в файле project. j son внутри проекта SportsStore
dependencies : {
Microsoft . NETCore . App :
version : 1 . 0.0,
type : platform
} 1
Параметр типа позволяет задать тип объекта, который ожидается извлечь; этот
тип используется в процессе десериализации.
Действи е Index извлекает объ ект Cart из состояния с е анса и использует его для
со здания объ е1па Car t indexViewModel, которы й зате м пер едается методу View ()
для пр име н е ния в качеств е модели пр едставл е ния.
Посл едний шаг при отображении содержимого кор зины предусматрива ет созда
ние пр едставл ения , которое будет визуализировать действи е I ndex. Со здайте папку
Vi ews/Cart и поместите в н ее файл представления Razor по им е ни Index . csh t rnl с
размет кой, приведенной в листинг е 9.23.
<tbody>
@foreach (va r li n e in Model . Ca r t .L i n es) {
<tr>
<td class = t e xt - center>@line . Quant i ty</td>
<td class=t e xt - left>@line . Prod u ct . Name</td>
<td class = text - r i g h t>@ l i n e . Pr o du c t. Price . ToString (c)</td>
<td c l ass= t ext - ri gh t>
@( (l ine . Qua n t i t y * l i n e . Pr o duct. Pri ce ) . ToSt r ing(c))
</td>
</tr>
)
</tbody>
274 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
<tf oo t >
<tr >
<t d c ol s p a n=З c l a s s =t ext-right>Total: </ t d>
<t d class= t ex t- right>
@Model . Cart .ComputeTo t a l Va l ue () .ToSt ring(c)
</td>
</tr>
</ tfoot >
< / t аЫ е>
=-==-------==----=
D SportsStor. х
С [2 l~calh~t:бO_ooo..:;_=;c=~.=,=,fP=age1 fJ
SPO! !TS STOHE
Н от в
Thinking Сар
lmprove brai11 efficiency Ьу 75%
Soccer
Watersports
С] Spo r tsSto r~ Х
~ с @)-io~~~·~0ooo/c;~"1nd~~'::"~~~~2~~~~--~~=~=---~·==-~=--=~
Sf'ofПS STOllE.
1
Home
1
Your cart
Chess
1 Quantity ltem Price SuЫota l
1 Soccer
.! Tl1 inking Сар 51 6.00 $16.00
Wat erspoГis
1 Unsteady Chair 529.95 S29.95
1
Резюме
В настоящей главе мы начали расширять пользовательские части приложения
SportsStore. Мы предоставили средства. с по мощью которых пользователь может пе
реходить по категориям, и создали базовые строительные блоки, позволяющие добав
лять элементы в корзину для покупок. В следующей главе разработка приложения
будет продолжена.
ГЛАВА 10
SportsStore:
завершение постр оения
Усовершенствование модели
корзины с помощью службы
В предыдущей главе был определен класс модели Ca r t и продемонстрирована воз
можность его сохранения с использованием средства состояния сеанса, что давало
[Jsonignore]
puЫic ISession Session { get ; set; }
puЫic override void Additem(Product product , int quantity) {
base.Additem(product , quantity) ;
Session . SetJson( " Cart ", this) ;
Регистрация службы
Сл едующий шаг заключается в создании службы для rшасса Cart . Цель в том,
чтобы удовлетворять запросы для объектов Cart выдачей объектов Session Cart,
которые будут сохранять себя самостоятельно. Создание службы иллюстрируется в
листинге 10.2.
278 Часть 1. Введение в инфраструктуру ASP.NET Со ге MVC
@model Ca r tindexViewModel
<h2>Your cart</h2>
<tаЫе class= " taЫe taЬle - bordered taЬle - striped " >
<thead>
<tr>
<th>Quantity</th>
<th> Item</th>
<th class= " text - right " >Price</th>
<th class= " text - right " >SuЬtotal</th>
</tr>
</thea d>
<tbody>
@foreach (var line in Model . Cart .L ines) {
<tr>
<td class= " text - center " >@line . Qu antity</td>
<td class= " text -l eft " >@ line.Product.Name</td>
<td class= " text-right " >@ l ine . Product.Price .T oString( " c " )</td>
<td class= " text - right " >
@( (line . Quantity * line. Product . Price) . ToString ( " с " ))
</td>
<td>
<form asp-action="RemoveFromCart" method="post">
<input type="hidden" name="ProductID"
value="@line.Product.ProductID" />
<input type="hidden" name="returnUrl"
value="@Model.ReturnUrl" />
<Ьutton type="suЬmit" class="Ьtn Ьtn-sm Ьtn-danger ">
Remove
</button>
</form>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan= " З " c l ass= " text-right " >Total : </ td>
<td class= " text - right " >
@Mode l .Cart.ComputeTotalValue() . ToString( " c " )
</td>
</tr>
</tfoot>
</tаЫе>
IJ Sport$Stor~
~ с [~~ r;,~1~~~~~~-~;~~:~?~;~;~.n::~~~s~;-~~~~~~~~-~-=·.~~==--· ~l _
Homo
Your cart
Chess
Ouonlity ltem Price SuЬtota l
Soccer
""
Thlnking Сар S16.00 $16.00
Wate,sports
Unsleady Cl1В ir
Stadium
S29.95
$79,500.00
Total:
S29.95
$79,500.00
$79,545.95
1111 1
1
$79.500.00
Total:
$79.500.00
$79.5 16.00
"
11111
Выберите проект SportsStore и щелкните на кнопке Show All ltems (Показать все
элементы) в верхней части окна Solution Explorer, чтобы отобразить ф айл bower . j s on.
Добавьте пакет Font Awesome в раздел dependencies этого файла (листинг 10.5).
После сохранения файла bower . j son среда Visual Studio с помощью инструмента
Bower загрузит и установит пакет Font Awesome в папку www/liЬ/fontawesorne.
yak
,oat for one person
Отп ра в ка з а казо в
Итак, мы добрались до финального пользовательского средства приложе ния
SportsStore: возможности пер ехода к оплате и оформлению заказа. В последующих
разделах мы расширим модель предметной области , чтобы обеспечить поддержку по
луч ения от пользователя под робной информации о достав1<е . и добавим в прилож е ни е
обработку этой информации.
Листинг 10.10. Добавление кнопки Checkout в файле Index. cshtml из папки Views/Cart
Ноте
Your cart
Chess
Quantity ltem Price SuЫotaJ
Soccer
"
Ufejacket Sд8.95 $48.95
Watersports
Ри с. 10 . З . К ноп к а Ch eckout
Метод
Checkout () возвращает стандартное представление и передает новый объ
ект ShippingDetails в качестве модели представления. Чтобы создать представле
ние, создайте папку Views/Order и поместите в нее файл представления Razor по
имени Checkout. cshtml с разметкой. п оказанной в листинге 10.12.
Листинг 10. 12. Содержимое файла Check out. c shtml из папки Views /Or d er
@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" >
Глава 1О . SportsStore: завершение построения корзины для по купо к 287
<hЗ>Ship to</hЗ>
<div class= " form - group " >
<label>Name:</labe l ><input asp-for="Name" class="form-control " />
</d i v>
<hЗ>Address</hЗ>
<di v class= " form -gr oup">
<label>Line 1 :</label><input asp -for="Linel" class="form-control " />
</div>
<div class= "form-gr oup " >
<label>Line 2 :</labe l><inp ut asp-for="Lin e2 " class="form-control " />
</div>
<div class= "form-group" >
<label>Line 3 : </label><input asp -f or= " LineЗ" c lass ="form- control" />
</div>
<div class= " form -gr oup ">
<label>City : </label><input asp-for="City " class= " form - control" />
</div>
<div class= "form - group " >
<label>State:</label>< inp ut asp-for= "S tate " class= " form - control" />
</div>
<div class= " form -group" >
<label>Z ip:</label><input asp - for= " Zip " class= " form - cont rol" />
</div>
<div class= " form -gr oup " >
<label>Country : </label><input asp-for= "Country" class="form-control" />
</div>
<hЗ>Options</hЗ>
<div class="checkbox">
<label>
<input asp- f or ="Gi ftWrap " /> Gift wrap these items
</label>
</div>
<div class= " text - center " >
<input class= " Ьtn Ьtn-primary " type= "submit" value= "Complete Order " />
</div>
</form>
Для каждого свойства в модели мы создали элементы label и input для поль
зовательского ввода, сформатированные с помощью Bootstrap. Атрибут asp -f or
в элементах input обрабатывается встроенным дескрипторным вспомогательным
классом, который генерирует атрибуты type , id, name и value на основе указанного
свойства модели, как объясняется в главе 24.
Чтобы увидеть результат добавления нового метода действия и представления
(рис. 10.4). запустите приложение, щелкните на кнопке со значком корзины в верх
ней части страницы и затем щелкните на кнопке Checkout (Оплата) . Попасть на это
представление можно также, запросив URL вида /Order/Checkout.
С) SportsStore
1 f- ·· С
Х
ГФ' 1ocalh;~;;QOO{o~-.~/Checka<1 1 ________________ __ " ______ */
1 ·-------------··-----·-·---·-·----·---··--·-"·--J
Sl'OfHS ~~ТОНЕ а
Horno
Chess
Check out now
Please enter your details, and \'1e'll ship your goods rk;ht 8\vay!
Soccer
Ship to
Waterspor1s
Name:
Address
Une 1:
Une2:
Un eз:
City:
State:
Zip:
Country;
Options
О Gift wrnp 11-.ese items
.•",.
Рис. 10.4. Форма для сбора деталей о доставке
Update - Database
В окне проводника объектов SQL Server появится новый элемент, который можно раскрыть ,
чтобы увидеть созданные базы данных LocalDB. Щелкните правой кнопкой мыши на име
ни базы данных , которую вы хотите удалить, и выберите в контекстном меню пункт Delete
(Удалить). В открывшемся диалоговом окне отметьте флажок для закрытия всех существу
ющих подключений и затем щелкните на кнопке ОК, чтобы удалить базу данных .
После того , как база данных удалена, запустите в консоли диспетчера пакетов следующую
команду, чтобы создать базу данны х и применить подготовленные миграции:
Update - Database
Эта команда переустановит базу данных, так что она в точности отразит вашу модель и
позволит возвратиться к разработке приложения.
Чтобы предоставить доступ 1< объектам Order, мы последуем тому же самому шаб
лону, который использовался для хранилища товаров. Добавьте в папку Models файл
класса по имени IOrderReposi tory. cs и определите в нем интерфейс, приведенный
в листинге 10.14.
290 Часть 1. Введение в инфраструктуру ASP.NET Саге MVC
context.SaveChanges();
Это гарантирует получение всех объектов данных, которые нужны, не выполняя запросы и
не собирая данные напрямую.
Дополнительный шаг требуется та кже и при сохранении объекта Order в базе данны х .
Когда данные корзины пользователя десериализируются из состояния сеанса, пакет JSON
создает новые объе кты, не известные инфраструктуре Eпtity Framework Core , которая затем
пытается записать все объекты в базу данных. В случае объектов Produ ct это означает,
что инфраструктура EF Core попытается записать объекты, которые уже были сохранены,
что приведет к ошиб ке. Во избежание проблемы мы уведомляем Eпtity Framework Core о
том , что объекты существуют и не должны сохраняться в базе данных до тех пор, по ка они
не будут модифицированы:
if (ModelState.IsValid) {
order . Lines = cart.Lines.ToArray();
repository.SaveOrder(order);
return RedirectToAction(nameof(Completed));
else {
return View(order);
Инфраструктура МVС контролирует огран ич е ния пров ерки достов е рности, кото
рые были прим е н е ны I< классу Order посредством атрибутов аннота ций данных , и
через свойство ModelState сообщает м етоду действия о любых пробл емах . Чтобы вы
яснить , есть ли пробл е мы, мы проверяем свойство ModelState . IsValid. Мы вызы
ваем м етод Mode l State. AddModelError () для р е гистрации сообщения об ошибке,
есл и в корзине н ет элементов. Вскоре мы объясним , как отобр ажать таки е сообщения
об ошибках , а более подробное описание привязки модел ей и пров е рки достов е рности
будет представлено в гл авах 27 и 28.
Тест проверяет отсутствие возмож ности перехода к оплате при пустой корзине. Мы удосто
веряе м ся , что метод SaveOrder () и митированной реализации IOrderRepos i tory ни
когда не вызывается, что метод возвращает стандартное представлен и е ( которое повторно
отобразит введенные пользователем данные, давая ему шанс откорре ктировать их) и что
состояние модели , передаваемое представлению, помечено ка к недопустимое. Это мо жет
выглядеть ка к и злишн е ограничивающий набор утверждений, но для провер к и правильности
поведения нуж ны все три утвер ждения. Следующий тестовый ме тод работает в основном
так же , но внедряет в модель представления ошибку, эмулирующую проблему, о которой
сообщает средство привяз ки модели (что долж но происходить в производственной среде,
когда пользователь вводит не корректные данные о достав ке):
294 Часть 1. Введение в и нфрастру ктуру ASP.NEТ Соге MVC
[Fact ]
puЫic void Cannot_ Checkout Invalid_ShippingDetails() {
//Организация - создание имитированного хранилища заказов
Mock<IOrderRepository> mock = new Mock<IOrderRepository>() ;
/ / Организация - создание корзины с одним элементом
Cart cart = new Cart() ;
cart . Additem(new Product() , 1) ;
//Организация - создание экземпляра контроллера
OrderControlle r target = new OrderController(mock . Object , cart) ;
//Организация - добавление ошибки в мо д ель
target.ModelState . AddModelError( " error ", " error " ) ;
// Действие - попытка перехода к оплате
ViewResult result = target.Checkout(new Order()) as ViewResult ;
11 Утверждение - проверка , что заказ не был сохранен
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_S u bmit_Order() {
//Организация - создание имитированного хранилища заказов
Mock<IOrderRepository> mock = new Mock<IOrderRepository>() ;
//Организация - создание корзины с одним элементом
Cart cart = new Cart() ;
cart.Additem(new Product() , 1) ;
//Организация - соз д ание экземпляра контроллера
OrderController target = new OrderController(mock . Object , cart) ;
// Действие - попытка перехода к оплате
RedirectToActionResult result =
target.Checkout(new Order()) as RedirectToActionResult ;
//Утверждение - проверка , что заказ был сохранен
mock . Verify( m => m. SaveOrder(It . IsAny<Order>()) , Times . Once) ;
D SportsStore Х
~ • С l CD lo~alh~t:бOOOOjQ~d;;/o~-::~-~~'----=-~====-==--~
SPOH ГS STOf~Г:: . . а
Нот е
Ship to
Name:
Home Thanks!
1
Chess Thanks fог placing your огdе г.
Резюме
Мы завершили все основные части приложения SportsStore, отвечающие за вза
имодействие с пользователями. Конечно, до сайта Aшazon приложению далеко. но в
нем имеется каталог товаров с возможностью просмотра по категориям и страницам,
Управление заказами
В предыдущей главе была добавлена поддержка для получения заказов от пользо
вателей и сохранения их в базе данных. В этой главе мы собираемся создать простой
инструмент администрирования, который позволит просматривать получ е нные зака
зы и помечать их как отгруженные.
Расширение модели
Первым изменением, которое необходимо внести, является расширение модели ,
чтобы можно было фиксировать, какие заказы были отгружены. В листинге 11.1
показано добавление нового свойства в класс Order , который определен в файл е
Order. cs внутри папки Models.
[BindNever]
puЬlicbool Shipped { get; set; }
[Required(ErrorMessage =" Please enter а name " )]
/ / Введите имя
puЫic string Name { get; set; )
[Required(ErrorMessage = " Please enter the first address line")]
11 Введите пер вую строку адреса
Глава 11 . SportsStore: администрирование 299
p u Ьlic str ing Line l get; set;
puЫ i c str ing Line2 get; s et ;
puЫic stri ng LineЗ get; set;
[Required(ErrorMessage = "Pl ease enter а city name " ) ]
11 Вв едите название г орода
puЫic string City { get ; set; }
[Required (ErrorMessage = "Plea se e n ter а state name")]
11 Вве дите наз вание штата
puЫic string Sta te { get ; s et; }
puЫic st ri ng Zip { get; set ; }
[Required(ErrorMessage = "Ple ase ente r а country name")]
11 Вв едит е название страны
puЫic string Country { get ; s et ; }
рuЫ ic boo l Gi ftW r ap { get ; set; }
return RedirectToAction(narneof(List));
if (ModelState . IsValid) {
order . Lines = cart . Lines . ToArray() ;
repository . SaveOrder(order) ;
return RedirectToAction(nameof(Completed)) ;
else {
return View(order) ;
Метод List () выбирает все объекты Order в хранилище, свойство Shipped кото
рых имеет значение false , и передает их стандартному представлению . Этот метод
действия будет использоваться для отображения администратору списиа неотгружен
ных заказов.
@mode l IEnumeraЫe<Order>
@{
ViewBag.Title = "Orders ";
Layout = "_AdminLayout ";
}
@if (Model . Count() > 0) {
<tаЫе class= " taЫe taЫe - bordered taЫe-striped" >
<tr><t h>Name</th><th>Zip</th>< t h colspan=" 2" >Detail s</th><t h></th></ tr>
@foreach (Order о in Model) {
<tr>
<td>@o . Name</td><td>@o . Zip</td><th>Product</th><th>Quantity</th>
<td> .
<form asp - ac ti on="Ma rkS hipped " method="post " >
<i nput type= "hidden " name ="orderid" value= " @o.OrderID " />
<butt on type= " s ubmit" class= "btn btn - sm btn -danger " >
Ship
</button>
</form>
</td>
</tr>
@foreach (CartLine line in o . Li nes) {
<tr>
<td colspan= " 2 " ></ t d>
<td>@line . Product . Name</td><td>@l ine. Quantity</td>
<td></td>
</tr>
</tаЫе>
else {
<div class= " text - center " >No Unshipped Orders</d i v>
~ Ord<rs Х
1
Name Zip Details
Lifejacket
Quantity
• 1
1
l ______ -
1
Soccer Вall
1
- - - - - "" ______ " ____ - · - - - - ",_" ____ ·-·--- -- - · - - - J
//Утверждение
Assert . Equal(З , result.Length) ;
Assert.Equal( " Pl " , result [ OJ . Name) ;
Assert . Equal( " P2 " , result[l] . Name) ;
Assert.Equal( " PЗ ", result[2 ] . Name) ;
В тест был доб авлен метод GetViewModel () для расп аков к и ре зультата, воз враща е м ого
методом де йствия, и получен и я данных модели представления . Далее в глав е будут реали
зованы дополнительные тесты , к оторые используют этот метод .
@model IEnumeraЫe<Product>
@{
ViewBag . Title = "All Produ cts ";
Layout = " _Adm inLayout";
<tаЫе class= " taЫe taЫ e -strip ed taЬle-bordered taЬle-condensed " >
<tr>
<th class= "text - right">ID</th>
<th>Name</th>
<th c l ass= "text - right " >Price</t h >
<th class= " text - ce n ter " >Actions</t h >
</tr>
@foreach (var item in Model) {
<tr>
<td class="text-right " >@ item.ProductID</td >
<td>@item . Name</td>
<td class= " text - right " >@item .P rice .T oStr in g (" c ") </td>
<td c l ass= " text - ce n ter " >
<form asp - ac ti on= " De l ete " method="post " >
<а asp-action= " Edit" class= "btn btn-sm btn -warni n g "
asp - route - productid= " @item .Produ ct I D" >
Edit
</а>
<input type= " hidden " name= " ProductID " value= " @item .Produ ctID " />
<button type= "s ubmit " class = "b tn btn - danger btn - sm">
Delete
</button>
</form>
</td>
</tr>
</tаЫе>
<div class= " text - cen te r" >
<а asp - act i on= " Create " class= "Ьt n Ьtn - primary " >Add Product</a>
</div>
Совет. Кнопка Edit (Редактировать) находится внутри элемента form в листинге 11.6, так что
две кнопки располагаются рядом благодаря примененному интервалу Bootstrap. Кнопка
Edit будет посылать серверу НТТР-запрос GET для получения текущих сведений о товаре;
это не требует элемента form. Однако поскольку кнопка Delete (Удалить) будет вносить
изменение в состояние приложения , необходимо использовать НТТР-запрос POST, кото
рый требует элемента form.
306 Часть 1. Введение в инфраструктуру ASP.NET Саге MVC
D All Products Х
1 Kayak $275.00
"111
2 l ilejackel
3 Soccer ВaJI
4 Corner Flags
$48.95
$19.50
$34.95
., . "
Е181
5 S\adium 579.500.00
B lll
6 Т11in king С а р $16.00
-· 1111
7 Unsleady Chair $29.95
131111
8 Human Cl1ess Board $75.00
"' 111
9 Bling..Bling Кing $1,200.00
111 1
1
1
ФФtil
1
[__· - · - - - - - - ___._______ j
Рис. 11.3. Отображение списка товаров
[Fact]
puЬlic void Can_Ed i t Prod uct( ) {
11 Организация - создание имитирова нн о г о храни л ища
Mock<IProductRepository> mock = new Mock<IProduc t Repository>() ;
moc k . Setup(m => m.Products) .Returns(ne w Produ ct [ ] {
new Product {ProductID 1, Name " Pl " ) ,
n e w Product { Product I D 2 , Name " Р2 "),
new Product {Product I D З , Na me " РЗ "},
}) ;
11 Организация - созда н ие ко н т р ол лера
AdminC o ntrol l er target = n ew Admi n Contro ll er(mock . Obj ect );
11 Д ействие
Pro duct pl GetViewMode l <Product>(target . Edit(l)) ;
Product р2 = GetV i ewModel <Product>(target . Edit(2)) ;
Product рЗ = GetViewMode l <Product>(ta rg e t.E dit(З) );
11 Утверждение
Assert . Equal(l , pl.Produ ct I D) ;
Assert . Equal(2 , p2 . Prod u ct ID ) ;
A ssert . Eq u al(З , pЗ . Prod u ct ID ) ;
[Fa c t ]
puЬlic void Cann ot_Edit_Non ex is te nt _ Pr o du ct( ) {
11 Организация - созда ни е ими т ирован н о г о хранилища
Mock<IProductRepository> mock = new Mock<IProductRepository>() ;
308 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
целей для элементов form и а, установку содержимого элементов label и выдачу ат
рибутов name, id и value для элементов input и textarea .
Чтобы увидеть НТМL-разметку, генерируемую представлением, запустите прило
жение, перейдите на URL типа /Adrnin/Inde x и щелкните на кнопке Edit для одного
из товаров (рис. 11.4).
1 f- С i ф lo;a lhostбO·JOO/P.dm111/Edit?productld=3
1 -
i1 Edit Product
j Name
1 Soc.cer Ball
1 Description
Category
Soccer
1
Price
19.50
'
i
i - Cancel 1
1
j_ ·- - ----- ---- ---- _1
Рис. 11.4. Отображение сведений о товаре для редактирования
Совет. Скрытый элемент input для свойства ProductID применяется ради простоты.
Значение ProductID генерируется базой данных как первичный ключ, когда новый объ
ект сохраняется инфраструктурой Eпtity Framework Саге, и его безопасное изменение мо
жет оказаться сложным процессом.
context.SaveChanges();
частью ASP.NET Core МVС. Тем не менее, в методе SaveProduct () есть кое-что, что
оказьmает влияние на проектное решение, положенное в основу приложения МVС.
Глава 11. SportsStore : администрирование 311
Нам известно, что обновление должно выполняться, когда получен параметр
Product, который имеет ненулевое значение ProductID. Это делается путем извле
чения из хранилища объекта Product с тем же самым значением ProductID и об
новления всех его свойств, чтобы они соответствовали значениям свойств объекта,
переданного в качестве параметра.
Причина таких действий в том, что инфраструктура Entity Framework Core отсле
живает объекты, которые она создает из базы данных. Объект, переданный методу
SaveChanges () , создается системой привязки моделей MVC , т.е. инфраструктура
Entity Framework Core ничего не знает о новом объекте Product, и она не будет при
менять обновление к базе данных, иогда объеит Product модифицирован. Существует
множество способов решения уиазанной пробл емы, но мы принимаем самый простой
из них. предполагающий поиск соответствующего объекта, о котором известно инф
раструктуре Entity Framework Core, и его явное обновление.
Добавление нового метода в интерфейс IProductRepository нарушает работу
класса имитированного хранилища FakeProductReposi tory, который был создан
в главе 8. Имитированное хранилище использовалось для быстрого старта процес
са разработки и демонстрации возможности применения служб для гладкой заме
ны реализаций интерфейса, не изменяя компоненты, которые на них опираются.
Имитированное хранилище больше не понадобится. В листинге 11.11 видно, что ин
терфейс IProductReposi tory удален из объявления класса, поэтому продолжать мо
дификацию класса по мере добавления функций хранилища не придется.
namespace SportsStore.Controllers {
puЫic class AdminContr oll er : Controller
private IP roductRepository repository ;
puЬlicAdminContro l ler( I ProductRepository repo) {
repository = repo ;
return RedirectToAction("Index");
else {
11 Что-то не так со значениями данных
return View(product);
[Fact]
puЫic void Can Save_ Valid_ Changes() {
11 Организация - создание имитированного хранилища
Mock<IProductRepository> mock = new Mock<IProductRepository>() ;
11 Организация - соз д ание имитированных временных данных
Mock<ITempDataDictionary> tempData = new Mock<I TempData Dictionary>() ;
11 Организация - создание контроллера
AdminController target = new AdminCont r o l ler(mock . Object) {
TempData = tempData . Object
};
11 Организация - создание товара
Product product = new Product { Name = "T est " };
11 Действие - попытка сохранить товар
IActionResult result = target . Edit(product);
11 Утверждение - проверка того , что к хранилищу был о произведено обращение
mock.Verify(m => m. SaveProduct(product)) ;
11 Утверждение - проверка , что типом р езультата является перена правление
Assert . Is Type<Redirect ToActionResu l t>(result) ;
Assert . Equal( " Index ", (result as Red i rectToActionResult) .ActionName);
[Fact]
puЫic void Cannot_Save_ Invalid_ Chang e s() {
11 Организация - создание имитированно г о хранилища
Mock<IProductRepos i tory> mock = n ew Mock<IProductRepository>() ;
11 Организа ция - созда н ие контроллера
AdminController target = new AdminController(mock . Object) ;
11 Организация - создание товара
Product product = new Product { Name = " Test " };
11 Организация - добавле н ие ошибки в состояние модели
target . ModelState . AddModelE r ro r ( " e r ror ", " error " ) ;
11 Действие - попытка сохранить товар
IActionResult resu l t = target . Edit(produ ct) ;
11 Утверждение - проверка того , что к хранилищу был о произведено обращение
mock.Verify(m => m. SaveProduct(It.IsAny<Produ ct>()) , Times.Never()) ;
11 Утверждение - проверка типа результата метода
Assert . IsType<ViewRes u l t >(result) ;
314 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC
@RenderBody ()
</div>
</body>
</html>
будут по-прежнему видеть это сообщение (при условии, что следующее представление
использует ту же самую компоновку).
"~
2 Lifejackel $48.95
5 Stadium
$34.95
579,500.00 .""
".
1 Са
111
----·-------~
6 ThinkingCap $16.00
--
7 Unstaady Chair $29.95
l_ "_ ·-----
·-
в Human Chess Вoard $75.00
!
IИIU 1
. _________.____________ ._ _1I
L
Рис. 11.5. Редактирование сведений о товаре и отобра жение сообщения из TernpData
<form asp - actio n=" Edit " method=" post " >
<input typ e= "h idde n " asp - for= " ProductID " />
<div c l ass= " form - group " >
<l abe l as p- f or=" Name " ></labe l >
<div><span asp-validation-for="Name" class="text-danger"></span>
</div>
<input asp -f or=" Name " class =" form - con t rol " />
</div>
<div class =" form - group " >
<label asp - for= " De scription " ></ l abel>
<div><span asp-val.idation-for="Description" cl.ass="text-danger">
</span></div>
<textarea a s p - for= " Desc ri ption " c lass= " form - cont r ol " ></textarea>
</div>
<d i v class =" fo r m- group " >
<label asp - for= " Category " ></la be l >
<div><span asp-validation-for="Category" class="text-danger"></span>
</div>
<input asp - for =" Category " c l ass =" form - con t rol " />
</div>
<div c l ass= " fo r m- g r oup " >
<label asp - for= " Price " ></label>
<div><span asp-validation-for="Price" class="text-danger"></span>
</div>
<input asp - for= "P r i ce " c l ass= " fo rm- co nt r o l" />
</div>
<div class ="t ex t- c e nt er " >
<button class =" Ьtn Ьtn - primary " t ype =" submit " >Save</button>
<а asp - action= " Index " c l ass =" Ьtn Ьtn - default " >Cancel</a>
</div>
</form>
Глава 11. SportsStore : администрирование 317
Когда атрибут asp - validation-for применяется к элементу span, он использу
ет дескрипторный вспомогательный класс, который добавляет сообщение об ошибке
проверки достоверности для указанного свойства, если при проверке возникли кюше
то проблемы.
Дескрипторные вспомогательные классы будут вставлять сообщение об ошиб
ке в элемент span и добавлять элемент в класс inp ut-va lidation - error, кото
рый позволит легко применять стили CSS к элементам с сообщениями об ошибках
(листинг 11.16).
@RenderBody ()
</div>
</body>
</htrnl>
Совет. Явная установка стилей при использовании библиотеки CSS, подобной Bootstrap,
может привести к несоответствиям, когда применяются темы содержимого . В главе 27
будет продемонстрирован альтернативный подход , при котором для применения классов
Bootstrap к элементам с сообщениями об ошибках проверки достоверности используется
код JavaScript, сохраняя все в согласованном состоянии.
CJ Ed~ Product Х
f- - С 1ф l
localhost:60000/Adm1.;/~-----------·-----·----- - - - -'tl
1 ·-·----·--=====-----=--===--==--==---:::=:.:=--=..:----·-·-·--'
1 Edit Product
1
1 1
Namo
Please eriter а prodt1ct name 1
1 1
i
1
/ Description
1
1 Please enter а description
,с 1
1
! Category
1 Please spec~y а category
1
~~~::е а pos~ive
1
1 enter price i
1
[ -275.00·- - - - - - - - - - - - - - - - - -
--- ~ i
i
!L______ - Cancel
"_____________ J
Рис. 11.6. Проверка достоверности данных при редактировании сведений о товаре
файле bower. j son (листинг 11.17). Чтобы увидеть файл bower . j son, может потре
боваться выбрать проект SportsStore и щелкнуть на кнопке Show All ltems (Показать
все элементы) в верхней части окна Solution Explorer.
11
name 11 : 11 asp . net 11 ,
11
private 11 : true ,
11
dependenci e s 11 : {
11
bootstrap 11 : 11 3 . 3 . 6 11 ,
11
fontawesome 11 : 11 4 . 6 . 3 11 ,
"jquery": "2. 2. 4",
11
jquery-validation 11 : 11 1.15.0 11 ,
"jquery-validation-unoьtrusive": 11
3. 2. 6 11
@RenderBody ()
</div>
</body>
</html>
В доб авл е нной разм е тке для выбор а файлов, которые включаются в элементы
имена файлов в пакете Bower изменятся, когда выйдет его новая версия. Однако тре
буется определенная осторожность, потому что (как будет показано в главе 25) до
вольно легко выбрать не те файлы, которые ожидались.
Включение проверки достоверности на стороне клиента не приводит к каким-либо
визуальным изменениям, но ограничения, которые указаны с помощью атрибутов,
примененных к классу модели С#, вступают в силу на уровне браузера, пр едотвращая
отправку пользователем формы с недопустимыми данными и обеспечивая немедлен
ный отклик при наличии проблемы. За дополнительными сведениями обращайтесь
в главу 27.
puЬlic ViewResul t Create () => View ( "Edi t", new Product ()) ;
На заметку! М одульный те ст для метода дей ствия Create () не до ба вл яе тся . О н п озв олил
бы пр о верит ь только сп особн ость об р аб от ки и нф растр у ктур о й ASP. NEТ Core MVC р езуль
тата, в о звращаем о го методо м дей ствия - то, что мы считаем сам о с о бой р азумеющим
ся . (Обычн о тесты для функ ци о нал ьных с редств инфраструктуры не пишутся, есл и тол ьк о
нет подозрения о нал и ч ии дефекта. )
~-
Snorkel 1 Gr(!en Kayalti $275.00
·-
а 111
Description 2 Lilejacl<el $48.95 . 1 .
--
Breat/1.Q underwaler З Soccer Вoll $19.50
--
Category
~-
Watorsports s Stndtum $79,500.00
--."
аав
10.50 7 UnslearJy Cliair $29.95
Вllng-Вllng Кing
l ..
_._
1
[
9 51,200.00
IUllll
10 Sno1ko1 $10.50
1 ~~~~~~~~~--~~~~~~~ 1
!
-~1·~=· __ j
Удалени е товаров
Обеспечить поддержку удаления элементов из каталога довольно просто. Для на
чала добавьте в интерфейс IProductReposi t ory новый метод, как показано в лис
тинге 11.20.
322 Часть 1. Введение в инфраструктуру ASP.NEТ Core MVC
Листинг 11.20. Добавление метода для удаления товаров в файле IProductReposi tory. cs
using System . Collections . Generic ;
namespace SportsStore.Models {
puЫic interface IProductRepository
IEnumeraЫe<Product> Products { get ;
void SaveProduct(Product product) ;
Product DeleteProduct (int productID);
context . SaveChanges() ;
return dЬEntry;
return RedirectToAction("Index");
Нам нуж но протестировать основное поведение метода действия Delete () , которое за
ключается в том, что при передаче в качестве параметра допустимого идентификатора
ProductID метод действия должен вызвать метод DeleteProduct () хранилища и пе
редать ему корректное значениеProductID удаляемого товара. Вот тест, добавленный в
файл AdminControlle rT ests . cs :
324 Часть 1. Введение в инфраструктуру ASP.NET Саге MVC
[Fac t]
puЫic v oid Ca n_De l ete_ Va l id_Pr odu cts( ) {
11 О р ганиза ц ия - созд а ние объек т а Pr oduc t
Product prod = new Product { ProductID = 2 , Name = " Test " } ;
//Ор г анизация - создание имитирова н но г о хранилища
Mock< IP roduct Repository> mock = new Moc k<I ProductRepository>() ;
mock . Setup (m => m. Products) . Retur ns(new Pr odu c t[ ] {
new Pr oduct {ProductID = 1 , Name = " Pl " }, prod ,
new Product {P r oduc t I D = 3 , Name = " РЗ " } , }) ;
//Ор ганизация - создание контроллера
Adm inCont ro ller ta r ge t = ne w Admin Con t r o ll er (mock . Ob j ec t ) ;
//Действие - . удаление т овара
target . Delete(prod.ProductID) ;
//Утв ержде н и е - проверка того , что был вызван метод удаления
/ / в х р а н ил и ще с корректным объе кт ом Pr odu c t
mock . Ve rify(m => m. Del e teProduct(prod .ProductID)) ;
Ю N Amв
1 Creen!Uyak
Price Acliona
3 &x::cErВalt
4 Cornit Fl;ig.s
5275.00
54595
m •
1'!:1111
S St&dium
$ t Q.50
аа
m
• Шlll
G Th1nkng Сар $.34.95
7 UMt1Ыdy C h.tr1
$79.500.00
.... , ша
8 Hu nюr1 Ch0s Oo.1rd ! 16.00
. 111
10 Snor!ФI
$7 500
lmia:I
~-
$ 1.200.00
-1
Рез юм е
В этой главе были введены средства администрирования и показано, как реализо
вать операции CRUD, которые позволяют администратору создавать, читать , обно в
лять и удалять товары из хранилища и помечать заказы как отгруженные. В следую
щей главе мы продемонстрируем способ защиты административных функций, чтобы
они не были доступны всем пользователям , и разверн ем приложение SportsStoгe в
производственной среде .
ГЛАВА 12
SportsStore:
защита и развертывание
Листинг 12.1. Добавление средства ASP.NET Core ldentity в файле project. j son
из проекта SportsStore
"dependencies ": {
"Microsoft.NETCore . App ":
"version ": " 1 . 0 . 0 ", " type ": "platform "
}'
"Microsof t. AspNetCore . Diagnostics ": " 1.0 . 0 ",
" Microsoft . AspNetCore . Server . IISintegration ": " 1 . 0 . 0 ",
"Microsoft . AspNetCore . Server . Kestrel ": " 1 . 0 . О ",
"Microsof t. Extensions . Logging.Console " : "1. 0 . 0 ",
"Microsoft. AspNetCore . Razor . Tools ": {
"version ": "l. 0 . 0- preview2 -fina l ", " type ": " build" },
"Microsoft . AspNetCore . StaticFiles ": " 1. О. О ",
"Microsoft . AspNetCore . Mvc ": "1.0. О ",
" Microsoft . EntityFrameworkCore . SqlServer ": " 1.0.0 ",
"Microsoft . EntityFrameworkCore . Tools ": " l . O. O- preview2 - final ",
"Microsoft . Extensions .Configuration . Json ": " 1 . 0 . 0 ",
"Mi crosof t. AspNetCore . Session ": "1 .0 . О ",
"Microso ft. Extens ions.C aching . Memory ": "1.0.0",
"Microsoft . AspNetCore . Http . Extensions ": " 1 . 0 . 0 ",
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": 11 1 . 0.0 11
}'
После сохранения файла proj ect . j son среда Visual Studio с помощью NuGet за
грузит и установит пакет Identity.
"SportStoreidentity": {
"ConnectionString": "Server=(localdЬ)\\
MSSQLLoca1DB;DataЬase=Identity;
Trusted_Connection=True;MultipleActiveResultSets=true"
Конфигурирование приложения
Подобно другим средств ам ASP.NET Core система Identity конфигурируется в
классе Start . В листинге 12.4 приведены добавления для настройки Identity в про
ект е Spo rts Store с применением ранее определенного класса контекста и строки
подключения.
us i ng Mi crosoft.EntityFrameworkCore ;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
namespace SportsStore {
puЫic class Startup {
IConfigurationRoot Conf i guration ;
puЫic Startup(IHostingEnvironment env) {
Configuration = new ConfigurationBuilder()
. SetBasePath(env . ContentRootPath)
. AddJsonFile ( " appsettings . j son " ) . Build () ;
}) ;
SeedData.EnsurePopulated(app) ;
IdentitySeedData.EnsurePopulated(app) ;
return RedirectToAction(nameof(List) ) ;
[Authorize]
puЫic class AdminController : Controller {
private IProductRepository repos it ory;
puЫic AdminController(IProductRepository repo) {
repository = repo ;
puЬlic ViewResul t Create () => View ( "E di t", new Product ()) ;
[HttpPost]
puЫic IActionResult Delete(int productld)
Product deletedProduct = repository . DeleteProduct(productid);
if (deletedProduct ! = null) {
TempData["message"J = $"{deletedProduct . Name} was deleted ";
using System.Threading.Tasks ;
using Microsoft .As pNetCore . Authorization;
using Microsoft . AspNetCore . Identity ;
using Microsoft . AspNetCore .Identity.Ent ityFrameworkCore ;
using Microsoft . AspNetCo re.Mvc ;
using SportsStore . Models .Vi ewModels ;
namespace SportsStore . Controllers
[Authorize]
puЫic class Accountcontroller : Controller
private UserManager<IdentityUser> userManager;
private SigninManager<IdentityUser> signinManager;
puЫic AccountController(UserManager<IdentityUser> userMgr ,
SigninManager<IdentityUser> signinMgr) {
userManager = userMgr;
signinManager = signinMgr;
[AllowAnonymous ]
puЫic ViewResult Login(string returnUrl) {
return View(new LoginModel {
ReturnUrl = returnUrl
) );
[HttpPost)
[AllowAnonymous]
[Va li dateAntiForgeryToken ]
puЬlic async Task<IActionResult> Login(LoginModel loginModel) {
if (ModelState. IsValid) {
IdentityUser user =
await userManager.FindByNameAsync(loginModel.Name);
334 Часть 1. Введение в инфраструктуру ASP.NET Саге MVC
i f (user ! = null) {
await signinManager . SignOutAsync() ;
if ((await signinManager . PasswordSigninAsync(user ,
loginModel . Password , false, false)) . Succeeded)
return Redirect(loginModel? .ReturnUrl ?? 11 /Admin/Index 11 ) ;
11
ModelState .AddModelEr r or ( 1111 , Invalid name or password 11 ) ;
return View(loginModel) ;
<!DOCTYPE html>
<htrnl>
<head>
<meta name= "viewport " content= " width=de vi ce - 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- co l or : #fee
</style>
<script asp-src-include="liЬ/jquery/**/jquery . min.js " ></script>
<script asp -s rc - include= " liЬ/jquery - validation/**/jquery .v alidate . min .j s " >
</script>
<script asp - src-include= "l iЬ/jquery-validation-unoЬtrusive/**/* . rnin . js " >
</script>
</head>
<body class= "panel panel - default " >
<div class= "p anel - heading " >
<h4>
@ViewBag . Title
<а class="Ьtn Ьtn-sm Ьtn-primary pull-right"
asp-action="Logout" asp-controller="Account">Log Out</a>
</h4>
</div>
<div class= " panel - body " >
@if (TempData[ "rnessage "] != null) {
<div class= " alert alert - success">@TempData["message"J</div>
336 Часть 1. Введение в инфраструктуру ASP. NЕТ Core MVC
@RenderBody ()
</ d i v >
</ body>
</ h t ml >
1 --
Name
P::::ro Л· 1Kayak2 .
1
Ю Namo
2 Lilejacket
3 Soccвr 8311
Р гiсв
s21s.oo
$48.95
$1 9.50
". -
~-
-
ActIOns
-
1
1 ВМ lll!llfl!ll '1
4 ComerRags $34.95 ~
~ -
.. · - .
1
1_ _ _ _ _ _ _ _ _ _ _ _ _ _ J 5 S1ad111m $79,500.00 Ш lll
Р азвертывание приложени я
Все средства и функциональность приложения SportsStore на месте , так что са
мое время подготовить приложение и развернуть его в производственной среде. Для
приложений ASP.NET Core МVС доступно множество вариантов размещения, и в это й
главе используется один из них - платформа Microsoft Azure. Она выбрана из-за того,
что поступает от Microsoft и предлагает бесплатные учетные записи , т.е. вы можете
полностью проработать пример SportsStore, даже если не хотите применять Azure для
собственных проектов.
На заметку! В данном разделе вам понадобится учетная запись Azure . Если у вас пока ее нет,
можете создать бесплатную учетную запись на h ttp : / / a zure .mi c r osoft . с от.
Глава 12. SportsStore: защита и развертывание 337
Внимание! Портал Azure часто меняется по мере того, как в Microsoft добавляют новые
средства и пересматривают существующие. Инструкции, приводимые в настоящем раз
деле, были точными на время его написания, но к моменту выхода книги необходимые
шаги могут слегка измениться . Базовый подход должен остаться таким же, но имена по
лей данных и точный порядок шагов может потребовать определенного экспериментиро
вания, чтобы добиться правильных результатов.
Имя пользователя
Имя базы данных Имя сервера Пароль
администратора
Выберите одну их двух баз данных в категории р е сурсов SQL Databases, щелкните
на кнопие Tools (С е рвис) и зат ем щел ините н а с сылие Ореп iп Visual Studio (Отирыт ь
в Visual Studio). Те перь щелините на ссылие Configure Your Firewall (Конфигуриров ать
брандмауэр) , щелкните на кнопке Add Client IP (Добавить IР-адр е с кли ента) и щелкни
те на кнопке Save (Сохранить). Это позволит вашему те кущему !Р-адресу достигать
серв е ра баз данных и выполнять команды конфигурирования. (Проинсп ектиро в ать
схему базы данных можно , щелкнув на кнопке Open ln Visual Studio, что приведет к
открытию Visual Studio и использ ованию окна SQL Server Object Explorer дл я и с сл е
дования базы данных.)
ний для баз данных). Строки подключений предлагаются для разных платформ раз
рабо тки ; прилож е ниям .NET требуются строки ADO .NET. Вот строк а подключ е ния ,
которую портал Azure пр едоставляет для базы данных iden t i t у:
Подготовка приложения
Перед те м , каи прилож е ние можно будет развернуть, пр едстоит выполнить опре
деленные подготовительные шаги, чтобы привести его в готовность к прои з водств ен
ной ср еде . В посл едующих р азделах будет и зменен способ отобр аже ния сообщений об
ошибках и настроены строки подключения для производств е нных баз данных.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device- width" />
<link rel= "stylesheet " asp-href-include="liЬ/bootstrap/dist/css/* .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>
Совет. В списке файлов окна Solution Explorer данный файл находится внутри узла
appsettings . j son, который понадобится раскрыть, если позже вы захотите отреда к
тировать этот файл .
Файл неудобен для ч тения, т.к. строки подключений разбивать нельзя. Соде ржимое
данного файла дублирует раздел строк подключений файла appsettings . j son , но
здесь используются строки поднлючений Azure. (Н е забудьте заменить заполнители
для имени пользователя и пароля . )
Конфигурирование приложения
Теперь можно и зменить код класса Sta r tup, чтобы в случае н ахождения в прои з
водств енной ср еде приложение вело с е бя по-другому, применяя контроллер Error и
строки подключений Azure. Изменения приведены в листинг е 12.15.
app . UseStaticFiles() ;
app. UseSession( ) ;
app . Useide ntity( );
app.UseMvc(routes =>
routes .MapRoute (narne: 11 Error", ternplate: "Error",
defaul ts: new { controller = 11 Error 11 , action = 11 Error 11 } ) ;
routes . MapRoute(
name : null ,
template: " {cate gory)/Page{page : int) ",
defaults : new ( control l er = 11 Product 11 , actio n 11
List 11 )
) ;
routes . MapRoute(
name : null,
template: " Page ( page: int) 11 ,
defaults : new ( con t roller = " Product ", action '' List ",
page = 1 )
) ;
routes . MapRoute(
name : null ,
template : " { ca tegory} ",
11 11
defaults: new ( controller Product 11 , action Li st 11 ,
page = 1
) ;
routes . MapRoute(
name : null ,
template : 1111
defaul ts : new ( contro l ler = 11 Product 11 , action = 11 List ",
page = 1 } ) ;
routes . MapRoute(name : null, temp l ate : "( controller)/(action)/( id? ) " ) ;
}) ;
SeedData . EnsurePopulated(app) ;
IdentitySeedData . EnsurePopulated(app} ;
342 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
}'
Глава 12. SportsStore: защита и развертывание 343
"f rarneworks ": {
" netcoreappl . 0 ":
" irnports ": [ " dotnetS . 6 ", " portaЫe - net45+win8 " ]
} 1
} 1
"puЫishOptions":
11
include 11 : [ "wwwroot", "Views", "appsettings. json",
11
appsettings. production. j son 11 , "web. config 11 ]
} 1
f@ lmpoit
1D fi/e System
~ Арр Service
l:j i:J Host your vJeb and moЬile applications. REST APls. and more in Azure
Subscriptio n
View
' R ~~ u~~ e G 10~ .-. - - - - - ------·- ·-· --
Se~ r ch
New".
ок [~
LWi--ndoм
1
Azur~ мSoN·- Visu; IStudio Ui~;at~ ~
------- --------- -----·---~ ---- -- -- ~J
R~ource Group
~~ort~Sto r; ._
Арр Servicc Р l м
New... 1(1
Clicking the Cre• te Ьutton win cre.ate the ro1&owing Azure resour ces
lf you havc removed yo ur spcndi ng limit or you arc u~ing Р4у гs Vou Go, therc may Ь с monct ~нy impact if you provision "dditional r ~ou rcc.s .
t ~~111 ~- ! cr~
ОК 1\ Canc~I
ф PuЬlish
Conncctto~
PuЫish method: Гwd;~oy--====--==~-------·-3_
-~
Settings
Prtview
Servll!r:
..-------------·
1sportssto r ec~m.az u rewebsit~. n e:t;443 -- -=:J
Site name:
Username:
Password:
1··········~················~~-;;;;.~~~~~~
0 Save password
Dt•ti nation URl: \ http://sportsstorecoro.azurewebsites.ne_t _ _ _ _ _ _ r_._J
Validate Connection
Резюме
В этой и предшествующих главах демонстрировалось использование инфраструк
туры ASP.NET Core MVC для создания реалистичного приложения электронной ком
мерции. В приведенном расширенном примере были проиллюстрированы многие
основные средства MVC: контроллеры, методы действий, маршрутизация, представ
ления, метаданные, проверка достоверности, компоновки, аутентификация и т.д . Вы
также видели, как пользоваться рядом ключевых технологий, имеющих отношение к
МVС, в том числе Eпtity Framework Core, внедрение зависимостей и модульное тести
рование. Результатом стало приложение с ясной и ориентированной на компоненты
архитектурой, обеспечивающей разделение обязанностей, и кодовой базой , которую
будет легко расширять и сопровождать. На этом разработка приложения SportsStore
завершена. В следующей главе вы научитесь применять Visual Studio Code для созда
ния приложений ASP.NET Core MVC.
Глава 12. SportsStore : защита и развертывание 347
CJ Spon<Store Х
С \ф sp~~s~orec~r~:z~~re\~:bsites.11et ----=~==-~--=====--~
SPOПTS STOHE а
Home
lifejacket
Protective and fashionaЫe
1
1
1
Corner Flags litИJ
Give your playing field а professional touch 811 1
1 1
• 2 . 3
1
!
L--------·---------·-··---------· J
Рис . 12. 7. Развернутое приложение
ГЛАВА 13
Работа с
Visual Studio Code
в настоящей главеVisual
применением
кодом производства
будет показано, как создать приложение ASP.NET Саге MVC с
Studio Code -
Microsoft.
межплатформенного редактора с открытым
Несмотря на название, продукт
Visual Studio Code не
имеет отношения: кVisual Studio и основан на инфраструктуре Electгon, используе
мой редактором Atom, который популярен среди разработчиков, применяющих дру
гие инфраструктуры для построения веб-приложений, такие как Angular.
Продукт Visual Studio Code поддерживает операционные системы Windows ,
OS X/macOS и наиболее популярные дистрибутивы Llnux. Продукт Visual Studio Code
находится на начальной стадии своего развития, поэтому не все средства работают
должным образом ; однако в Microsoft предлагают ежемесячные обновления: и про
движение происходит довольно быстро. Некоторые текущие ограничения, такие как
отсутствие поддержки отладки для приложений ASP.NET Саге, могут быть устранены
ко времени выхода этой книги, но вьmолнение всех примеров в данной книге по-пре
жнему требует Visual Studio и Windows.
Процесс разработки в Visual Studio Code менее автоматизирован, чем в полной
версии Visual Studio, но он вполне осуществим, предлагая: пристойную отправную
точку для построения приложений ASP.NET Core MVC в средах других операционных
систем или легковесную альтернативу Visual Studio 2015 в Windows.
На заметку! Доступны две версии Node.js. Версия LTS (Loпg Term Support - долгосрочная
поддер жка ) предоставляет стабильный фундамент для развертывания в производствен
ных средах, где изменения должны быть сведены к минимуму. Обновления версии LTS
выпускаются каждые 6 месяцев и сопровождаются в течение 18 месяцев. Версия Curreпt
(текущая) является более часто изменяющимся выпуском, в котором преимущество от
дается новым средствам взамен стабильности . В настоящей главе мы будем применять
выпуск Curreпt.
n d~
Custoni 5etup
г "",::;::-.~
1
. . ".._""'' """. .,
Select lhe way you want features to Ье instaИed.
"'"~
InstaП
@j ----
node - v
Если установка прошла успешно, а путь к Node бьm добавлен в переменную среды
РАТН, тогда вы увидите номер версии. На момент написания главы текущей версией
Node являлась 6.3.0. Если во время проработки примеров, рассмотренных в главе, вы
получаете неожиданные результаты, то попробуйте воспользоваться указанной кон
кретной версией.
Установка Git
Продукт Visual Studio Code вЮiючает интегрированную поддержку Git, но требует
ся отдельная установка для поддержки инструмента Bower, который применяется при
управлении пакетами юшентской стороны.
Таблица 13.1. Пакеты NPM, полезные для разработки приложений ASP.NET Core
Имя Описание
brew update
brew install openssl
brew link --force openssl
Загрузите установщик .NЕТ Core, находящийся по адресу h t tps : / / go. rnicrosoft .
corn/fwlink/?LinkID=809124, и запустите его, чтобы добавить .NET Core в свою
систему.
dotnet --version
Команда dotnet запускает исполняющую среду .NET, после чего отобразится но
мер версии установленного пакета .NET. На момент написания главы текущим выпус
ком был 1. О. 0-preview2 - 003121, но ко времени выхода книги он наверняка будет
заменен более новым выпуском.
Глава 13. Работа с Visual Studio Code 353
На заметку! Компания Microsoft предлагает новые выпуски Visual Studio Code ежемесячно, а
это значит, что устанавливаемая вами версия будет отличаться о версии 1.3, которая при
меняется в главе. Хотя основы должны остаться теми же самыми, выполнение некоторых
примеров может потребовать определенной доли экспериментирования.
ваются ср едой Visual Studio 2015. Самое важное расширени е , насающееся разработ
ки приложе ний ASP.NET Core MVC, добавляет подде ржку для С# , отсутствие которой
может выглядеть как странное упущение в базовой установке.
354 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
----·-------------
Vntltled· 1 - v11u;:,1 Studlo Code
Рис. 13.2. Выполнение Visual Studio Code в средах Windows, OS X/macOS и Ubuntu Linux
Тем не менее, такое положение дел отражает тот факт, что в Microsoft позициони
ровали Visual Studio Code как универсальный межплатформенный редактор, который
поддерживает широчайший спектр я зыков и инфраструктур.
Чтобы установить расширение С#, выберите пункт Command Palette (Палитра ко
манд) в меню View (Вид) редактора Visual Studio Code. Палитра команд предоставляет
доступ ко всем командам, которые можно выполнять с помощью Visual Studio Code.
Введите ext и нажмите на клавишу <Return>. в результате чего Visual Studio Code от
кроет окно Extensions (Расширения). Введите csharp и отыщите в списке расширение
С# for Visual Studio Code (С# для Visual Studio Code), как показано на рис. 13.3.
Щелкните на кнопке lnstall (Установить), после чего Visual Studio Code загрузит и
установит расширение. Щелкните на кнопке ЕnаЫе (Включить), чтобы активизиро
вать расширение (рис. 13.4).
rn ... 1
С# Litense
С# license
[!zma ~Nnstan'
Рис. 13.4. Включение расширения С#
уо aspnet
Эта команда запускает пакет Yeoman и сообщает ему о необходимости создания
нового проекта ASP.NET Core. Весь процесс настройки проекта производится через
командную строку, переходя по параметрам с использованием клавиш со стрелками
Empty Web Application Этот шаблон создает проект ASP. NEТ Core с минималь
(Пустое веб-приложение) ным начальным содержимым; он похож на шаблон Empty
(Пустой) в Visual Studio 2015
Web Application Этот шаблон создает проект ASP.NEТ Core с начальным
(Веб-прило жение) содержимым, которое включает контроллеры, пред
Web Application Basic Этот шаблон создает проект ASP.NET Core с начальным
(Базовое веб-приложение) содер жим ым, которое включает контроллеры и представ
ления, но не аутентификацию
Web API Application Этот шаблон создает проект ASP.NET Core с контроллером
(Веб-приложение API) API (который будет описан в главе 20) . Он эквивалентен
шаблонуWеЬ API в Visual Studio 2015
356 Ч асть 1. Вв еде н ие в и н фрас тру кту ру AS P.NET Core MVC
? Wh a t ' s the name of you r ASP.NET a ppli cat i on? (Empt yWe bAppl i cat i on)
Partyi nvi t es
? Каков о имя вашего приложения ASP . NE T ? ( Empt yWe ЬApplication )
Нажмите <Retu rn>; пакет Yeoman создаст папку Partyinvi tes и наполнит е е ми
нимальным набором файлов, которые требуются для проекта ASP. NEТ Core.
'iii~ Required assets to build and debug are miss1n9 from your pro;ect. Add them? w~~
~~~т~
~~
Щелкните на кнопке Yes (Да). Редактор Visual Studio Code создаст папку . vscode
и добавит в нее файлы, которые конфигурируют процесс построения . По умолчанию
Visual Studio Code использует компоновку из трех разделов. Боковая панель, выделен
ная на рис . 13.6. предоставляет доступ к главным областям функцион ал ьности .
Самая верхняя кнопка позволяет от
крыть панель проводника, которая отоб
V 1~v1 Goto H~lp разит содержимое ранее открытой папки.
Остальные кнопки об еспечивают до ступ
-4 к средству поиска, к интегрированному
'
управлению исходным кодом, к отладчи
"dependencies ":
"Microsoft . NETCore . App " :
"version " : " 1 . 0 . 0",
"type " : "platforrn "
) ,
"Microsoft . As pNetCor e . Diagnos ti c s" : "1 . 0 . 0" ,
"Mi crosoft .AspNetCore . Se rver . IISintegrat i on ": " 1 . 0 .0" ,
"Microsoft .AspNetCore . Serve r. Kest r e l": " 1 . 0 . О ",
"Microsoft . Extensions . Logging . Conso le": " 1 . 0 . 0",
" Microsoft . Extensions . Conf i g u ra t ion.Enviro n rnentVa r iaЫes " : " 1.0 .0 ",
"Microsoft . Extensions . Configuration . Fi leExte ns i ons ": " 1 . 0 . 0",
"Microsoft . Extens i ons.Conf i gu rat i on . J son ": " 1 . 0 . 0",
"Microsof t . Extensions . Configura ti on . ComrnandLin e ": " 1 . 0 . 0",
"Mic r o s oft . AspNetCore.Mvc" : 11 1 .0.0 11 ,
"Micro s oft . AspNetCore.Stati c Fil es " : 11 1 .0.0 11
) '
"tools " : {
"Micro s oft .DotNet . Watc her. Tools " : 11
1. О . 0-preview2 - f i nal ",
"Microsoft. AspNetCore . Server . I IS integrat ion . Tool s" : "1 . 0 . 0- previ ew2 - f i nal "
},
pro;et"tJsoп х
(
"depe ndenc ies ·· :
" ~\ ic rosoft. NEТCore. Арр" :
4 "version'' : " 1 . 0 . 0 J
1
'
s "type": "platform"
6 }'
"M i crosoft . AspNetCore.Oiвgnostics":
"name": "Party!nvites",
"private": true,
"dependencies": {
"boo tstrap": " 3.3 .6",
"jquery": " 2 . 2. 3" ,
"j query-validation" : " 1. 15. О",
" jquery - validation-unoЬtrusive ": " 3.2.6 "
При добавлении пакетов в файл proj ect. j son или bower. j son редактор Visual
Studio Code обеспечивает поддержку IntelliSense, что облегчает выбор нужных паке
тов и указание применяемых версий.
Глава 13. Работа с Visual Studio Code 359
Восполь зуйтесь окном ком андной строки/т ерминала , чтобы выполнить в папке
Partyinvi tes показанную ниже команду . которая применя ет инструмент Bower для
загрузки и установки пакетов клиентской стороны, указанных в файле bower . j son :
bower install
Редактор Visual Studio Code не предоставляет аналога для средства Browser Link и
не открывает окно браузера автоматичес 1ш. Чтобы протестировать приложение, от
кройте окно браузера и перейдите на URL вида http: / / localhost: 5000. Вы полу
чите ответ, представленный на рис. Ошибка 404 связана с тем, что в текущий
13.8.
момент в проекте отсутствуют какие-либо контроллеры для обработки запросов.
D localhost:SOOO Х
1 · - - - - - - - · - - - - · - · -·- · - ,
[ ·f-- ~- с l~,;_~~~"(}"_ ~~~~ ~-- -1
l::~~~~ode: ~04~-~ot .~:~~~-------------·J
Рис. 13.8. Тестирование примера приложе ния
EXPLOREI< х
; OP.EN IOITORS
"de pendenc ies'":
f
pгoje•t,fien 2
3 "'r1i crosoft. NEТCore. Арр" : ~
" PARТYIN\llТfS
''version'' : '' 1.0.0'' ,
f
4
~ .vscode "'type'' : "platform"
i• Proper·ties 6 }, '
t· W\WlfOO! "Hic1·o~oft. AspNetCoi-e. Diag11ostics" : \
8 "Microsoft . A~pHe'tCore . Server. IISI1>t,.,
9 "l-lic1·osoft. д,pNetCo r·e. Ser>rer. Kest•·y
"!IF",.....111""' --ll!l!!!!lll[!Jl.,,,..,~,~.}J/J1iii1\~~on~o
Рис. 13.9. Создание новой папки
using System.ComponentModel.DataAnnotations;
namespace Partyinvites . Models
puЫic class GuestResponse {
puЫic int id {get ; set; }
[Required(ErrorMessage = " Please enter your narne")]
11 Пожалуйста , введите свое имя
puЬlic string Narne { get; set; }
Класс EFReposi tory следует шаблону, который похож на тот, что применялся в
главе 8 Для настройки базы данных SportsStore. В листинге 13.9 видно, что к методу
Глава 13. Работа с Visual Studio Code 363
ConfigureServ i ces () класса S t art u p добавлен оператор конфигурирования, кото
рый сообщает инфраструктуре ASP.NET Core о необходимости созд а ния экземпляра
клас с а EFRepo si tory, когда требуются реализации интерфейса I Rep o si tory , с по
м ощью ср едства внедр ения зависимостей (рассматриваемого в главе 18).
using System ;
using System . Co l lections . Gener i c ;
using System .Linq ;
us i ng System.T h reading . Tasks ;
us i ng Micro soft . AspNe t Core . Bui lder ;
using Mi crosoft . Asp NetCore . Host i ng ;
using Microsoft . AspNetCore . Http;
using Microsof t. Extens i ons . Depe n d ency i n j ectio n;
using Mic r osof t. Ex t ens i o n s . Logg i ng ;
using Partyinvites.Models;
namespace Partyinvite s {
p u Ы i c clas s Start up {
Одна из причин того , что LocalDB является настолько полезным инструментом , связана с
возмо ж ностью разработ к и с применением механизма баз данных SQL Server, который де
лает переход в производственную среду SQL Server простым и почти полностью лишенным
рисков. SQLite - великолепная база данны х , но она не очень х орошо подходит для крупно
м асштабны х веб-приложений , а значит, что при развертывании пр и лож ения MVC требуется
пере ход на другую базу данны х . Из м енения в конфигурации м огут быть упрощены с ис
пользование м средств конфигурирования , которые будут описаны в главе 14, но приложе
ние должно быть тщательно протестировано в испытательной среде, чтобы выявить любые
отличия , вносимые производственной базой данных.
Почитайте статью по адресу https : / /www . sqli te . org /whentouse . html , если вы не
уверены в том, применять ли SQLite в производственной среде. Там приведен обзор ситуа
ций, когда база данных SQLite может быть хорошим вариантом, а когда нет.
Важ но иметь в виду тот факт, что SQLite не поддер живает полный набор изменений схемы ,
которые Entity Framework Core может генерировать для других баз данны х. В целом это не
проблема , к огда SQLite используется при разработк е, посколь ку вы можете удалить файл
базы данных и сгенерировать новый файл с чис той сх емой. Тем не менее, ситуация усло ж
няется, если вы обдумываете развертывание приложения, в котором применяется SQLite.
Если вы хотите использовать ту же самую базу данных в среде разработки и производс
твенной среде , тогда ознакомьтесь со списком поддерживаемы х инфраструктурой Entity
Framework Core баз данных по адресу http : //ef . readthedocs . io/en/latest/
providers/ index . htrnl. На момент написания главы список был коротким, но в Microsoft
объявили о поддер жк е баз данных, которые больше, чем SQLite, подходят для процесса
разработки и могут функционировать также на платформах, отличных от Windows .
"dependencies ":
"Microsoft . NETCore . App ":
"version ": " 1.0.0 ",
" type ": "platform "
) 1
) '
Сохраните изменения в файле proj ect . j s on, откройте новое окно командной
строки/терминала и выполните следующую команду в папке Partyinvi tes :
dotnet rest ore
Совет. Создание пап ки в редакторе Visual Studio Code может вызвать затруднения, т.к. щел
чок на элементе PARTYINVITES в панели проводника скрывает содер жим ое папки, а
не выбирает корневую папку. Щелкните на одном из файлов в корневой папке, таком как
proj e ct. j son, наведите курсор мыши поверх элемента PARTYINVITES и щелкните
на значке New Folder.
366 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC
[HttpGet]
puЫic ViewResult RsvpForm() {
return View() ;
[H ttpPost ]
puЫic ViewResult RsvpForm(GuestResponse guestResponse)
if (ModelState . IsValid) {
repository.AddResponse(guestResponse);
return View( " Thanks ", guestResponse) ;
e l se {
//Имеется ошибка проверки достоверности
return View();
Далее создайте папку Views/Home и добавьте в нее файл по имени MyView . cshtml,
в котором определяется представление , выбираемое методом действия Index () из
листинга 13.11. Пом естите в него разметку, приведенную в листинге 13. 13.
Глава 13. Работа с Visual Studio Code 367
Л и стинг 13.13. Содерж и м ое файла МyView. cshtml из папки Views/Home
@{
Layout = null ;
<!DOCTYPE html>
<html>
<head>
<meta name= "v i ewport " content= " width=device-width " />
<title>Index</title>
<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 invited</h4>
<а class= " Ьtn Ьtn - primary " asp - act i on= " RsvpForm " >RSVP Now</a>
</div>
</body>
</html>
До бав ьте в папку Views/Home файл по и мени RsvpForm . cshtml с с оде ржимы м,
показ анным в листинге 13. 14. Это пр едставление пр едлаг ает НТМL- форму , которая
будет з аполняться по л ь з ователе м , чтобы принять или о тклонить пригл аш е ни е на
в е ч е рин ку .
<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-control" asp - for= "WillAttend">
<option value="">Choose an option</option>
<option value= " true " >Yes, I'll Ье there</option>
<option value="false " >No, I can ' t come</option>
</select>
</div>
<div class="text- center " >
<button class= " Ьtn Ьtn-primary" type="submit">
Submi t RSVP
</button>
</div>
</form>
</div>
</div>
</body>
</html>
<!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 <а class= " nav-link" asp - action="ListResponses">here</a>
to see who is coming.
</body>
</html>
Глава 13. Работа с Visual Studio Code 369
Финальный файл представления имеет имя ListResponses. cshtml и подобно
другим представлениям в примере добавляется в папку Views/Home. Он отображает
список ответов гостей, используя разметку из листинга 13.16.
@{
Layout = null;
<!DOCTYPE html>
<html>
<head>
<meta name= " viewport " content="width=device - width " />
<link rel=" stylesheet" href=" /liЬ/bootstrap/dist/ css /bootstrap . css " />
<title>Responses</title>
</head>
<body>
<div class= "pane l-body " >
<h2> Here is the list of people attending the party</h2>
<tаЫе class= "t aЫe taЬle-sm taЫe-striped taЬle -b ordered " >
<thead>
<t r><th>Name</th><th>Email</th><th>Phone</th></tr>
</t head>
<tbody>
@foreach (Partyinvites .Models .Gues tResponse r in Model) {
<t r> <td>@r . Name</ td><td>@r . Email</td><td>@r.Phone</td></tr>
}
</tbody>
</tаЫе>
</d iv>
</body>
</html>
Конфигурирование приложения
Первый шаг предусматривает добавление пакетов в файл proj ect . j son и указа
ние деталей об используемом пакете тестирования (листинг 13.17).
370 Часть 1. Введение в инфраструктуру ASP.NET Соге MVC
1. ~
f- _,с
-i RSVP
\V:~:: :ood?
7
Namo µr\Of}Q
Yll!i.1"000\h&ro
1 55.> 1234
''**'' 5S!:o--(;789
} 1
dotnet restore
Прогон тестов
Редактор Visual Studio Code обнаруживает тесты и добавляет встроенную ссылку
для их з апус1ш (рис . 13.11).
372 Часть 1. Введение в инфраструктуру ASP.NET Core MVC
[Fact)
О refere rкeJ 1 run test !Jiebug test
puЬlic v___ """ •..• tionFiltersNonAttendees() {
//Arrange
HomeController controller = new HomeController(ne1~ FakeReposi tory());
// Act
ViewResult result = controller.ListResponses();
11 Assert
Assert.Equal(2, (result.Model as IEnumeraЬle<GuestResponse>).Count());
}
Щелчок на ссылке run test (прогнать тест) приведет к открытию окна вывода и
отображению результата . (На момент написания: главы ссылка debug test (отладить
тест) не работала, а в ряде случаев вывод мог вообще не отображаться.)
Более надежный подход предусматривает прогон всех тестов в проекте. Выполните
следующую команду в папке прое1па:
dotnet test
Все тесты в проекте запустятся, и в результате отобразится вывод, подобный по
казанному ниже:
xUnit . net .NET CLI test runner (64 -b it . NET Core win10-x64)
Discovering : Partyinvites
Discovered : Partyinvites
Starting : Partyinvites
Finished: Partyinvi tes
=== TEST EXECUTION SUMMARY ===
Partyinvites Total : 1, Errors : О, Failed : О, Skipped: О , Time : 0.196s
SUMMARY: Total: 1 targets, Passed: 1, Failed: О .
Прогнать все модульные тесты в проекте после каждого изменения классов С #
можно также с помощью следующей команды:
Резюме
В этой главе был предложен краткий обзор работы с редактором Visual Studio
Code - легковесным инструментом разработки , который поддерживает создание при
ложений ASP.NET Core МVС в средах Windows, OS X/macOS и Linux. Редактор Visual
Studio Code пока еще не является заменой полного продукта Visual Studio, но предо
ставляет основные средства, которые необходимы при построении приложений MVC,
и расширяется компанией Microsoft в ежемесячных выпусках.
Итак, первая часть книги завершена. Во второй части мы начнем погружени е в
детали и посмотрим, как работают средства, которые использовались для создания
приложения.
ЧАСТЬ 11
Подробные сведения
об инфраструктуре
ASP.NET Core МVС
к
настоящему моменту вы уже знаете, для чего существует инфраструкту
ра .NET Core MVC , а также понимаете ее архитектуру и лежащие в основе
проектные цели. Вы получили эти знания благодаря построению реалистичного
приложения электронной коммерции. Наступило время заняться исследованием
внутреннего устройства инфраструктуры.
Вопрос Ответ
Что это такое? Классы Pr o gram и Startup и файлы JSON используются для
конфигурирования работы приложения, а также указания паке
тов , от которых оно зависит
Как она используется? Самым важным компонентом является класс Startup, который
применяется для создания служб (объектов, предоставляющих
общую функциональность повсюду в приложении) и компонентов
промежуточного программного обеспечения (ПО) , используемых
для обработки НТТР-запросов
Глава 14. Конфигурирование пр илож ен и й 375
Окончание табл . 14. 1
В опрос Ответ
</tаЫе>
</body>
</html>
В текущей главе нас интересует вторая роль формата JSON, связанная с тем, что
он является форматом для конфигурационных файлов. В табл. 14.3 описаны разно
образные конфигурационные файлы JSON, которые могут быть добавлены к прило
жению ASP.NET Core MVC.
Имя Описание
Конфигурирование решения
Файл globa l. j son применяется для конфигурирования р ешения в целом. Вот со
держимое, которое Visual Studio по умолчанию добавляет для проекта ASP.NET Core:
Глава 14. Конфигурирование приложений 379
Настройка proj ects указывает набор папок. которые содержат проекты л ибо ис
ходный код. По соглашению развертываемая часть решения - например, приложе
ние МVС - помещается в папку src, тогда как проекты тестирования сохраняются
в папке test . Это только соглашение, и вы можете использовать файл global . j son
для перечисления любого желаемого набора папок и применять его для любой цели,
которая вам подходит. Настройка sdk сообщает среде Vlsual Studio о том, какая вер
сия .NET будет использоваться для запуска проекта. Версия. указанная в этой на
стройке, прим еняется для всех проектов в решении.
Во-первых, едва ли не все данные в JSON помещаются в кавычки. Очень легко забыть , что
вы пишете вовсе не код С#, и посчитать, что имена свойств и значения должны быть вос
приняты без кавычек. Тем не менее, в JSON все кроме булевских значений и чисел должно
помещаться в кавычки, например:
Заметить разницу довольно трудно даже в случае ее выделения полуж ирным (именно пото
му такая ошиб ка является распространенной), но здесь помещена запятая после символа },
закрывающего раздел sdk. Одна ко будьте внимательны , потому что замыкающая запятая ,
после которой отсутствует раздел, такж е приводит к ошибке. Если внесенные вами изме
нения в файл JSON вызвали проблемы, то в первую очередь нужно выполнить проверку на
предмет двух указанны х выше ошибо к.
"dependencies ": {
"Microsoft . NETCo r e . App ":
"version ": " 1 . 0 . 0 ",
"type ": "platform "
} 1
) 1
) 1
Глава 14. Конфигурирование прило ж ений 381
11
puЫ i shOptions
11
: {
11
incl ude 11
: [
11
wwwroot 11
,
11
web. conf i g 11
]
) '
11
scripts ": {
11
p o s tpuЫ i sh
11
: [
11
do tnet p u Ьlish -iis -- p u Ы i sh - folder
% puЫ is h : Output P ath % -- fra mewo r k
% puЫish : FullTargetFramewo r k % 11
]
В т абл. 14.4 описаны все разделы конфигурации в файл е p roj ec t. j son . Двумя
наиболее важными частями файл а proj ect . j s on являются d e pendenc i es и too ls,
которые будут об суждаться в последующих разделах.
Имя Описание
dependen cies В этом разделе указываются пакеты NuGet, от которых зависит проект,
как объясняется ниже в разделе "Добавление зависимостей в файл
proj e ct . j son"
too l s В этом разделе настраиваются пакеты, которые используются в качес
тве инструментов разработки, как показано в разделе "Регистрация
инструментов разработки в файле p r oj ect . j son" далее в главе
puЫ i s hOp t i ons Этот раздел применяется для конфигурирования способа опубликова
ния проекта
11
dependencies 11
: {
11
Microsoft . NETCore . App 11
:
vers i on
11
1.0.0 11
:
11 11
,
11 11
typ e 11
pla tf orm :
11
} ,
382 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
}/
В свойстве version указывается номер выпуска пакета точно так же, как в про
стом синтаксисе. В свойстве type предоставляется дополнительная информация о
роли пакета. Здесь допускается указывать одно из трех значений, которые описаны
в табл. 14.5.
defaul t Это значение указывает обычную зависимость разработки, так что прило
жение при выполнении своей работы полагается на сборки в пакете.
Такое значение применяется в простом синтаксисе
же пакет, который добавляет команды Entity Framework Core для управления базами
данных, как было описано в главе 8.
Класс Program
Класс Program определ е н в файл е по им е ни Program . cs и об е спечивает точку
входа для запуска прилож е ния , предоставляя инфраструктуре .NET мето д Main ().
который может быть выполн е н для конфигурирования среды размещения и выбора
кл а сса , конфигурирующего приложение. В листинге 14.7 приведен стандартный код
кл ас са Program, добавляемый к проектам средой Visual Studio.
В бол ьш и нстве проектов из м енять класс Program н е придется. е сли только вы
не производите ра з вертывание в необычной или высокоспециализированной среде
разм е щения. Одно тако е измен е ние будет продемонстрировано в разделе "Работа со
сложными конфигурациями" далее в главе , но в проектах, которые развертывают
ся на стандартных платформ ах , подобных IIS или Azure , м ожно использовать класс
Program, пр едлага емый по умолчанию .
using System . IO ;
using Mic r osoft.AspNetCore.Hos t ing ;
namespace ConfiguringApps
puЬlic c l ass Prog ram {
puЫic static void Main (string [ ] args) {
var host = new WebHostBu i lder()
.UseKestrel()
. UseContentRoot(Direct or y. GetCu rr entDirectory())
. UseIISintegration()
384 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
.UseStartup<Startup>()
. Build ();
host.Run();
Имя Описание
применению
При добавлении пакетов в файл proj ect. j son вы заметите, что одна из стандартных за
писей в разделе dependencies (даже в проектах, созданных с использованием шаблона
Empty) предназначена для Kestrel.
"Microsoft.AspNetCore.Server.Kestrel": "1.0.О",
dotnet run
По умолчанию веб-сервер Kestrel начинает прослушивать порт 5000 на предмет поступле
ния НТТР-запросов.
Класс Startup
Для конфигурирования функциональности приложений в ASP. NEТ Core применя
ется класс С# по имени St ar tup . Он определен в файл е Start up . cs , который Visual
Studio добавляет в корневую папку проектов веб-приложений. Изучение работы клас
са Startup позволяет понять суть способа обработки НТТР-запросов и интеграции
инфраструктуры MVC с остальными частями платформы ASP.NET.
Совет. Имя класса Start up предоставляется как параметр типа методу Us eSt ar t up () ,
вызываемому в классе Pr ogram, т. е . при желании можно указать другое имя класса .
Рис. 14.1. Запуск прил ожения с исп ользованием стандартн о го класса Startup
ние может создать свои службы. Как объясняется в разделе "Службы ASP.NET" далее
в главе, службы - это объекты, 1юторые предоставляют функциональность другим
частям приложения. Приведенное описание не отличается особой строгостью по той
причине, что службы могут применяться для предоставления практически любой
функциональности.
После того как службы созданы, ASP.NET вызывает метод Configure ().Целью ме
тода Configure () является настройка конвейера запросов, который представляет со
бой набор компонентов (называемых промежуточным программным обеспечением),
используемых для обработки входящих НТГР-запросов и генерации ответов на них.
В разделе "Промежуточное программное обеспечение ASP.NET" далее в главе будут
даны объяснения, как работает конвейер запросов, и продемонстрировано создание
компонентов промежуточного ПО. На рис. 14.2 показано, как ASP.NET имеет дело с
классом Startup.
Службы Конвейер
созданы подготовлен
Класс Startup, который для всех запросов просто возвращает одно и то же сооб
щение "Hello World ! ",не особенно полезен, поэтому перед подробным обсуждением
методов класса понадобится немного забежать вперед и включить МVС (листинг 14.9).
Глава 14. Конфигурирование приложений 387
Листинг 14.9. Включение MVC в файле Startup. cs
·--·------·------------------,
С 1 ф locatl1ost:SOOO '(:;
L ___ ". ---".--"·-·-
- .,.. -·· .. "_ _____" ____ --·--
~. ··~ - -- .J 1
1 i\Ic.>ssage Пus is tl1e lпdex actio11
l______ - !
--- ,_______" _____ -- --·· ---------------- ____________.J
Службы ASP.NET
Инфраструктура ASP.NEТ вызывает метод Startup . ConfigureServices (),так
что приложени е может настроить требуемые службы. Термин "служба " относится к
любому объекту, который предоставляет функциональность другим частям приложе
ния. Как уже отмечалось, такое описание не является строгим , поскольку службы спо
собны делать для приложения все что угодно. В качестве примера создайте в проекте
папку Infrastructure и добавьте в нее файл класса по имени UptimeService. cs с
опр еделением, приведенным в листинге 14.10.
388 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
приложения .
·-
1 ~ · l
С ф localhostSOCO --~
·--··------==:=-:::::-:==-====---:-=-=::::-:::-:-_____,___ ,
: \
l Message
~~ ~~
П1is is the Iшlex
------------··-"------··-·-------
Рис. 14.4.
actio11
Службы MVC
Пакет с таким уровнем сложности, как МVС , имеет дело со многими службами;
одни из них предназначены для внутреннего употребления , а другие предлагают фун
кциональность разработчикам. Пакеты определяют расширяющие методы, которы е
настраивают все требующиеся им службы в единственном вызове метода. Для MVC
такой метод имеет имя Ad dMvc ( ) и является одним из двух методов, добавленных в
класс Sta r t up, чтобы обеспечить работу МVС:
в цепочке .
392 Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC
f:j l0<•1host5000/middil!'Ф' Х
r-·-··- -··-------- ------·-::;::1
~
·-
.
·--
_______
с ~.:!!~~~~:::1~~.::._
. --- - ·- .. _____ ___
3..1 ;
-----·-- ----------
_. ~ - -----··-"-··-
Промежуточное ПО
для генерации содержимог о
1-
ш 'r ------ - .D
"...
z 1
Ответ
1
1
1
ф
s:
;r
а.: 1 :s;
1 1 :z;
1
ел 1
---- -
1 "'
Q.
~ о
~
~
'
<(
usi n g System . Li nq ;
using S y stem.Thr ead i ng .T a sk s ;
usi ng Microso ft . Asp NetCo r e . Http ;
na me s pace ConfiguringApps . Inf r ast r uctu r e
puЬlic c l ass ShortCircuitMiddleware {
pr i vate Req ue s t Delega t e nextDeleg ate ;
pu Ы icSho r tC i rcuitMiddleware(Req uestDelegate next) {
nextDelega t e = next;
Понятие " обход" применяется из-за того, что такой тип промежуточного ПО не
всегда направляет запросы следующему номпоненту в цепочке. В данном случае, если
заголовок User - Agent содержит элемент edge, тогда компонент устанавливает код
к е , а это значит, что ответ будет с ген е рирован , когда запрошенны м URL является
/middleware . На рис . 14.7 иллюстрируется добавление в конвейер компонента про
межуточного ПО для обхода.
Про ме жуточ н о е ПО
П р о м е жуточное ПО для ген е р а ц ии
дл я о бхода содер жим ого
1-
ш
.D
,..
~
z
CJ)
1 1 >--
:s:
т
От ве т От в ет s
о.: 1 1 1 1 :r:
<(
Рис. 14. 7. Добав лен и е в ко н ве й ер ко м поне нта п р о межуто чн ого ПО для о бхода
using System.Linq ;
using System .Th reading.Tasks ;
using Microsoft . AspNetCore .H ttp;
namespace ConfiguringApps . Infrastructure
puЬlic class ShortCircuitMiddleware {
private RequestDelegate nextDelegate ;
puЫic ShortCircuitMiddleware(RequestDelegate next) {
nextDelegate = next ;
Пом е щение компон ента в начале конв ейера гарантирует, что до попадания в дру
гие компоненты запрос уже будет модифицирован (рис . 14.8).
~
ш ~ 1
Запрос
1
-- - -- - --- ---
.!J
z 1
r
1
"'~
ф
~
а.: Ответ Ответ 1
1 1 1 1 I
(f) 1 1 "'
а.
<(
~
- --- - ~
-----f< - _1 о
Компонент н е заинте ресован в з апросе до т ех пор , пока он н е про йдет сво й пут ь
по конвей е ру проме жуточного ПО и н е сгенерируется ответ. Если кодо м со стоя ния
отв ет а явля ется 403 или 404, тогда компон ент добавляет к от вету о пи сательно е со
общение. Все други е ответы игнорируются . В листинге 14.22 показана р еги стр а ция
кл асс а компонент а в классе Startup.
Совет. Вас может интересовать, от куда поступил код состояния 4 04 - Not Found (404 -
не найдено) , пос кольку он не устанавливается ни одним из тре х созданны х компонентов
промежуточного ПО. Дело в том, что именно так инфраструктура ASP.NET конфигурирует
ответ, когда запрос входит в конвейер, и этот ответ будет тем результатом, который воз
вращается клиенту, если компоненты промежуточного ПО не изменяли его .
Ри с. 14.9 . До бавление в ко нвей ер ко м по нента п р о межуточн ого П О дл я редакти ров ани я отв ето в
С) localhostSOOO/otherURL Х
Ри с. 14.1 О. Ре дакти ров ание от в ето в от друг их ко м пон е нтов про межуточного П О
400 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
Имя Описание
А Profi!e
г:;. :.-:--:--:;:-;:--;:-::.;;------
L..... . -- - --· -·-· -
Сохр аните измен е ния , чтобы новое имя ср еды вступил о в силу .
Совет. Имена сред не чувствительны к регистру символов , так что Staging и staging
трактуются ка к одна и та же среда . Хотя Development, Staging и Production явля
ются традиционными именами сред , вы можете использовать любое желаемое имя. Это
может быть полезно, напри м ер , когда над проектом трудятся несколько разработчи ков ,
каждый из которы х требует разных конфигурационных настрое к. В разделе " Работа со
сло ж ными конфигурациями " далее в главе объясняется, ка к учитывать слож ные различия
ме жду конфигурациями сред.
Глава 14. Конфигурирование приложений 403
Внутри метода Configure () можно выяснить, какая среда размещения приме
няется, прочитав свойство IHostingEnvironrnent. En viro nrnentName или исполь
зуя один из расширяющих методов , которые оперируют над объектами реализации
IHostingEnvironrnent и описаны в табл. 14.10.
Таблица 14.10. Расширяющие методы IHostingEnvironment
Имя Описание
app.UseMvc(routes =>
routes.MapRoute(
name : " default ",
template : " {cont rol ler=Home}/{action=Index}/ {id ?} ");
}) ;
404 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
Листинг 14.25. Добавление пакета для регистрации в журнале в файле project. json
) '
Совет. Посылать сообщения отладки можно не только на консоль и в окно Output. Доступны
также варианты с применением журнала событий и пакетов регистрации от независи
мых разработчиков, таких как Nlog . Для этого понадобится добавить в приложение па
кетMicrosoft . Extensions . Logging. EventLog или Microsoft. Extensions .
Logging . NLog и вызвать метод AddEventLog () или AddNLog () соответственно на
объекте реализации ILoggerFactory внутри метода Configure ().
406 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
uptime up ;
logger log ;
Q rr.tc~! S1t1W1frl'Q1'
~ (- с [р~~!~;!5~~~-;~;~~~~~~.;:~~--~:=-==-~-=~:~::-----------·--1 . ~
An unhandled exceptioп occшrecl while pюcessing t/1e r D """'' - - - - · - - - - ___________ -,
1 . •
~ f\:uliRerereгceExc ep:юn: O~ec t
. , . • J ~ ·, С 1(!) l~fllho$t;5000/Нoin•/1Mi'"?thrc.\\(11.Cf"P!>Ol'l- lrut
reference not Set to an instance 011 an сЬ;есч .. -- ~~--.:;---:=.::.--,:_-. .::-"::-~:---:-:;:-::--·- - - - - - ·
'(( 1
:.:::.=-:-.·:.;с;-:_,_···-::~.;:-.:' - rf
! Ccinfi9ur1r9.Gpps.U)ntro!lf!'S Homl!-ContФ\ler!nde:ir(8ooJtJn 1rtrowE~ci-pt1cn) 1n ~~Controlltr -'~· 11\ :-irм~~I" Thic is tlie Bn-oi· nctio" '.'
; _,J..'·~~~~$·~~s,~~tpti7~t~)~#.,,..
app.UseStaticFiles();
app . UseMvc(routes => {
routes.MapRoute(
name : "defau l t ",
template : " {cont r olle r =Home }/{act i on=Index}/{ i d?} " ) ;
}) ;
С) Re~ vlt х
' - - - - - -----------·---------------'
Рис. 14.13. Включение статического содержимого
}'
" ShortCircuitMiddl eware" :
" EnaЬleBrowserShortCircuit ": true
Имя Описание
"dependencies " :
"Microsoft . NETCore . App 11
:
Чтобы прочитать содержимое файла appse tt ings . j son , в класс Start up добав
л е н кон структор , в которо м с помощью м етода AddJsonFi l e () осуществля ется чте
ние ф айла appset tings . j son (листи нг 14.34).
ряющих методов, таких как AddJsonFile (). Третий шаг предполагает вызов метода
Build () ConfigurationBuilder, который создает структуру пар "'клю ч
на объекте
значение" и разделы , после чего присваивает результат свойству Configuration.
Доступно несколько в ерсий метода AddJsonFile (), описанных в табл. 14.13. Вьпuе
использовалась простейшая версия метода AddJsonFile (), которая сгенерирует ис
ключение, если файл не существует, и будет игнорировать любые изменения в файл е .
Система конфигурации ASP.NET Core подцерживает повторную загруз ку данных , когда фай
лы конфигурации изменяются . Некоторые встроенные компоненты промежуточного ПО , та
кие как система регистрации в журнале, подцерживают эту функциональность, что означает
возможность изменения уровней регистрации во время выполнения без необходимости в
перезапуске приложения. Аналогичные средства можно также предусмотреть при разработ
ке специальных компонентов промежуточного ПО.
Однако просто тот факт, что средство делает что-то возможным, вовсе не означает, что оно
является целесообразным . Внесение изменений в файлы конфигурации производственных
систем - верный способ вызвать простой из-за отказа . Слишком легко допустить ошибку
при модификации и получить неправильно работающую конфигурацию . Даже при успешном
изменении могут возникать непредвиденные последствия, такие как переполнение дисков
Метод Описание
Данные из файла
appsettings . j son доступны через свой ств о Configuration,
добавленное в класс
Startup , которо е во зв раща ет объект, реализующий интерфейс
IConfigurationRoot. Доступ к значениям данных производится посредством ком
бинации членов , определенных упомянутым интерфейсом, и расширяющих методов
из табл. 14.14.
Имя Описание
loggerFactory.AddConsole(LogLevel.Debug);
loggerFactory.AddDebug(LogLevel . Debug);
if (env . IsDevelopment()) {
app.OseDeveloperExceptionPage();
app . OseStatusCodePages() ;
app . OseBrowserLink() ;
else {
app . OseExceptionHandler( " /Home/Error " ) ;
app . OseStaticFiles() ;
app . OseMvc(routes => {
routes.MapRoute(
name : 11 default 11 ,
template : 11 {controller=Home}/{action=Index}/{id?} " );
) ) ;
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug(LogLevel.Debug);
На заметку! Такая фильтрация вступает в силу, только когда приложение запускается из ко
мандной стро ки с применением Kestrel напрямую, как описано во врезке "И спользование
Kestrel напрямую" ранее в главе.
"Logging " : {
"I ncludeScopes ": fa lse,
"LogLevel ": {
"Default ": " Debug",
"System": "Information",
"Microsoft": "Information",
"ConfiguringApps.Controllers.HomeController": "Critical"
}'
"ShortCircuitMiddleware":
" EnaЫeBrowserShortCircuit ": true
Имя Описание
Имя Описание
Совет. Может nоказаться, что файл appsettings . development . j son исчезает сразу же nос
ле его создания. Щелкнув на стрелке слева от заnиси appsettings . j son в окне Solution
Explorer, вы увидите, что Visual Studio груnnирует вместе элементы с nохожими именами.
методов, специфичных для той или иной среды. В рассматриваемом примере это оз
начает, что методы Configur eServi ces () и Configure () будут применяться для
подготовительной и производственной сред.
using Systern . I O;
us ing Mi crosoft .AspNet Core.Hosting ;
namespac e Config u ringApps
puЫic c l ass Pr ograrn {
p uЫi c s t a ti c v oid Ma in (string[] a r gs) {
var host = new WebHo s tBui lde r ( )
. UseKe strel()
. UseConte n tRoot( Directo r y . Ge t CurrentDi re c t ory())
. Use IISin t e gra tion ( )
. UseStartup( " Co nfiguringApps")
. Build () ;
host . Ru n () ;
Рез юме
В настояще й главе рассматривались способы конфигурирования приложений MVC.
Были описаны конфигурационные файлы J SON, объяснены особенности использо
вания класса St artup и дано введение в службы и промежуточное ПО. Вы узнали,
как обрабатыв аются запросы с применением конвейера. и каким образом различные
типы промежуточного ПО используются для управления потоком запросов и ответов.
В следующей главе будет представлена система маршрутизации, посредством кото
рой инфраструктура MVC имеет дело с отображением URL запросов на контроллеры
и действия .
ГЛАВА 15
Маршрутизация URL
в
ранних версиях ASP.NET предполагалось наличие прямой связи между запра
шиваемыми URL и файлами на жестком диске сервера. Работа сервера заключа
лась в получении запроса от браузера и доставке вывода из соответствующего файла.
Подобный подход успешно работал для инфраструктуры Web Forms, в которой каждая
страница ASPX представляла собой и файл, и самодостаточный ответ на запрос .
Это совершенно не имеет смысла в приложении MVC, где запросы обрабатывают
ся методами действий из классов контроллеров, и однозначное соответствие между
запросами и файлами на диске отсутствует.
Для обработки URL в MVC платформа ASP.NET применяет систему маршрутиза
ции, которая была модернизирована для инфраструктуры ASP.NEТ Core. В настоящей
главе вы узнаете, как использовать систему маршрутизации для обеспечения мощной
и гибкой обработки URL в своих проектах . Вы увидите, что система маршрутизации
позволяет создавать любой желаемый шаблон URL и выражать его в ясной и лаконич
ной манере. Система маршрутизации выполняет две функции.
Вопрос Ответ
дящие URL
Чем она полезна? Система маршрутизации обеспечивает гибкую обработку запросов
без привязки URL к структуре классов в проекте Visual Studio
переменных маршрутизации
"dependencies ": {
"Mi cro s oft . NETCore . App ":
"ve r s i o n": " 1 . 0 . 0 ",
"type ": "pla t f o rm"
} 1
"frameworks": {
" netcoreappl.O ":
"imports": [ " dotnet5 . 6", "portaЬle - net45+winB " ]
},
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Dependencyinjection ;
namespace UrlsAndRoutes
puЫic class Startup {
puЫic void ConfigureServices(IServiceCollection services) {
services.AddМvc();
Свойства Cont r o l ler и Ac t ion будут использоваться для у1<азания того, как за
прос был обработан, а словарь Da ta - для хранения других деталей о запросе, выда
ваемых систе мой маршрутизации.
Создание контроллеров
Для демонстрации работы маршрутизации нам нужны про ст ые контроллеры. Соз
дайте папку Control lers и добавьте в нее файл класса по им ени HomeController . cs
с содержимым из листинга 15.4.
Аналогично тому, как было описано выше, добавьте в папку Contro ll ers
файл класса Cu st o merControl l er . cs с определением контроллера Customer
(листинг 15.5).
Создание представления
Во всех методах действий, определенных в предыдущем разделе, указано представ
ление Resul t, что позволяет создать одно представление, которое будет разделяться
всеми контроллерами. Создайте папку Views/Shared и добавьте в не е новый файл
представления по имени Resul t. cshtml с содержимым, показанным в листинг е 15.7.
Листинг 15. 7. Содержимое файла Resul t. csh tml
@model Result
@{ Layout = null ;
<IDOCTYPE html>
<html>
<head>
<meta name= " viewport " content="width=device - width " />
<title>Routing</title>
<link rel= "stylesheet " asp- href-include= "lib/bootstrap/dist/css/*.min.css" />
</head>
<body class= "panel-body " >
<tаЫе class= " taЫe taЬle -b ordered taЫe - striped taЬle-condensed">
<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аЫе>
</body>
</html>
432 Часть 11 . Подробные сведения об инфраструктуре ASP.N ET Core MVC
@using UrlsAndRoutes.Models
@addTagHelper * , Microsoft.AspNetCore . Mvc . TagHelpers
http://mysite.com/Admin/Index
Глава 15. Маршрутизация URL 433
URL могут быть разбиты на сегменты - части URL за исмючением имени хоста и
строки запроса, rюторые отделяются друг от друга символом /.В приведенном выше
примере URL есть два сегмента, как по1<азано на рис. 15.2.
http://mysite.com/Admin/Index
t
Первый
t
Второй
сегмент сегмент
Первый сегмент содержит слово Admin, а второй - слово Index . В глазах чело
Controller: AdmlnController
Action: lndex
Корневой URL для приложения не работает из-за того, что маршрут, добавленный
в файле Startup. cs, не сообщает инфраструктуре МVС о том, как выбирать класс
контроллера и метод действия, когда URL запроса не содержит сегментов . Мы испра
вим это в следующем разделе.
Ранее объяснялось, что шаблоны URL будут соответствовать только URL с указан
ным количеством сегментов. Один из способов изменить такое поведение предусмат
ривает применение стандартных значений. Стандартное значение используется,
когда URL не содержит сегмент, который можно было бы сопоставить с шаблоном
маршрута . В листинге 15. 11 определяется маршрут, использующий стандартное
значение.
routes .MapRoute (
name: "default",
template: "{controller}/{action}",
defaul ts: new { action = "Index" }) ;
}) ;
436 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
Controller: AdminController
Action: lndex
D Routing Х
f- - С ГФ lo~Jh~~\~s2994====~~=-=-!:J
. -· ---· - - · - -
:
" - - - - -- - - - - - - · - - - - · - - - - - - - - - · ---1
1
Controller: HomeController li
Action: lndex
__________"_________ --________J
l__________
Рис. 15.5. Применение стандартных значений для расширения области действия маршрута
Это можно сделать с применением шаблона URL, подобного показанн ому в лис
тинге 15.14.
Листинг 15.14. Шаблон URL со статическими сегментами в файле Startup. cs
using Microsoft.AspNetCore.Builder ;
using Microsoft . Extensions . Dependencyinjection ;
namespace UrlsAndRoutes {
puЫic c l ass Startup {
puЫic void ConfigureServices(IServiceCol l ection services) {
services . AddMvc() ;
Новый шаблон будет соответствовать только URL, содержащим три сегмента, пер
вым из которых должен быть PuЬlic . Остальные два сегмента могут содержать лю
бые значения, и они будут использоваться для переменных controller и a ction.
Если последние два сегмента не указаны. тогда будут применяться стандартные
значения.
Глава 15. Маршрутизация URL 439
Можно также создавать шаблоны URL, которые имеют сегменты, содержащие как
статические, так и переменные элементы, вроде шаблона в листинге 15. 15.
routes . MapRoute(
name : 11 default 11 ,
template: 11 {controller=Home}/{action=Index} 11 ) ;
routes. MapRoute (narne : "",
11
template : P uЬlic/{controller=Home}/{action =I ndex} ") ;
}) ;
D Routing
f- - С ГФ localt;~st52994/Xнome/111dex --
Controller: HomeController
1
с
lndex
Упорядочение маршрутов
В листинге 15.15 новый маршрут был определен и помещен перед другими маршрутами .
Так было сделано потому, что маршруты применяются в порядке, в котором они определе
ны . Метод MapRoute () добавляет маршрут в конец конфигурации маршрутизации, а это
означает, что обычно маршруты применяются в порядке их определения. Мы сформулиро
вали "обычно" , т.к . есть методы, которые позволяют вставлять маршруты в специфические
позиции. Я стараюсь не использовать такие методы, поскольку применение маршрутов в
порядке их определения упрощает понимание маршрутизации в приложении.
Тогда первый маршрут, который соответствует любому URL с нулем, одним или двумя сег
ментами, будет использоваться всегда. Более специфичный маршрут, который теперь вто
рой в списке , никогда не будет достигнут. Новый маршрут исключает ведущую букву х из
URL, но это не будет сделано из-за совпадения со старым маршрутом. Таким образом, URL
такого вида :
С) Rout ing х
r---- ------"'==
~ С ~ loca lhost:S2:i94/Shop/lndr.>: .
-----------··· =====:::::::
1 Controller: HomeController
1 Act/On' lnde)(
1___ - · - - - - · - - - - - - - - - - - - - - - - - - - - - - - - - - · - - - - - -
Шаблон URL опр едел яет стандартные переменные controller и act i on, а так
же специальную п е р еменную по имени id. Маршрут будет соответствовать URL с
количеством сегментов от нуля до трех . Содержимое третьего сегмента присваива
етс я п е р емен ной id, а при отсутствии третьего сегмента применяется стандартное
з н а чени е.
В классе
Controller, который является базовым для контроллеров, определе
но свойство RouteData . Это свойство во звращает объект Microsoft . AspNetCore .
Routing . RouteData, предоставляющий детали о системе маршрутизации и способе,
которым был маршрутизирован текущий запрос. Внутри контроллера можно полу
ч ать доступ к любой п е ременной сегмента в методе действия с применением с войс
тваRouteData . Values, которое возвращает словарь, содержащий переменные сег
мента. В цел ях демонстрации в контроллерHome добавлен метод дей ствия по имени
CustomVariaЫe (), пр и веденный в листинге 15.19.
Шаблон маршрута распознает третий сегмент в данном URL как значение для пе
ременной id, давая приведенный на рис. 15.8 результат.
CJ Routing х
Controller: HomeController
Action: CustomVari aЫe
Шаблон URL в листинге 15. 19 определяет стандартное значение для сегмента id,
т.е. маршрут может также соответствовать URL, которые имеют два сегмента. Увидеть
применение стандартного значения можно, запросив такой URL:
/Home/CustomVariaЫe
D Routin9 х
Controller: HomeController
Action: CustomVariaЫe
id: Defaultld
_J
Рис. 15.9. Стандартное значение для специальной переменной сегмента
___J___________________________J
::::,::: 1
Данный маршрут будет соответствовать URL, которые имеют или не имеют сег
мента id. В табл. 15.5 демонстрируется его работа с различными 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: HomeCoпtrolle r
Action: CustomVariaЫe
Количество
Пример URL На что отображается
сегментов
о / controller=Home action=Index
/Customer control l er=Customeraction=Index
2 /Customer/List controller=Customeraction=List
3 /Customer/List/All controller=Customer action=List id=All
4 /Customer/List/All/Delete control l er=Customeraction=Listid=All
catchall=Delete
5 /Customer/List/All / controller=Customer act i on=
Delete/Perm List id=All catchall=Delete/ Perm
Верхнего предел а количества се гментов , для которого шаблон URL в этом марш
руте будет давать совпадение, не существует. На рис . 15.12 пока з ан э ффект от опр е
деления сегмента общего з ахвата. Обратите внимание, что сегменты , попадающие в
переменную общего захвата, представлены в форме с е гмент/ сегмент/ сегмент , и
мы сами отвечаем з а обработку строки для ее р а збиения н а отдельны е се гменты.
D Rou tiog х
f-
- -
С [Фlocalhost:52994/C~tomer/list/Hl?Ho/1f2/3
--
·----·-*l
---~-..:--:":':".:=:=--:-:-:-=--:-:--=-:-·=--=-==-=-~-=-=-.--:::::--:-:=..~~
Controller: HomeController
Action: List
id : Hello
catchall : 1/2/3
_______________________________] 1
Ограничение маршрутов
В начале главы было указано , что шаблоны URL консервативны в отношении коли
чества сопоставляемых сегментов и либеральны в отношении содержимого сопостав
ляемых сегментов. В нескольких предшествующих разделах объяснялись различные
приемы управления степенью консервативности: увеличение или уменьшение числа
Ограничения также м огут быть указаны за пр еделам и шабл она URL с использо
ванием аргум е нта constra in ts метода MapRoute () при определ е нии м а ршрута.
Такой прием удобен , если вы предпочитаете удержи вать шаблон URL отдел ьно от ог
раничений или следовать стилю маршрутизации, принятому в бол ее ранних в е р с иях
MVC , где встро е нны е огр аничения н е подде рживались. В листинге 15.26 показ ано то
же самое огран ич ение int на перем енно й сегмента id, выраженн о е с прим ен е ни ем
отдельного ограничения. При использовании такого формата стандартны е з нач е ния
также выражаются вн е шне.
Совет. Можно ограничить доступ к методам действий со стороны запросов, которые сдела
ны с помощью специфических НТТР-команд, таких как GE T или POST , используя набор
атрибутов MVC наподобие HttpGet и HttpPost. В главе 17 описано применение этих
атрибутов для обработ ки форм в контроллерах , а в главе 20 приведен полный список до
ступных атрибутов.
Встроенное
Описание Имя класса
ограничение
Встроенное
Описание Имя класса
ограничение
Объединение ограничений
Если необходимо применить 1t одиночному элементу сразу несколыш ограничений,
тогда их можно соединить в цепочку, отделяя каждое ограничение от других двоето
дней недели, определенных в статическом пол е Days . В листинге 15.33 новое ограни
чение применяется к маршруту с использованием синтаксиса отдельно определяемых
ограничений.
Использование маршрутизации
с помощью атрибутов
Во всех примерах, приведенных до сих пор в главе , маршруты определялись пос
редством приема, который называется маршрутизач,ией на основе соглаше ний.
Инфраструктура MVC также поддерживает прием, известный как маршрутизач,ия
с помощью атрибутов, когда маршруты определяются через атрибуты С# , которые
применяются напрямую к классам контроллеров. В последующих разделах будет по
казано, как создавать и конфигурировать маршруты с использованием атрибутов.
которые могут свободно смешиваться с маршрутами на основ е соглаш е ний, проде
монстрированными в пр едшествующих примерах .
Маршрут Описание
Совет. Изменить имя действия можно также с помощью атрибута ActionName, который
рассматривается в главе 31 .
Маршрут Описание
Можно использовать все ограничения, описанные в табл. 15.8, или, как проде
монстрировано в листинге 15.39, применять специальные ограничения, которые
были зарегистрированы с помощью службы RouteOptions. Чтобы применить сразу
несколько ограничений, их понадобится соединить в цепочку, отделяя друг от друга
двоеточиями.
Резюме
В этой главе подробно обсуждалась система маршрутизации. Вы увидели, как оп
ределяются маршруты по соглашениям и с помощью атрибутов, каким образом сопос
тавляются и обрабатьmаются входящие URL, и как настраиваются маршруты за счет
изменения способа, которым они сопоставляются с сегментами URL, а также за счет
использования стандартных значений и необязательных сегментов. Кроме того, бьmо
показано, каким образом ограничивать маршруты, чтобы сузить диапазон запросов,
для которых они будут давать совпадение, с применением встроенных ограничений и
специальных классов ограничений.
В следующей главе мы рассмотрим генерацию исходящих URL на основе маршру
тов внутри представлений и выясним, как использовать средство областей, которое
полагается на систему маршрутизации и может применяться для управления круп
маршрутизации
Вопрос Ответ
разделов
Чем они полезны? Каждое средство полезно по своим соображ ениям. Наличие воз
можности генерации URL облегчает изменение схемы URL без
необходимости в обновлении всех представлений . Существование
возможности использования специальных классов позволяет под
Совет. Если вы не хотите воссоздавать примеры вручную , тогда загрузите готовые проекты
Visual Studio из веб-сайта издательства.
После запуска приложения браузер запросит стандартный URL, который будет на
правлен на действие Index контроллера Home (рис. 16. 1).
D Routing )(
--~---°--[~;.~~-~~~s-~~~o;~a~:.==~-=-===~·-===- ~]---~!
Controller: HomeController 1
Action: lndex 1
________________________ )
[j Rou tin9 Х
1 - ~ с [? -~;;;ih-;!:60588
1 ··-------·------- -- -· " ·--- "
1
Controller: HomeController
Action: lndex
~----------------------------
routes.MapRoute(
name: "NewRoute",
template: "App/Do{action}",
defaults: new { controller ="Ноте" }) ;
routes.MapRoute (
name: "default",
template: "{controller=Home)/{action= Index)/{id?)");
}) ;
Новый маршрут изменяет схему URL для запросов, которые нацелены на контрол
лер Home. Запустив приложение, вы увидите, что это изменение отразилось в НТМL
разметке, сгенерированной дескрипторным вспомогательным классом:
[J Routing Х
~ С 1· Q ~~lh~st:WS Sffl.p;/DoC~s;o~Vи11aЫ~- . -
Controller: HomeCoпtroller 1
1
Action: CLJstomVarlaЫe
1
Вы уже видели, что изменение маршрутов, определяющих схему URL, изменяет способ ге
нерации исходящих URL. В приложениях обычно определено множество маршрутов, поэто
му важно понимать, как они выбираются для генерации URL. Система маршрутизации обра
батывает маршруты в порядке их определения, и каждый маршрут проверяется по очереди
на предмет соответствия, что требует удовлетворения следующих трех условий.
• Для любой переменной сегмента, определенной в шаблоне URL, должно быть доступно
значение. Чтобы найти значения для каждой переменной сегмента, система маршрути
зации просматривает сначала предоставленные (посредством свойств анонимного типа)
значения, затем значения переменных для текущего запроса и, наконец, стандартные
Чтобы было совершенно ясно : система маршрутизации не пытается найти маршрут, который
дает наилучшее совпадение . Она находит только первое совпадение и использует данный
маршрут для генерации URL; любые последующие маршруты игнорируются. По этой при
чине наиболее специфичные маршруты должны определяться первыми. Важно проверить
генерацию исходящих URL. Попытка генерации URL, для которого не удается найти соот
ветствующий маршрут, приведет к созданию ссылки , содержащей пустой атрибут href:
<а href='"' >This i s an outgo in g URL< / a>
Та кая ссылка корректно визуализируется в представлении, но не будет функционировать
ож идаемым образом, когда пользователь выполняет на ней щелчо к. Если вы генерируете
только URL (как будет показано поз же в главе), тогда результирующим значением будет
null , которое визуализируется в виде пустой строки в представлениях. С помощью име
нованных маршрутов можно получить определенный контроль над сопоставлением маршру
тов . За более подробными сведениями обратитесь в раздел " Генерирование URL из специ
фического м аршрута" далее в главе.
В случае указ а ния атрибута asp - action внутри эл ем ента а де скрипторны й вспо
м огательный класс пр едполагает, что вы хотите установить в качестве цели действие
в том ж е контроллере, который вызвал визуализацию представления. Чтобы создать
исходящий URL, который направляет на другой контроллер, можно применить атри
бут asp - controller (листинг 16.4).
</tаЫе>
<а asp-controller="Aclmin" asp-action="Index">This is an outgoing URL</a>
</body>
</html>
@model Result
@{ Layout = null;
< ! DOCTYPE html>
<html>
<head>
<meta name= " viewport " content= " widt h=device - width " />
<title>Routing</title>
<link rel=" stylesheet " asp-href-i nclude= " liЬ/bootstrap/dist/ css/* . min . css " />
</head>
<body class= " panel - body " >
<tаЫе class= " taЫe taЫe - bordered taЫe-striped taЫe - condensed " >
<tr><th>Controller : </th><td>@Model . Controller</td></tr>
<tr><th>Action : </th><td>@Model . Action</td></t r >
@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>
[Route("app/[controller]/actions/[action]/{id:weekday? }")]
puЫic class CustomerControl l er : Contro l ler {
Глава 16. Дополнительные возмо ж ности маршрутизации 473
Здесь предоставляется значение для переменной сегмента по имени id. Если при
ложение применяет маршрут, показанный в листинге 16.3, тогда при визуализации
представления будет получена следующая НТМL-разметка :
При описании способа сопоставления маршрутов для исходящих URL объяснялось, что ког
да система маршрутизации пытается найти значения для каждой переменной сегмента в
шаблоне URL маршрута, она просматривает текущий запрос. Такое поведение сбивает с
толку многих программистов и может вылиться в длительный сеанс отладки.
<а asp - controller= " Home" asp -a ction= "Index" asp -r oute - page="789 " >
This is an outgoing ORL
</а>
Глава 16. Дополнительные возмож ности маршрутизации 475
Мож но было бы ожидать , что система маршрутизации не сумеет выполнить сопоставление с
маршрутом, т. к . не было предоставлено значение для переменной сегмента colo r , к тому
же для нее не определено стандартное значение . Однако это не так. Система маршрути
зации будет сопоставлять с определенным ранее маршрутом. В результате сгенерируется
следующая НТМL-разметка:
<а hre f = " / Home/ Index/Red/789 " >This i s a n outgo i ng URL </a>
Листинг 16.8. Генерирование полностью заданного URL в файле Resul t. csh tml
@model Resu l t
@{ Layout = null ;
< ! DOCTYPE html>
<htm l >
<h ead>
<meta name= " vie wpo rt" con t en t = " wi dt h=dev ice-wid t h " />
<title>Rout in g< /titl e>
<link rel= " styl e sheet" asp - href - inc lude ="liЬ/b ootstra p /d i st/c s s /*.min. css " />
</head>
<body class= " pane l- body " >
<tаЫе class= " taЫe ta Ы e - bordered t aЫe - striped taЬle - condensed " >
<tr><th>Con tro l ler : </th><td>@Model . Controller</ t d></t r >
<tr><th>Action : </th><td>@Model . Ac ti on</ t d></tr>
@foreach (string key in Model . Da t a .Keys) {
<tr><th>@key : </th><td>@Mode l. Dat a[ 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>
476 Часть 11. Подробные сведения об инфраструктуре ASP.NET Соге MVC
<а href= " https : //myse r ver . mydomain . com/Home/Index/Hello#myFragment " >
This is an outgo i ng URL
</а>
Будьте осторожны при использовании полностью заданных URL, поскольку они со
здают зависимости от инфраструктуры прилож ения и. когда инфраструктура изменя
ется, вы должны не забыть о внесении соотв етствующих измен е ний в представления
мvс.
@model Re s ul t
@( La yout = null;
< !DOCTYPE html>
<html>
<head>
<meta name ="v i ewport " content= "wi dt h=devic e - wi dth " />
<t itle>Routing</title>
<l i nk re l="stylesheet " asp-h r ef- incl ude="liЬ/b o otstrap /dist/cs s / * .m i n.css" />
</head>
<body cla ss=" pane l- body ">
<tаЫе class= " ta Ы e ta Ьl e - bo r de r ed ta Ы e -s t rip ed t a Ы e - conde n s e d " >
<tr><th>Controller : </th>< t d>@Mode l . Co nt roll e r</ t d></tr>
<tr><th>Action : </th><td>@Model . Acti on</td></ t r>
@foreac h (s tr in g key in Mode l. Data .Keys ) (
<tr> <th >@ ke y : </th>< t d>@Model.Data[key] </td>< / t r>
</tаЫе>
<а asp-controller="Home" asp-action="CustomVariaЬle">
This is an outgoing URL</a>
<а asp-route="out">This is an outgoing URL</a>
</body>
</html>
<а href =" /Home/Cus t om V ariaЬl e" > T his is a n outgo i ng URL</a>
<а hr ef= " /outbound " >This is an out go i ng URL</a>
Проблема с зависимостью от имен маршрутов при генерации исходящих URL связана с тем,
что это нарушает принцип разделения обязанностей , который занимает центральное место
в паттерне проектирования MVC. При генерации ссылки либо URL в представлении или ме
тоде действия необходимо сосредоточиться на действии и контроллере , куда пользователь
будет направлен, а не на формате URL, который будет использоваться. Привнося знание о
различных маршрутах в представления или контроллеры, мы создаем зависимости, которых
@model Result
@{ Layout = null ;
< ' DOCTYPE html>
<html>
<head>
<meta name= " viewpo rt" content= " width=device-width " />
<title>Routing</title>
<link rel= "st ylesheet" asp-href-include= "l iЬ/bootstrap/dist/css/* . min.css" />
</head>
<body class= " panel-body">
<tаЫе class= " taЫe taЬle-bordered taЫe-striped taЬle-condensed " >
<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><t d>@Model.Data[key]< /td></t r>
</tаЫе>
<p>URL: @Url.Action("CustomVariaЬle", "Home", new { id = 100 })</р>
</body>
</htrnl>
<p>URL: /Home/CustomVariaЬle/100</p>
[j Routil\g х
~ ' С ~a lhost:60588/Horne/CustornVarraЫe
Controller: HomeController
Action: Cu stomVariaЫe
1 id : <ПО value>
1 ~~l• /Hom&C"'tom,.V-/-:ro-i-~be/-le-~-~-:-o_m_V_a-ri_a_Ы_e_/1_0_0-.
L _ __
Рис. 16.4. Генерация URL в методе действия
D Routing х
С [.--··--·-·--···--------·-·-----····"------·· ----·-·---··l
Ф locall1ost:б0588/Home/CunomVaпaЬle -Q-
'---------------·
- -· - .... - .
~ ~ ~ ' -·-~· ~- --- - ....... --
- · - - -......
' --
Controller: HomeController
Action: CustomVariaЫe
1 URL: /home/cus1omvaпaЫe/1001
!_____________________. - ---·--- ---·-·----- ------·-- - - - " - ·--
J
Рис . 16.5. Конфигурирование системы маршрутизации
Метод Rou t eAsync ( ) отвеча ет за оценку, может ли запрос быть обработан, и е сли
может, то за управление процессом, посредством которого генерируется отв ет, отправ
Имя Описание
[J l0<alhost:60S88/articles: Х
l~f- ·. С Lso_~~c~~~t:бOSB~:r~~cles.№~doщs;~.1_0verv1e111.html -~ :
--·-·--------~---··--· ~---- -----~~---- ·
Совет. Обрат и те внимание , что в листинге 16.16 аргумент метода View() приводится к
типу obj ect. Одна из перегруженных версий метода View () прини мает параметр типа
s tr ing, указывающий имя представления для визуализации , и без приведения компи
лятор С# выберет именно ее. Во избежание этого мы выполняем приведение к obj ect,
чтобы однозначно вызывалась перегруж енная версия, которая принимает модель пред
ставления и применяет стандартное представление . Проблему можно было бы также
решить за счет использования перегруже нной версии, принимающей как и м я представ
ления, так и модель представления, но предпочтительнее не делать явны х ассоциаций
между методами действий и представлениями , если это возмож но. За дополнительными
сведениями обращайтесь в главу 17.
Создайте папку Views/Legacy и поме стите в не е файл пр едс тавл ения по имени
GetLegacyUrl. cshtml с содержимым из листинга 16.17. Новое представление отоб
ражает значение модели, которое покажет запрошенный клиентом URL.
@model string
@{ Layout = null;
< !DOCTYPE html>
<htm l >
<head>
<meta name="vi ewport " content= " width=device-width " />
<title>Routing</tit l e>
<link rel="stylesheet" asp-href-include="liЬ/bootstrap/dist/css/* . min . css " />
</head>
<body class= "panel - body " >
<h2>GetLegacyURL</h2>
Th e URL requested was : @Model
</body>
</html>
context.RouteData.Values["controller"] = "Legacy";
context.RouteData.Values["action"] = "GetLegacyUrl";
context.RouteData.Val.ues["l.egacyUrl"] = requestedUrl;
await mvcRoute.RouteAsync(context);
GetlegacyURL
The URI_ requested was: raгtic les/Windows_з. 1 _overview.html
1
---~----------·-----~---- ... ·-·------·------·------------------.....1"
return null;
Имя Описание
AmЬientValues Это свойство возвращает словарь значений, которые полезны для гене
рации URL, но не будут встраиваться в результат. При реализации собс
твенного класса маршрутизации такой словарь обычно пуст
@model Resu l t
@{ 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 Ыe -strip ed taЬle - condensed " >
<tr><th>Contro l ler : </th><td>@Model . Cont roll er</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_З.l_Overview.html"
class="Ьtn Ьtn-primary">This is an outgoing URL
</а>
<p>URL: @Url.Action(null, null,
new { legacyurl = 11 /articles/Windows_З.l Overview.html"})</p>
</body>
</html>
Глава 16. Дополнительные воз м ожности маршрутизации 491
В э том пример е нет необходимости указывать дескрипторному вспомогательному
классу контроллер и действие для исходящего маршрута, потому что при генерации
URL они не применяются. Учитьmая это, атрибуты дескрипторного вспомогател ьного
класса asp - control ler и asp - action в элементе а опушены. Когда генерируется
только URL, первые два аргумента для вспомогательного метода Url . Action () уста
н авливаются в null по той же причине.
Если вы запустите приложение и просмотрите НТМL-размет1{у, полученную в от
вет на запрос стандартного URL, то увидите, что для создания URL использовался
специальный класс маршрута:
<а class= " Ьtn Ьtn - primary " hr ef= "/articles/windows_З .l_overview.html/" >
This is an outgoing URL
</а>
<p>URL : /articles/windows_З.l_overview.html/ </p>
Совет. Если URL, который вы видите в НТМL-ответе, имеет отличающийся формат, такой
как/?legacyurl = %2Farticles %2FW indows 3 . l_Overview .html , тогда для ге
нерации URL специальный маршрут не применялся , а вместо него был вызван какой-то
другой маршрут в прило жении. Поскольку никакого контроллера или действия не было
у казано, произошло нацеливание на действие Index контроллера Home, и к строке за
проса URL добавилось значение legacyU rl. В таком случае удостоверьтесь в установ
ке свойства IsBound в true внутри метода GetVirtualPath () , и проверьте , что в
конфигурации внутри файла Startup. cs указаны корректные URL для конструктора
класса LegacyRoute, а специальный маршрут определен перед всеми остальными
маршрутами .
Работа с областями
Инфраструктура ASP.NET Core MVC поддержив ает организацию веб-прилож е ния в
виде областей, где каждая область представляет функциональный сегмент приложе
ния, такой как администри рование , выписка счетов- фактур. поддержка пользо вате
лей и т. д . Это удобно в крупных проектах, в которых наличие единственного набора
папок для всех контроллеров, представлений и моделей может привести к сложнос
тям в управлении.
Каждая область МVС имеет собственную структуру папок, позволяя все хранить
отдельно. Такой подход делает более очевидным то, 1<акие элементы проекта относят
ся к каждой функциональной области приложения, и помогает множеству разраб от
чиков т рудиться над проектом, не конфликтуя друг с другом. Области в значительной
степени поддерживаются системой маршрутизации и потому они рассматриваются
вместе с URL и маршрутами. В настоящем разделе будет показано, как наст раивать
и пользоваться областями в проектах MVC.
492 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
Создание области
Создание области требует добавления папок в проект. Папка верхнего уровня на
зывается Areas . Внутри н ее для каждой необходимой области пр едусмотр ен а своя
пап к а, содержащая собственные подпапки Con t rollers , Vi ews и Models. Мы пла
нируем создать обл асть по имени Adrnin , что означает создание набора папок, опи
санных в табл. 16.6. Для подготовки примера проекта со здайте вс е папки, п е речис
л енные в табл. 16.6.
Имя Описание
Areas Эта папка будет содержать все области в прилож ении MVC
Ar eas/Admin Эта папка будет содержать классы и представления для
области Admin
Areas/Adrnin/Controllers Эта папка будет содержать контроллеры
для области Admin
Ar eas/Admin / Views Эта папка будет содержать представления
для области Admin
Are as/Adrnin/Views/ Horne Эта папка будет содержать представления для контролле
ра Horne в области Admin
Are as/Admi n/Mode l s Эта папка будет содержать модели для области Admin
routes . MapRoute(
name : "default 11
,
}) ;
Заполнение области
Контроллеры, представления и м одели в области можно создавать точно так же,
как в главной части приложения MVC. Чтобы создать модель, щелкните правой кноп
кой мыши на папке Areas/Admin/Models, выберите в контекстном меню пункт
Add <=:>Class (Добавить <=:> Класс) и создайте файл класса по имени Person . cs, содержи
мое которого показано в листинге 16.24.
494 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
};
Новый контроллер в целом похож на стандартный кроме одного аспекта. Чтобы ас
социировать контроллер с областью, к классу должен быть применен атрибут Area :
[Area ( 11 Admin 11 ) ]
Без атрибута Area контроллеры не будут принадлежать обл асти, даже если они не
определены в главной части приложения. Отсутствие атрибута Area может привести
к странным р езультатам. Это первое, что следует проверять , если при работе с облас
тями получаются не те результаты, которые ожидались.
Совет. Если для настройки маршрутов используются атрибуты, как было описано в гла
ве 15, тогда с применением маркера Rou te можно ссы
[ area J в аргументе атрибута
латься на область , указанную с помощью атрибута
Area : [Route ( [area] /арр/ 11
@model Person [ ]
@{ Layout = null ;
<!DOCTYPE html>
<html>
<head>
<meta name= " viewpo r t " content=" wi dth=de v i ce - wi dth " />
<t i tle>Areas</title>
<link re l =" stylesheet" a sp- href- inc lude=" liЬ/ boo ts trap/dist/ css/* .min. css " />
</head>
<body class =" pa nel - body" >
<tа Ы е c l ass= " taЫe taЫe-borde r ed t aЬl e -strip ed taЫe - condensed " >
<t r ><t h>Name</th><th>City< / t h>< / t r>
@f o r each (P e rson р in Mo de l) {
<tr><td>@p . Name</td><td>@p . Ci ty</ td></t r >
</tаЫе>
</body>
</html>
Name City
Alice London
ВоЬ Paris
</tаЫе>
<а asp-action="Index" asp-controller="Home">Link</a>
</body>
</html >
Запустив приложение и запросив URL вида / a d.mi n, вы заметите, что ответ содер
жит следующий элемент:
</tаЫе>
<а asp - action="Index" asp - co ntroll e r="Home " >Link </a>
<а asp-action="Index" asp-controller="Home" asp-route-area="">Link</a>
</body>
</html>
На заметку! Чтобы было предельно ясно: я глубоко уважаю компанию Amazoп, которая про
дает больше моих книг, чем все остальные вместе взятые. Мне известен факт, что все
сотрудники Amazoп - замечательно умные и приятные люди . Никто из ни х не окажется до
такой степени мелочным, что прекратит продажи моих книг из-за настолько несуществен
ного, как критика применяемого в Amazon формата URL. Я люблю Amazon. Я преклоняюсь
перед Amazoп . Я просто х очу, чтобы они привели в порядок свои URL.
лось. Позже в компании обнаружили, что поисковый агент наткнулся на URL адми
нистративной страницы и прошелся по всем ссылкам на удаление. Аутентификация
может защитить от таких действий со стороны пользователей, но не способна обеспе
чить защиту от веб-акселераторов.
Р езюме
В этой глав е рассматривались дополнительные возможности системы маршру
тизации. Вы узнали, как генерировать исходящие ссылки и URL, а также научились
настраивать систему маршрутизации. По ходу дела вы ознакомились с концепцией
областей и с рекомендациями относительно того, как должна создаваться удобная и
содержательная схема URL. В следующей главе мы перейдем к исследованию контрол
леров и действий, которые являются центральной частью инфраструктуры ASP.NET
Core МVС. Будет подробно описана их работа и показано, как их применять для полу
чения наилучших результатов в приложении.
ГЛА В А 17
Контроллеры и действия
Вопрос Ответ
Что это такое? Контроллеры содержат логику для получения запросов, обновления
состояния или модели приложения и выбора ответа, который будет от
правлен клиенту
Чем они полезны? Контроллеры являются центральной частью проектов MVC и содержат
логику предметной области для веб-приложения
Как ОНИ Контроллеры - это классы С#, открытые методы которых вызываются
используются? для обработки НТТР-запроса. Методы могут брать на себя ответствен
ность за выдачу ответа клиенту напрямую, но более распространенный
подход предусматривает возвращение результата действия, который
сообщает MVC, как ответ должен быть подготовлен
Существуют ли Если вы - новичок в области разработки приложений MVC, то можете
какие-то скрытые легко создавать контроллеры, содержащие функциональность, которая
ловушки или больше подходит для модели или представления. Более специфичная
ограничения? проблема связана с тем, что любые открытые классы с именами, закан
чивающимися на Con t r o ller, инфраструктура MVC считает контрол
лерами; это означает возможность непредумышленной обработки НТТР
запросов в классах, которые не планировались быть контроллерами
Существуют ли Нет, контроллеры - это основная часть приложений MVC
альтернативы?
На заметку! Настоящая глава содерж ит модульные тесты для основных средств . Для крат
кости проекты модульного тестирования в инструкции по созданию примера проекта не
"dependenc ies": {
"Microsoft . NETCore . App ":
" version ": " 1 . 0 . 0 ",
" type ": "platform "
} ,
"Microsof t.AspN etCore . Diagnostics ": " 1 . 0.0 ",
"Microsoft . AspNetCore . Server . IISintegration ": "1.0 . 0 ",
"Microsoft . AspNetCore . Server . Kestrel ": " 1 . 0 . 0",
"Microsoft . Extensions .Logging.Console ": " 1 . 0 . 0",
"Microsoft. AspNetCore. Mvc" : "1. О. О" ,
"Microsoft.AspNetCore.StaticFiles": 11 1.0.0 11 ,
502 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
"Microsoft . AspNetCore.Razor.Tools": {
"version": "1. О. O-preview2-final",
"type": "build"
}/
}'
" b u ildOptions ":
" emit Ent r yPoint ": t r u e ,
" pre s erve Comp i la ti o nCo nt ext ": t r u e
В дополн ение к базовым пак етам MVC были добавлены пакеты, тр ебующие ся для
хр анилища сеанса. В листинге 17.2 приведен класс Startup. которы й конфигуриру ет
приложение в соотв етствии с пакетами NuGet.
пром ежуточного ПО MVC , и модифицировать ответы после того, как они были сгене
рированы. Друтие методы настраивают стандартные пакеты, которые рассматрива
лись в главе 14.
Подготовка представлений
Основное внимание в главе уделяется контроллерам и их методам действий , поэ
тому классы контроллеров будут определяться в главе повсеместно. В качестве под
готовки будут определены представления, которые помогут демонстрировать работу
контроллеров и методов действий. Созданные в настоящем разделе представления
находятся в папке Views/Sha r ed, что позволит их использовать в любых контрол
лерах. которые будут определяться позже в главе. Создайте папку Views/Shared , до
бавьте в нее файл представления Razor по имени Resul t . cshtml и поместите в файл
разметку, показанную в листинге 17.3.
</tаЫе>
</body>
</html>
Понятие контроллеров
Контроллеры - это классы С#, открытые методы которых (известные как методы
дейсmвий или просто действия) отвечают за обработку НТТР-запроса и подготовку
ответа, возвращаемого клиенту. Для определения, какой класс контроллера и метод
действия должны обработать запрос, инфраструктура MVC использует систему мар
шрутизации, описанную в главах 15 и 16. Затем она создает новый экземпляр класса
контроллера , вызывает метод действия и применяет результат метода для выпуска
ответа клиенту .
Когда МVС вызывает метод действия, ответ из метода описывает ответ, который
должен быть послан клиенту . С амый распространенный вид ответа создается путем
визуализации представления Razor, поэтому метод действия использует свой ответ,
чтобы сообщить MVC, какое представление задействовать и какие данные модели
представления ему предоставить. Но имеются также другие виды ответов, и методы
действий могут делать все, что угодно, от требования у MVC отправки клиенту пере
направления НТГР до отправки сложных объектов данных .
Таким образом, существуют три области функциональности , которые важны для
понимания контроллеров. Во-первых, важно понимать особенности определения кон
троллеров, так чтобы инфраструктура MVC могла применять их для обработки запро
сов. Контроллеры представляют собой просто классы С#, но создавать их можно раз
ными способами , и важно уяснить отличия между ними. Определение контроллеров
рассматривается в разделе " Создание контроллеров" далее в главе.
Во-вторых, важно понимать, каким образом МVС снабжает методы действий дан
ными контекста. Получение необходимых данных контекста важно для эффективной
разработки веб-приложений, а инфраструктура MVC облегчает его, определяя набор
классов, которые используются с целью описания всего , что требуется тому или ино
му методу действия. В разделе "Получение данных контекста" далее в главе объясня
ется, как МVС описывает запросы и ответы .
506 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
Создание контроллеров
Вы сталкивались с применением контроллеров почти во всех предшествующих
главах. Наступило время заглянуть "за кулисы" и посмотреть, как они определяются.
В последующих разделах будут описаны различные способы создания контроллеров
и приведены объяснения отличий между ними.
~ С [ ф localhost:61521/Poco/lndex
выбора РОСО, применив атрибут Cont roller, который также определен в пространстве
имен Microso ft. AspNetCore . Mvc.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace Cont r o ll ersAndAc t i o ns.C o nt r oll e r s
puЫic c l ass PocoCo ntro ll e r {
508 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Саге MVC
f- С <D localhost:61521/Pocoilпdex
Запустив приложение и запросив URL вида / Der i ved/ Index, вы получите резуль
тат, показанный на рис. 17.3.
На заметку! Инфраструктура MVC создает новый экземпляр класса контроллера для каждого
запроса, который ей предлагается обработать. Это значит, что вам не придется синхро
низировать доступ к методам действий или свойствам и полям экземпляра. Разделяемые
объекты, в том числе базы данных и службы-одиночки, которые описаны в главе 18, могут
применяться параллельно и должны быть соответствующим образом реализованы.
систем ой маршрутизации, что все вместе называют данными контекста. Есть три
основных способа досrупа к данным контекста :
Здесь мы рассмот рим подходы к получению входных данных для м етодов де йс
твий, сосредоточившись на объектах контекста и параметрах методов действий .
Привязка моделей р а скрывается в глав е 26.
Имя Описание
Многие контроллеры пишутся без применения свойств из табл. 17.3, потому что
данные контекста доступны через средства, рассматриваемые в последующих главах,
Раб от а с объ е ктам и контек ста означает перемеще ни е по диапа з ону различных
типов и простран ств имен. Свойство Contr o lle r. Request , которо е применяет
ся дл я получ ения данных контекста о НТГР-запросе в листинг е , во звраща ет объект
HttpRequest . В табл . 17.4 описаны сво йства HttpRequest, наибол ее полезные при
нап и сан ии классов к онтроллеров.
Body Это свойство возвращает поток, который может использоваться для чте
ния тела запроса
Свой ство Request . Headers прим е ня ется для получ е н ия слов аря з аголовков, ко
торый обр а батывается с п о м ощью LINQ:
Словарь , в озвращаемы й свойством Request . Headers, хр анит зна ч е ния всех з аго
ловков с исполь з ованием структуры StringValues, которая приме ня ется в ASP.NET
для представления посл едовательности строковых з нач ени й . Клиент НТТР может от
пр авить для загол овков НТГР нес к ол ько знач ений, но н ам нужно отобр аз ить тол ько
п е рво е з нач е ние. С помощью LINQ-м eтoдa ToDictionary () дл я каждого заголовка
получается объ е кт KeyValuePair<string , Str i ngValues > и выбирает ся первое
зн ач е ни е . Результато м явля ется слов ар ь , содержащий з начения string , к оторые мо
гут о тображать ся пр едставлени ем DictionaryResult.
512 Часть 11 . Подробные сведения об инфраструктуре ASP. NET Core MVC
~- --~ [~~~~~~:~~~:_~~~~.~~-i~~=~:.~~~=~=;:;-:,-_~-=~~-
Name Value
Connection Keep-Alive
Accept
Accept-
=~::,::::::"""'""'mlopPl"'loo'"" '"''""""'~ь..·r '"'' 1
Encoding
Ассер\- en·US.en;q:O.B
Language
Host locall10st:61521
};
Глава 17. Контроллеры и действия 513
puЫic ViewResult Headers () =>
new ViewResult() {
ViewName = "DictionaryResult",
ViewData = new ViewDataDictionary(
new EmptyModelMetadataProvider(),
new ModelStateDictionary()) {
Model = ControllerContext.HttpContext.Request.Headers
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.First())
};
Дllя получения данных контекста определяется свойство по имени Con trollerCon text
типаControllerContex t, которое декорировано атрибутом, также имеющим имя
Control l erContext .
Пол езно объяснить три разных случая применения термина ControllerContext .
Первое использование - класс ControllerContext из пространства имен
Microsoft . AspNetCore . Mvc, представляющий собой класс, который собирает вмес
те все объекты контекста, требующиеся методу действия контроллера, с применением
свойств, описанных в табл. 17.5.
Имя Описание
Имя Описание
Свойство HttpContext - это всего лишь более удобный способ получить значе
ние свойства ControllerContext . HttpContext. Никакой "магии" в базовом классе
Controller нет: он дает в р езультате более простые и ясны е контроллеры по той при
чине, что объединяет общие задачи в удобные методы и свойства, которые при необ
ходимости можно воссоздать самостоятельно в контроллере РОСО . Если углубиться в
детали, то выяснится, что масса функциональности в инфраструктуре ASP.NEТ Core
MVC удивительно проста; здесь отсутствует какая-либо сп ециальная и зюминка -
только продуманная функциональность , предлагаемая тщательно спроектированным
набором пакетов NuGet. При наличии времени рекомендуется удостовериться в это м,
загрузив исходный код МVС из http : //github . com/aspnet и изучив его.
1
City:
------------"·--11
~------"·--г-··--------·-------~
Гlond;~ -~----
___________· ____________!
Рис. 17 .5. Получение данных формы из объектов контекста
516 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
действия ci ty может быть наполнен значением из Request . Form [ "Ci ty " ] , напри
мер. Такой подход также дает методы действий, легче подцающиеся модульному тес
тированию, пос:коль:ку значения , которыми оперирует метод действия , получаются
:как обычные параметры С# и не требуют имитации объектов контекста.
Генерирование ответа
После того как метод действия завершил обработку запроса , ему необходимо сге
нерировать ответ. Для генерирования вывода из методов действий доступно много
средств, которые будут описаны в последующих разделах.
StatusCode Это свойство используется для установ к и кода состояния НТТР, связанного
с ответом
Headers Это свойство возвращает словарь заголов ков НТТР, которые будут включе
ны в ответ
Вместо того чтобы иметь дело напрямую с объектом HttpResponse . методы дейс
твий возвращают объект, который реализует интерфейс IActionResul t из про
странства имен Microsoft . AspNetCore . Mvc. Объе кт реализации IActionResu l t,
518 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Саге MVC
известный как результат действия, описывает, каким должен быть ответ из конт
роллера, например, визуализация представления или перенаправление клиента на
} ;
Многие части ASP.NET Core MVC спроектированы для упрощения модульного тестирования,
и это особенно справедливо в отношении действий и контроллеров. Наличие такой подде
р ж ки объясняется несколькими причинами.
Далее в главе будет показано, как создавать модульные тесты для разных видов результа
тов действий. В главе 7 приводились инструкции для настройки проекта модульного тести
рования; можно также загрузить готовые проекты из веб - сайта издательства.
520 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
Генерирование НТМL-ответа
В предыдущем разделе была возможность изъять из класса контроллера код. ге
нерирующий отв ет, с прим е н ением результата действия . Инфраструктура ASP.NET
Core MVC укомплектована более гибким подходом 1t выпуску ответов - классом
ViewResul t .
Класс Vi ewResu l t - это результат действия, предоставляющий доступ к механиз
му визуализации Razor, который обрабатывает файлы . cshtrnl для внедрения данных
модели и отправляет результат клиенту через механизм контекста HttpResponse.
Работа механизмов визуализации объясняется в главе 21, а здесь мы сосредоточим
внимание на использовании класса ViewResult как результата действия.
В листинге 17 .18 специальный класс результата действия заменен классом
ViewResul t, экземпляр 1шторого создается посредством метода View (), предостав
ляемого базовым классом Controller.
Метод Описание
View (rnodel) Этот метод создает объект ViewResul t для стандартного пред
ставления, ассоциированного с методом действия, и применяет
указанный объект как данные модели
View (vi ew , rnodel) Этот метод создает объект Vi ewResul t для указанного представле
ния и использует указанный объект как данные модели
Глава 17. Контроллеры и действия 521
Запустив приложение и отправив форму, вы увидите знакомый результат
(рис. 17.6).
1
1
L ____ _
Рис. 17 .6.
----- "'' ·--~·-------·-
/Аrеаs/<ИмяОбласти>/Viеws/<ИмяКонтроллера>/<ИмяПредставления> . сshtтl
/Аrеаs/<ИмяОбласти>/Viеw s/Sh аrе d/<ИмяПр едставления> . сsh tтl
/V iews/Shared/<ИмяПpeдcтaвлeния>.cshtml
Первой тестируемой ситуацией является момент, когда метод действия выбирает специфи
ческое представление:
С помощью значения null объект ViewResul t сигнализирует MVC о том, что было вы
брано стандартное представление, ассоциированное с методом действия .
Глава 17. Контроллеры и действия 523
При таком указании представления путь должен начинаться с / или ~ / и может включать
расширение имени файла (которым будет . c shtml, если оно не указано).
Когда вы обнаруживаете , что пользуетесь этим средством, возьмите паузу и задайте себе
вопрос : чего вы стараетесь достичь? Если вы пытаетесь визуализировать представление , при
надлежащее другому контроллеру, тогда может быть лучше перенаправить пользователя на
метод действия в другом контроллере (пример ищите в разделе "Перенаправление на метод
действия" далее в главе). Если вы пытаетесь обойти схему именования файлов представле
ний, поскольку она не соответствует способу организации вашего проекта, тогда обратитесь
в главу 21, в которой объясняется реализация специальной последовательности поиска.
слово Model механизма Razor. Далее создается папка Views/Example, куда помеща
ется файл представления по имени Index. cshtml, содержимое которого приведено
в листинге 17.20.
@{ Layout = null ;
< ! DOCTYPE html>
<html>
<head>
<meta name="viewport" content= " width=device - wi dth " />
<title>Controllers and Actions</title>
<link rel= "stylesheet" asp-href-include="liЬ/bootstrap/dist/css/*.min.css" />
</head>
<body class= "panel-body " >
Model : @(( (DateTime)Mode l) .DayOfWeek)
</body>
</html >
·lJ<t1eao ...
j <meta narre• " vie\'lpoгt " content •",,..idth•devi ce- \'lidth" />
1 <title >Controllers • nd Act i ons </title>
.
I
; <link reJ •"stylosheet" osp·href-include•"liЬ/Ьcotstrap/dist/cso/ • . css" />
</l1ead>
-.- .; <body class• "p-ll · l">
~>od ol : ёf'lodel .j
; </Ьоdу > ф AddYeщ
.. </html>
Ф ComparoTo
,1- Dщ
/' Day
-1' •. D•yOIWocl: Dot•Тinle. D •yOfWeek ( get;}
/' DayOfYear Gets the d•y of the >vtek 1'prt<ented Ьу t hi~ instanco.
(i' Equals
Ф GetDateTimeFormats
~ GetHa<hCode
[Fact]
puЫic void ModelOb jectType() {
11 Организация
Examp l eController controller = new ExampleController();
11 Действие
ViewResu lt result = controller .I ndex() ;
11 Утверждение
Assert .IsType<System.DateTime >(res ult.ViewData . Model) ;
Метод Assert . IsType () применяется для проверки того, что объект модели представ
ления является экземпляром DateTime .
В новом методе действия Resul t () необходимо применить метод View (), кото
рый ви зуал изирует стандартно е представление для действия , и указ ать д анны е мо
дели, чт о соответствует треть ей версии этого метода из табл. 17.8. Но посл е з апус
ка приложения и запроса URL вида /Example /Resu l t будет получ ено сообще ни е об
ошибке вроде показанного ниже :
InvalidOpe rationExceptio n: The view 'H e l lo , Wor l d ' was not found.
The f ol lowi ng loca t ions we re searched :
/V i ews/Example/Hel l o , Wo rl d . cs h tml
/Vi ews/Shared/He l lo , Worl d. cshtml
InvalidOpera t ionException : Предст а вление ' Hello , World ' не найдено .
П оиск производился в следующих мес т оположениях :
Явное приведение данных модели к object гарантирует соотв етствие вы з ова пра
вильной версии метода Vi ew () и обеспечит визуализацию представления и з файла
Re su l t . cshtml.
Глава 17. Контроллеры и действия 527
Передача данных с помощью ViewBag
Объ ект ViewBag был описан в главе 2. Это средство позволяет определять свойс
тва в динамическом объекте и получать доступ к ним в представлении. Динамический
объект доступен через свойство ViewBag, предоставляемое классом Controller, как
демонстрируется в листинге 17.24.
puЫic ViewResult Result() => View( (object) " Hello World " ) ;
Внимание! Среда Visual Studio не способна предоставить поддержку lntelliSense для любых
динамических объектов, включая ViewBag , и ошибки не будут обнаружены до тех пор ,
пока не произойдет визуализация представления .
[Fact]
puЫic void Mode l ObjectType()
11 Организация
ExampleController controller = new ExampleController();
11 Действие
ViewResult result = controller . Index{) ;
11 Утверждение
Assert.IsТype<string>(result.ViewData["Message"]);
Assert.Equal("Hello", result.ViewData["Message"]);
Assert.IsType<System.DateTime>(result.ViewData["Date"]);
Этот тестовый метод проверяет типы свойств Message и Date , используя метод
Assert. IsType () , и проверяет значение свойства Message с применением метода
Assert . Equal ().
Выполнение перенаправления
Обычный результат из метода действия предназначен не для выпуска какого-ли
бо вывода напрямую, а для перенаправления клиента на другой URL. Большую часть
времени таким URL является другой метод действия внутри приложения, который
генерирует вывод, подлежащий отображению пользователям. Когда выполня ется пе
ренаправление , браузеру отправляется один из следующих двух кодов НТГР.
• Код НТГР 302, который означает временное перенаправл ение. Это наиболее час
то используемый тип перенаправления и в случае применения паттерна Post/
Redirect/Get необходимо посылать данный код .
• Код НТГР 301, который означает постоянное перенаправление. Этот код должен
использоваться с осторожностью, т.к. он инструктирует получат еля не запраши
вать снова исходный URL, а применять новый URL, вмюченный вместе с кодом
перенаправления. В случае сомнений используйте временное перенаправление,
т.е. отправляйте код 302.
Для выполнения перенаправления могут применяться и другие р езультаты дейс
твий, которые описаны в табл. 17.9.
Глава 17. Контроллеры и действия 529
Табл и ца 17.9. Р езультаты действ ий для перенаправления
действие и контроллер
Зде сь URL пе р енаправления выражается к ак аргумент string м етода Redi rect (),
к оторый об е сп е чив а ет вр е м е нно е п е р е направл е ни е . Постоянное п е рен апр авлени е
м ож но выполнить с помощью м етода RedirectPermanent (), как по казано в лис
тинге 17.27.
530 Часть 11. Подробные сведения об инфраструктуре ASP. NET Core MVC
пользователя на ненадежный сайт. Такой вид результата действия может быть создан пос
редством метода LocalRedirect (), унаследованного от класса Controller.
puЫic ViewResult Result () => View ( (object) " Hello World " );
puЬlic RedirectResult Redirect{) => RedirectPermanent("/Example/Index");
[Fact]
puЫic void Redirect i on () {
11 Организация
ExampleController controller = new Examp l eController();
11 Действие
RedirectResult result = controller . Redirect() ;
11 Утверждение
Assert .Equal( " /Example/Index ", result.Url) ;
Assert . True(result . Permanent);
Обратите внимание , что тест обновлен для получения объекта RedirectResul t при вы
зове метода действия .
Глава 17. Контроллеры и действия 531
Перенаправление на URL системы маршрутизации
При перенаправлении пользователя на другую часть прилож е ния необходимо
удостовериться в том, что отправляемый URL является допустимым в рамках схемы
URL. Проблема, связанная с использованием для перенаправления буквальных URL, в
том, что любое изменение в схеме маршрутизации означает необходимость пересмот
ра кода и обновления таких URL. К счастью, можно задействовать систему маршрути
зации для генерирования допустимых URL с помощью метода RedirectToRoute (),
который создает экземпляр класса RedirectToRouteResult (листинг17.28).
[E'act]
puЫic void Redirection() {
11 Организация
ExampleController controller new ExampleController() ;
532 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Core MVC
11 Действие
RedirectToRouteResult result = controller.Redirect();
11 Утверждение
Assert.False(result.Permanent);
Assert.Equal("Example", result.RouteValues["controller"]);
Assert.Equal("Index", result.RouteValues["action"]);
Assert.Equal("MyID", result.RouteValues["ID"]);
[Fact]
puЫic void Re direc tion() {
//Ор г анизация
Examp l eControl ler control l er = ne w Exampl e Controller() ;
11 Действ и е
Red i rectToActio nResult resul t = c ontrolle r .Redi r ect() ;
11 У т ве р ждени е
Asse r t .False (r e sul t .Perma ne nt ) ;
Asser t. Equ al ("Index ", result.ActionName);
намеревался предпринимать.
534 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
[Ht t p Po s t ]
p uЫ ic Redir ect ToAct i onRe s u lt ReceiveForm(st r i ng name , s tr ing c ity) {
TempData [ "name"] = name;
TempData["city"] = city;
r eturn Red i rectToAct i on( n a me o f (Data )) ;
Метод класса
Имя Описание
Controller
J sonRes ult J son Этот результат действия сериализирует объект в
формат JSON и возвращает его клиенту
NotFoundOb jectResult Not Found Этот результат действия будет применять со
гласование содержимого для отправки объекта
клиенту с кодом состояния НТТР 404, если со
гласование содержимого успешно
Тема формата JSON и его роли в веб - приложенилх раскрывается в главе 20, а в
листинге 17.32 демонстрируется применени е метода J son ( ) для создания объекта
J s onRes ul t .
[Fact]
puЫic void JsonActionMethod()
11 Организация
ExampleController controller new ExampleController();
11 Действие
JsonResult result = controller.GetJson();
11 Утверждение
Assert.Equal(new[J { "Alice", "ВоЬ", "Joe" }, result.Value);
Такой тип результата действия полезен, когда имеется содержимое, которое удобно
представлять в формате string, и известно, что клиент способен принимать указы
ваемый тип MIME. Опасность этого подхода в том, что клиенту может быть отправлен
ответ в формате. который он не в состоянии обработать. Более надежный подход по
лагается на согласование содержимого, которое выполняется классом Obj ectResul t
(листинг 17.34).
Accept : text/html,application/xhtml+xml,application/xml ;
q=0.9,image/webp,*/*;q=0 . 8
Поддерживаемые форматы выражаются как типы MIME. Инфраструктура MVC
имеет набор форматов, которые она может применять для значений данных, и срав
нивает их с форматами, поддерживаемыми браузером. Пр едпочтительным форма
том, используем ым инфраструктурой MVC, является JSON, и он будет применяться
в большинстве случаев кроме ситуации, когда действие возвращает значение string
и используется простой текст. Процесс согласования содержимого и особенности его
реализации более подробно рассматриваются в главе 20.
Внимание! Будьте осторожны при использовании этих результатов действий, чтобы не создать
приложение, которое позволит запрашивать содержимое произвольных файлов . В частнос
ти, не получайте путь к подлежащему отправке файлу из любой части запроса либо из лю
бого хранилища данных , которое пользователь может модифицировать через запрос.
Метод класса
Имя Описание
Controller
FileContentResult File Этот результат действия посылает клиенту
байтовый массив с указанным типом MIME
FileStreamResult File Этот результат действия читает поток
и отправляет содержимое клиенту
Метод класса
Имя Описание
Controller
StatusCodeResult StatusCode Этот результат действия отправ
ляет клиенту у казанный код со
стояния НТТР
НТТР 200
CreatedResult Created Этот результат действия от
правляет клиенту код состояния
НТТР 201
Created.AtActionResult Created.At Action Этот результат действия отправ
ляет клиенту код состояния НТТР
201 вместе с URL в заголовке
Location, которы й нацелен на
действие и контроллер
НТТР 404
UnsupportedMediaTypeResult Этот результат действия от
правляет клиенту код состояния
НТТР 415
Глава 17. Контроллеры и действия 541
Класс StatusCodeResul t следует шаблону, который вы уже видели для други х ти
пов результатов, и делает свое состояние доступным через набор свойств. В этом
StatusCode возвращает числовой код
случае сво йство состояния НТТР, а свойство
StatusDescription - связанную описательную строку. Приведенный ниже тестовый
метод предназначен для проверки метода действия из листинга 17.28:
542 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Core MVC
[ Fact ]
p uЬlic vo i d NotFoundActionMe thod ()
11 Ор г ан и за ци я
Exampl eCo ntrol l e r cont r ol ler = new Examp le Con t rol l e r () ;
11 Д е й стви е
St atusCodeRes ult result = con t roll e r.Index () ;
11 Утвержден и е
Asser t. Equal(404 , res ul t . StatusCode);
Метод класса
Имя Описание
Controller
Pa r t i a l Vi ewResult Pa rt i alView Этот результат действия применяется для
выбора частичного представления , как
объясняется в главе 21
ViewComponent Resul t Vi ewComponent Этот результат действия используется для
выбора компонента представления , как
описано в главе 22
EmptyResult Этот результат действия ничего не делает
и производит пустой ответ для клиента
Резюме
Контролл еры являются одним из основных строительных блоков в паттерне проек
тирования MVC и находятся в це нтральной части разработки приложений MVC . В на
стоящей глав е было показ ано. ка к создавать контроллеры РОСО с испол ьзованием
обычных классов С# и получать преимущество от удобства, предлагаемого базовым
классом Cont r ol l er. Вы узнали, какую роль результаты действий играют в контрол
лерах МVС, и выяснили , каким образом они облегчают модульное тестирование. Были
продемонстрированы различные способы получения ввода и генерации вывода и з ме
тода действия, а также встроенные результаты действий, которые делают этот про
цесс простым и гибким. В следующей главе будет описано одно из средств, кото ро е
вызывает наибольшую путаницу у разработчиков, использующих ASP.NET, но очень
важно для эффективной разработки приложений МVС - внедрение зави сим остей.
ГЛАВА 18
Внедрение зависимостей
в настоящей
injection -
главе рассматривается внедрение зависимостей
DI) - методика, которая помогает создавать гибкие приложения и уп
(dependency
Вопрос Ответ
Что это такое? Внедрение зависимостей облегчает создание слабо связанных компо
нентов , что обычно относится к компонентам, которые потребляют функ
циональность, определяемую интерфейсами, и не обладают непосредс
твенным знанием о том, какие классы реализации используются
Чем оно полезно? Внедрение зависимостей упрощает изменение поведения приложения пу
тем изменения компонентов, которые реализуют интерфейсы, определя
ющие фун к циональные средства приложения . Оно та кже дает в результате
компоненты, которые легче изолировать для модульного тести рования
Указание способа для созда Создайте отображение службы с приме 18.29 , 18.30,
ния объектов реализации нением метода жизненного цикла, кото 18 .32, 18.33
рый подходит управляемой службе
"dependencies ": {
"Microsoft.NETCore . App ":
"version ": "1. 0 . 0 ",
" type ": "platform "
} ,
"Microsoft . AspNetCore . Diagnostics" : "1.0.0",
"Microsoft . AspNetCore . Se rver . IIS in tegration ": "1. 0 . 0 ",
"Microsoft . AspNetCore. Server . Kestrel" : "1. О . О ",
"Microsoft . Extensions .Logging . Console ": " 1 . 0 . О ",
Глава 18. Внедрение зависимостей 545
"Microsoft. AspNetCore. Mvc" : "1. О. О" ,
" Мicr osoft. AspNetCore. StaticFiles" : "1. О. О",
"Microsoft.AspNetCore.Razor .Tools":
"version": 11 1. О. 0-preview2-final",
"type": "Ьuild"
}'
" tools " :
"Microsoft .AspNetCore . Server .II Sintegration . Tools ": "1 . 0 . 0- previ ew2 - f i nal ",
"Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final"
) '
" frameworks ": {
"netcoreappl . 0 ":
" irnports ": [ "dotnet5 . 6", " portaЫe-net4 5+win 8 " ]
}'
"buildOptions ": { " ernitEntryPoint ": true ,
"preserveCompilationContext ": true },
" runtimeOptions ": {
"conf i gProperties ": { " System . GC .S erver ": true}
@model IEnumeraЫe<Product>
@{ Layout = null ;
< ! DOCTYPE html>
<html>
<head>
<meta name= " viewport " content= " width =de v ic e-width " />
<title>Dependency Injection</title>
<link rel= "styles heet " asp-href - include= "li Ь/bootstrap/dist/css/* .min . css " />
</head>
<body class= "panel-bod y" >
@if (ViewData .C ount > 0)
<tаЫе class= " taЫe taЬle - bordered taЫ e - condensed taЫe - striped">
@foreach (var kvp in ViewData) {
<tr><td>@kvp . Key</td><td>@kvp . Va lu e</td></tr>
}
</tаЫе>
<tаЫе class= " taЫe taЫe-bordered taЬle - co n densed ta Ьl e -strip ed " >
<thead>
<tr><th>Name </th ><t h > Price</t h ></t r >
</thead>
<tbody>
@if (Model == n ull) {
<tr><td colspan= " З " class= "text - center " >No Model Data</td></tr>
} else {
548 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
</ t body>
</tаЫе>
</ b ody >
</html >
С (_2._locall1~:_s_9_4_39_ __ --~
Name Price
No Model Data
L ___
_"___···--·------
Рис . 18.1. Запуск примера приложе ния
Я использую DI в своих проектах главным образом потому, что проекты часто двигаются в
неожиданных направлениях, а наличие возможности легкой замены компонента новой реа
лизацией мож ет уберечь от внесения большого числа утомительных и чреваты х ошибками
изменений. Я предпочитаю приложить определенные усилия в начале проекта , чем иметь
дело со сложным набором правок на более позднем этапе. Я совершенно не категоричен в
отношении внедрения зависимостей, т.к. оно решает проблему, которая возникает не в каж
дом проекте. Только вы можете решить, нужно ли DI в вашем проекте, и только вы способны
оценить получаемые выгоды и сопутствующие затраты.
Хорошая новость об этом коде состоит в том, что он работает. Запустив приложе
ние, вы увидите детали объектов модели, отображаемые в браузере (рис. 18.2).
Глава 18. Внедрение зависимостей 551
CJ Dep1mdency lnjection Х
- С [ <D l~all1ost:59439
Name Price
Kayak $275.00
Lifejacket $48.95
-----·------------··---------
Рис. 18.2. Отображение данных модели
Плохая новость в т ом, что контроллер Н оте и класс Meтor yR e p osi t or y теперь
сильно связаны, т.е. заменить хранилище , не изменяя класс HoтeCo n t r o ll e r, не удас
тся. Как объяснялось в главе 7, выполнение эффективного м одульного тестирования
означает возможность изоляции одиночного компонента , но протестировать метод
1Ко"'ролмр Н Хр'"илище
Р ис. 18.З. Эффект от сильно связанных компонентов
Контроллер Хранилище
"
Развертывание
Теперь в примере приложения имеется более сложный набор отношений, как де
монстрируется на рис. 18.5. Важно отметить, что между классом контроллера и клас
сом хранилища никакого прямого отношения нет - все реш а ется при посредстве
интерфейса и брокера . Это значит, что класс хранилища можно изменять без необхо
димости во внесении изменений в класс контроллера.
Хранилище
~
Контроллер "'-
Интерфейс .....
~ Брокер ~
Чтобы посмотреть, как применя ется брокер типов , добавьте в папку Models файл
класса по имени Al ternateRepos i tory . c s с еще одной реали з ацией интерф е йса
IRepos i tory, приведенной в листинге 18.16.
Результат этого изм енения можно увидеть, запустив приложение, которо е отобра
зит данны е , пр едоставля емые новым I<Лассом хранилища (рис . 18.6).
[J Dependency lnjection
Stadium $79,500.00
L___________"__________" .________________ . __
using Xunit ;
n amespace Tests
puЫic class DITests
[Fact]
puЬlic void Contro l lerTest()
11 Организация
var data = new[] { new Pr oduct { Name = " Test ", Pr ice 100 } } ;
var mock = n ew Moc k<IRe p osi t or y > {);
mock . SetupGe t (m => m. Products) . Returns(data) ;
TypeBroker.SetTestObject(mock.Object);
HomeController controller = new HomeController();
11 Действие
ViewResu l t resul t = controller . Index() ;
11 Ут верж д е н ие
As s ert . Equal(data , resul t. Vi e wDat a . Model) ;
В целом результат будет таким же, как в случае использования специального клас
са брокера типов, но важное преимущество заключается в том, что процесс внедре
ния зависимостей интегрирован в МVС, т.е. компонент поставщика служб будет при-
558 Часть 11. Подробные сведения об инфраструктуре ASP.NET Соге MVC
меняться всякий раз, когда создается экземпляр класса контроллера. Таким образом ,
классам контроллеров разрешено объявлять зависимости, не имея никакого понятия
о том, как они будут распознаваться. Вы просто пишете классы контроллеров, кото
рые объявляют свои зависимости как параметры конструкторов, и позволяете инфра
структуре вместе с компонентом поставщика служб позаботиться об остальном.
'-----
'1
~ lnter·мl Serve1 Error Х
С lф-=-~~~~-------=-----~~-===-~=о~~}
Ап unhandled exception occurred while pгocessing the request.
__:
-11
J
lпva li clOpeгationExceptio11:
Unable to гeso lve seгvice for type
j ' Depeпdeпcylnjectio11. Models.IReposito1·y' \Vhile atterпp<iпg to activate /
1 'Depeпdeпcylп1ectюп.Coпёrollers . HorпeCoпtro!ler'. i
j Microsoft.Extensions.lnternal.Actf.1atcrU tilrties.GetService((St>rvkeProvider sp. Туре type-, Турё' requiredBy. Boolean 1
1 isDefaul tParameterRequired) j
!№l]~?1pe1~qti~x~tif~~~T~'~~~#,,,-_..~
Рис. 18. 7. Запуск примера приложения
D D•pendency lnjection Х
.---
с [_SO~lhost:59439 ,-,........__---~--,----~--=-=~
Name Price
Kayak $275.00
1 Lifejacket $48.95
using Dependencyinjection.Controllers;
using Dependencyinjection.Models;
using Microsoft.AspNetCore.Mvc;
using Моq;
using Xunit;
namespace Tests
puЫic class DITests
[Fact]
puЫic void ControllerTest()
11 Организация
var data = new[] { new Product { Name = "Test", Price 100} };
var mock = new Мock<IRepository> ();
mock . SetupGet(m => m.Products) . Returns(data);
HomeController controller = new HomeController(mock.Object);
11 Действие
ViewResult result = controller.Index();
11 Утверждение
Assert.Equal(data , result.ViewData . Model);
D Dep~ndency lnjection Х
i
J Name Price
i ~ ......_..,..."6,-
il!!!1fjiilt_....,.41>. $275.00
. ~~
Рис. 18.9. Использование внедрения зависимостей для классов
Имя Описание
AddSi ng l eton<se r vice , impl Type >() Эти методы указывают поставщику служб на
AddSing l eton<service>() необходимость создания ново~о экземпляра
AddSi ngl eton<s ervi ce> (facto ryFunc ) типа реализации для первого запроса к служ
AddSing l eton<se r vi ce>( ins tance ) Этот метод предоставляет поставщику служб
объект, который должен применяться для об
служивания всех запросов к службе. Поставщик
служб не будет создавать новые объекты
1 Dependency lnJeclion Х
Name
Kayak 1 +- ""1:" С r 'tl
При каждой пер езагрузке веб-страницы в браузере новый НТГР- запрос приводит
к тому, что инфраструктура MVC создает новый объект HomeContro ll er , вызывая
создание двух новых объектов Memo r yRepo si tory , каждый с собственным иденти
фикатором GUID.
Совет. Идентификаторы GUID уникальны (или настолько близ ки к уникальным, что фактичес
кая разница отсутствует), поэтому при запуске приложения на своей машине вы увидите
другие значения.
using Microsoft.AspNetCore.Builder;
using Microsoft .Extensi ons . Dependencyinjection;
using Dependencyinjection .I nfrastructure ;
using Dependencyinjection.Models;
using Microsoft.AspNetCore.Hosting;
namespace Dependencyinjection {
puЫic class Startup {
private IHostingEnvironment env;
puЬlic Startup(IHostingEnvironment hostEnv)
env = hostEnv;
В главе 14 было описано, как инфраструктура ASP. NEТ снабжает класс Startup
службами, содействуя в настройке приложения; в число этих служб входит реализа
ция интерфейса IHostingEnvironment, предназначенная для определения среды
размещения. Службы можно получать как аргументы метода Configure (), но не
метода ConfigureServices () , поэтому в класс
Startup был добавлен конструктор.
который предоставляет доступ к объекту реализации IHostingEnvironment и при
сваивает его полю по имени env.
Имя Описание
ет отображение
services.AddTransient<MernoryRepository>();
Совет. Как было описано в табл. 18.4, доступны также версии метода AddScoped () , кото
рые принимают фабричную функцию и могут использоваться для регистрации конкретно
го типа. Такие методы работают аналогично методу AddTransient () , который демонс
трировался в предыдущем разделе , с вполне очевидной разницей в том, что ж изненный
ци кл создаваемы х ими объектов отличается.
6dba7caa-e118-4798-b26c-aabcdeBd90C6
56912сЬО-2ес а-4ЬЗ5-Ьбаd-5с1383426163
Bdba7caa-e 118-4798-b26c-aabcde8d90c6
Price
Имя Описание
(глава 23)
ViewComponentContext Этот атрибут устанавливает свойство
ViewComponentContext для компонентов представлений,
которые обсуждаются в главе 22
Совет. Если вам нужен доступ к службам в методе Configure () класса Startup, тогда
можете использовать свойство Appl ica tionServ ices, предлагаемое интерфейсом
IApplicationBuilder .
Резюме
В э той главе объяснялась роль, которую внедрение зависимостей играет в при
ложении MVC. Оно помогает создавать слабо связанные компоненты, 1<оторые лег
ко заменять и просто изолировать в целях тестирования. Было продемонстрировано
средство внедрения зависимостей ASP.NET, а также ат рибуты, которые инфраструк
тура MVC предоставляет для внедрения зависимостей в свойства и методы действий.
Кроме того, были описаны варианты жизненных циЮiов , доступные при конфигури
ровании поставщика служб , и показано, как они влияют на способ, котор ым созда
ются объекты. В следующей главе рассматриваются фильтры, добавляющие дополни
тельную логику в процесс обработки запросов.
ГЛАВА 19
Фильтры
Вопрос Ответ
Изменились ли они по Фильтры ведут себя по большому счету так же, как в предшес
сравнению с версией твующих версиях MVC. Существует несколько незначительных
мvс 5? изменений.
Функциональность , ранее предоставляемая фильтрами аутен
тификации , была помещена в фильтры авторизации.
Атрибут Authorize больше не является фильтром (детали
применения этого атрибута приведены в главе 29) .
Все типы фильтров могут определяться с использованием от
дельны х синхронных или асинхронных интерфейсов.
Глава 19. Фильтры 577
В табл. 19.2 приведена сводка для этой главы .
методами действий
"dependencies ": {
"Microsoft.NETCore.App" :
" version ": " 1 . 0.О ",
"type": "platforrn"
}'
578 Часть 11. Подробные сведения об инфрастру ктуре ASP.NET Core MVC
) '
" tools ":
"Microsoft . AspNetCore.Razor.Tools": "1.0.0-preview2-final",
"Microsoft.AspNetCore . Server .I ISintegration.Tools ":
" 1 . 0 . 0-preview2 - final "
}'
" frameworks 11
: {
netcoreappl . O":
11
) 1
"buildOptions ":
emitEntryPoint
11 11
: true , "preserveCompilationContext 11
: true
) '
" runtimeOptions" : {
configProperties ": {
11 11
System . GC . Server ": true)
Включение SSL
Для ряда примеров в настоящей главе требуется применение протокола SSL, ко
торый по умолчанию отключен. Чтобы включить SSL, выберите пункт Filter Properties
(Свойства фильтров) в меню Project (Проект) среды Visual Studio и отметьте флажок
ЕnаЫе SSL (Включить SSL) на вкладке Debug (Отладка), как показано на рис. 19. l.
Примите к сведению назначенный номер порта, который в каждом проекте будет
отличаться.
@{ Layout = null;
< ! DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device - width" />
<tit l e>Filters</title>
<link asp-href-include="liЬ/bootstrap/dist/css/* .min.css" rel="stylesheet" />
</head>
580 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Core MVC
</tbody>
</tаЫе>
</body>
</html>
-------------·-------------~--·-.J
Рис. 19.2. Запуск примера приложения
Глава 19. Фильтры 581
Использование фильтров
Фильтры позволяют изъять логику, которая иначе применялась бы в методе дейс
твия, из контроллера и определить ее в многократно используемом классе. В качестве
Именно так решалась бы задача с НТГРS без фильтров. После запуска приложения
браузер будет запрашивать стандартный URLдля проекта, отличный от НТГРS, который
метод действия Index () обработает путем возвращения объекта
StatusCodeResul t ,
отправляющего код состояния НТГР 403 17). Если за
в ответе (как описано в главе
просить стандартный URL для НТГРS , например, https: //localhost : 44318 , тогда
метод действия I ndex () отреагирует визуализацией представления Message (пр ежде
чем браузер отобразит результат, может понадобиться подтверждение в окне предуп
реждения безопасности) . Оба исхода иллюстрируются на рис. 19.3.
[j lcx:alhost:51000 Х
1 С [(D-locall~~-st-:5-100......:::0=
1
1
Status Cod~~-~3; - Fo1·Ьi~~:-1 ..: .. ____ ? г~~!!~~~t~44318 -·--~-=~
L _________ __j This is the lndex action on the Home controlfer
1
i
L--------·------
Рис. 19.З. Ограничение доступа только запросами HTTPS
582 Часть 11 . Подробные сведения об инфраструктуре ASP.NEТ Соге MVC
Нужно помнить о реализации той же самой проверки внутри каждого метода дейс
твия в каждом контроллере, для которого планируется требование протокола HTTPS.
Код реализации политики безопасности занимает значимую часть довольно простого
контроллера и затрудняет его понимание. Кроме того , забыть добавить его в н овый
м етод действия и тем самым создать брешь в политике безопасности - дело тол ько
времени. Проблемы такого рода способны решать фильтры, как демонстрируется в
листинге 19.9.
На заметку! Фильтр Requi re Https работает не совсем так, как специальный код из листин
га 19.7. Для запросов GET атрибут Requi r eHttp s обеспечивает перенаправление кли
ента на первоначально запрошенный URL, но делает это с использованием схемы
ht t ps ,
в результате чего запрос
h t tp : / / l ocalho st/H ome/ Index будет перенаправлен на
https: / / localhost/Home/ Inde x . Такой подход имеет смысл в большинстве развер
нутых приложений, но не на стадии разработки, поскольку протоколы НТТР и НТТРS обслу
живаются на разных локальных портах. В классе Requ i r eHttp sAt t r i bute определен
защищенный метод по имени Handle NonH ttps Re q ue s t (), который можно переопре
делить для изменения поведения. В качестве альтернативы в разделе "Использование
фильтров авторизации" исходная функциональность воссоздается с нуля.
П онятие фильтров
Теперь, когда вы видели, как используются фильтры , на ступило время объяснить .
что происходит " за кулисами". Фильтры реализуют интерфейс IFi l terMetadata , ко
торый находится в пространстве име н Mi c r o s oft . As pNetCore . Mvc . Fi l ters . Вот
его опред ел е ние :
пользователей
В т а бл. 19.3 приведены неч еткие описания, потому что фильтры можно исполь
зовать для широкого спектра задач. ограниченного только вашим воображением и
проблемами, которые необходимо решить. По мере погружения в детали их работы
все станет яснее , а пока следует уяснить два важных момента .
Во-п е рвых, для каждого типа фильтра в табл. 19.3 пр едусмотр ены два разных ин
терфейса . Фильтры могут выполнять свою работу синхронно или асинхронно, так что
синхронный фильтр результатов, например, реализует инт е рф е йс IResu l tFilter,
тогда как ас инхронный - инте рфейс IAsync Re s u ltFil ter.
Во-вторых, фильтры прим е няются в особом порядке. Фильтры авторизации вы
полняются первыми, з а ними идут фил ьтры действий, а з атем фильтры результатов.
Фильтры исключений выполняются только в случае генер ации какого-либо исключе
ния , которо е наруша ет нормальную последовательность .
Глава 19. Фильтры 585
Получение данных контекста
Фильтры обеспечиваются данными контекста в форме объекта Fil terContext.
Класс Fil terContext является производным от
ActionContext, который также
представляет собой базовый класс для юшсса ControllerContext, рассмотренного
в главе 17. Из соображений удобства в табл. 19.4 перечислены свойства, унаследо
ванные от класса ActionContext , наряду с дополнительными свойствами, которые
определены в FilterContext.
using System.Threading.Tasks;
namespace Microsoft.AspNetCore . Mvc.Filt ers
puЫic interface IAsyncAuthorizationFi l ter : IFilterMetadata {
Task OnAuthorizati onAsync(AuthorizationFilterContext context);
586 Часть 11 . Подробные сведения об инфрастру ктуре ASP.NET Core MVC
Метод OnAuthorizationAsync () вызыв ается, чтобы фильтр мог авторизов ать за
прос. КШ{ОЙ бы интерф е йс ни пр име нялся, фильтр получает данны е конте кста , опи
сывающие з апрос , чер ез экз е м пляр класса Aut horization FilterContext , которы й
является производны м от класса Fil te r Context и добавляет одно в аж ное свойство,
приведенное в табл . 19.5.
using System ;
us i ng Microsoft . AspNetCo r e . Http ;
using Micro s of t .AspNetCo r e. Mvc ;
using Microsoft . AspNetCore . Mvc . Filters ;
namespace Filters . Infrastructure {
puЬl i c class HttpsOnl yAtt r ibute : Attribute , IAuthorizationFilter
Если запрос удовлетворяет политике авторизации , то фил ьтр автор изации нич ег о
не делает; такая пассивность позволяет инфраструктур е MVC перейти к следующему
фильтру и в конечном итоге выполнить метод дей ствия.
Большая часть работы при модульном тестировании фильтра связана с настройкой объекта
к онтекста, который передается методам фильтра. Объем требующейся имитации зависит
от информации контекста, используемой фильтром. В качестве примера ниже приведен мо
дульный тест для фильтра HttpsOnl y из листинга 19.11 .
using System . Linq ;
using Filters .I n fr astruct u re ;
using Microsoft . As pNetCore . Http;
using Microsoft . AspNetCo r e . Mvc ;
using Microsoft . AspNetCore . Mvc . Abst ra ction s ;
using Microsoft . Asp NetCo r e .Mvc . Filte r s ;
u s ing Moq ;
using Xunit ;
namespace Tests
puЬlic class FilterTests
[ Fact ]
puЫ i c void TestHttpsFi lt er()
11 Организация
var httpRequest = new Mock<HttpRequest>() ;
httpRequest . SetupSequence (m => m. Is Ht tps ) .Returns(true )
. Returns( f al s e) ;
588 Часть 11. Подробные сведения об инфраструктуре ASP.NET Соге MVC
Этот оператор настраивает свойство HttpRequest. IsHttps таким образом, что оно
возвращает последовательность значений: свойство возвращает true, когда читает
ся в первый раз, и false - во второй раз. Имеющийся объект HttpContext можно
применить для создания объекта ActionContext, который позволит создать объект
AuthorizationContext , необходимый модульным тестам. За счет инспектирования
свойства Resul t объекта Au thor i za tionFil terCon text выполняется проверка, как
фильтр реагирует на запросы, отличные от HTTPS, и затем проверка того , что происходит
с запросами НТТР. Для настройки объекта AuthorizationFilterContext требуется
много типов, которые полагаются на многочисленные пространства имен ASP.NET Соге и
MVC, но после получения объекта контекста написание оставшейся части теста будет от
носительно простым.
Имя Описание
Имя Описание
Cance led Это свойство типа bool устанавливается в true, если дру
гой фильтр действий предпринял обход процесса обработки
запросов за счет присваивания результата действия свойству
Result объекта ActionExecutingContext
Exception Это свойство содержит любой объект Exception, который
был сгенерирован методом действия
Совет. Как ни странно, контроллеры являются также и фильтрами действий. Базовый класс
Controller реализует интерфейсы IActionFil ter и IAsyncActionFil ter , а это
значит, что можно переопределить методы, определяемые упомянутыми интерфейсами,
и создать функциональность фильтра действий. Для контроллеров РОСО инфраструктура
MVC инспектирует классы и проверяет, реализуют ли они любой из интерфейсов фильтра
действий, и если это так, то автоматически использует их в качестве фильтров действий .
Глава 19. Фильтры 591
Листинг 19. 14. Применение фильтра в файле HomeCon troller . cs
using Microsoft . AspNetCore . Mvc;
using Filters.Infrastructure;
namespace Filters.Controllers {
[Profile]
puЫic class HomeController : Controller
puЬlic ViewResul t Index () => View ( "Message",
"This is the Index action on the Home controller");
puЫic ViewResul t SecondAction () => View ( " Message",
" This is the SecondAction action on the Home controller " ) ;
На заметку! При записи фрагментов НТМL-разметки прямо в ответ производится расчет на то
лерантность браузера к неправильно оформленным НТМL-документам: элемент di v, гене
рируемый в фильтре , находится в начале тела ответа перед элементами DOCTYPE и html,
которые указывают на начало НТМL-документа , выпускаемого представлением Razor. Такой
прием работает и может быть удобным для отображения диагностической информации, но
на него не следует полагаться при реализации производственных функций .
namespace Microsoft.AspNetCore.Mvc.Filters
puЬlic interface IResultFilter : IFilterMetadata
void OnResultExecuting(ResultExecutingContext context);
void OnResultExecuted(ResultExecutedContext context);
Глава 19. Фильтры 593
Фильтры ре зультатов следуют то му же самому шаблону , что и фильтры действий .
Метод OnResultExecut ing () вызывается до того, кан результат де йствия, выпущен
ный м етодом действия, будет обработан, и снабжается информацией контекста через
объект Resul tExecutingContext. Класс Res ul t ExecutingCon text является про
изводным от класс а FilterContext и определяет дополнительные свойства , описан
ные в табл . 19.8.
Имя Описание
Except i on Это свойство содержит любой объект Except i on, который был
сгенерирован методом действия
us i ng System . Collections . Ge ne r ic ;
u si ng Microsoft . AspNetCore . Mvc ;
u s ing Microsoft . AspNetCore . Mvc . Filter s;
usi n g Microsoft . AspNetCore . Mvc.ModelBinding ;
u si n g Mic r o s oft . Asp Ne tCore . Mv c . View Featu r es ;
namespace Filters . Infrastructur e {
p u Ьl ic class Vi ewRe s u ltDeta i lsAttribute : ResultFilterAttribute {
p uЫic override void OnRe s ultExec u ting(Resu l t ExecutingContext context)
Dict i o n ary<string , st rin g> dic t = n ew Dict ionar y <string , s tr i ng> {
[ " Resu l t Туре " ] = context . Re s ult . GetType() . Name
};
ViewResult vr ;
if ( (vr = co n text . Resul t a s Vi e wResu lt ) ! = n u ll ) {
dict [ " View Name " ] = v r. ViewName ;
dict[ " Mo d el Ту р е " ] vr . Vi ew Data . Mode l . Get Type ( ) . Name ;
dict [" Model Data " ] = vr . ViewData . Model . ToSt r ing ( ) ;
Name Value
};
Name Value
Имя Описание
Except i onHandl ed Это свойство типа boo l используется для указания, обработа
но ли исключение
Этот фильтр применяет объект Excepti onCon t ex t для получения типа необра
ботанного исключ ения. Если типом является Argumen t OutOfRangeExcep tion , то
Интерфейс IFil terDia gnost i c s определяет простую модель для сбора диагности
ческих сообщений во время выполнения фильтра. Класс Defaul tFil t erDiagnost i c s
является реализацией. которая будет использоваться. В листинге 19.24 показан класс
S t artup , обновленный для конфигурирования поставщика служб с целью примене
ния нового инте рфейса и его реализации.
602 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Core MVC
применение фильтров
Финальный шаг заключается в применении фильтров к классу контроллера.
Стандартные атрибуты С# не располагают всеобъемлющей поддержкой для распоз
навания зависимостей конструктора, из-за чего фильтры, которые были определены
в предшествующих разделах , не являются атрибутами. Взамен применя ется атрибут
TypeFilter, сконфигурированный с необходимым типом фильтра (листинг 19.27).
Совет. Порядок применения фильтров в листинге 19.27 важен, как будет объясняться в разде
ле "Порядок применения фильтров и его изменение " далее в главе .
Атрибут TypeFil ter создает новый экземпляр класса фильтра для каждого запро
са, но делает это с использованием средства внедрения зависимостей, которое поз
воляет создавать слабо связанные компоненты и помещать объекты, участвующие в
распознавании зависимостей, под управление жизненным циклом.
В текущем примере сказанное означает, что оба фильтра , примененные в лис
тинге 19.27, получат тот же самый объект реализации IFilterDiagnostics, а
сообщения, сохраненные классом TimeF il ter, будут записаны в ответ классом
DiagnosticsFi l ter. Запустив приложение и запросив стандартный для него URL.
можно увидеть результат (рис. 19.8).
Глава 19. Фильтры 605
Обратите внимание, что жизненный цикл для класса реализации IFil terDiagnostics
также изменен, чтобы он стал одиночкой. Если бы сохранилось создан ие нового эк
земпляра для каждого запроса, то объект-одиночка TirneFil ter получал бы раз ные
объекты реализации IFilterDiagnostics от фильтра Diagnost i csFilter, который
продолжил бы создаваться через атрибут TypeFil ter и создавался бы для каждого
запроса.
Глава 19. Фильтры 607
Применение фильтра
Рис. 19.9. Применение поставщика служб для управления жизненным циклом фильтра
608 Часть 11 . Подробные сведения об инфраструктуре ASP.N ET Core MVC
Совет. Добавлять глобальные фильтры можно также с использованием метода Add () вмес
то AddService () , который позволяет регистрировать объект фильтра в качестве гло
бального фильтра, не полагаясь на внедрение зависимостей и поставщика служб. Метод
Add () применяется в следующем разделе .
r-:---~~----~~--~~~~~~-~~.
С [ <D localhost:51000/global
Совет. Атрибуты TypeFil ter и Servi ceFil tеrтоже реализуют интерфейс IOrderedFil ter,
т. е. в случае использ о вания внедрения зависимостей порядок применения фильтров так
же можно изменять.
Рез ю м е
В настоящей главе было показано, как инкапсулировать логику сквозной ответс
тв е нности в виде фильтров. Вы узнали о доступных типах фильтров и о том, как их
р е ализовать. Были приведены объяснения, каким образом применять фильтры в
форме атрибутов к контроллерам и методам действий, а также в качестве глобальных
фильтров. В сл едующей гл аве будет поrшзано, как использовать контроллеры для со
здания веб-служб.
ГЛАВА 20
Контроллеры API
Вопрос Ответ
Чем они полезны? Контроллеры API позволяют клиентам иметь доступ к данным в
приложении, не получая также и НТМL-разметку, которая требу
ется для представления содержимого пользователю. Не все кли
енты являются браузерами , и не все клиенты отображают данные
пользователям. Контроллер API делает приложение открытым
для поддержки новых типов клиентов или клиентов, разработан
ных третьей стороной
Затем добавьте в папку Models файл класса по имени I Reposi tory . cs и опреде
лите в нем интерфейс для хранилища объе1tтов м одели (листинг 20.2).
Теп ерь создайте папку Views/Home , добавьте в нее файл представления по имени
Index . cshtml и поместите в него содержимо е из листинга 20.6.
</tbody>
</tаЫе>
Конфигурирование приложения
Доб ав ьте в ра здел depende n cies файла proj ect . j son требуемы е пакеты NuGet
и настройте в разделе tools инструментарий Razor, как демонстрируется в листин
ге 20.9. Разделы, которые не нужны для данной главы, понадобится удалить .
"dependenc i es ": {
"Microsoft.NETCore.App":
"version ": "1 .0 . 0 ",
" type ": " platform "
} ,
"Microsoft . AspNetCore . Diagnos ti cs ": " 1 . О . 0 ",
"Microsoft . AspNetCore . Server.IISintegration ": " 1 . 0 . О ",
"Microsoft . AspNetCore . Server . Kestre l": "1. 0 . 0 ",
"Microsoft . Extensions . Logging . Conso l e ": "1. 0.0 ",
"Microsoft.AspNetCore.Mvc": 11 1.0 . 0 11 ,
"Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Microsoft.AspNetCore.Razor.Tools":
"version": "1.0.0-preview2-final",
"type": "build"
},
},
"buildOptions ":
"emitEntryPoint ": true , "preserveCompilationContext ": true
}'
" runtimeOptions ": {
" configProperties ": { " System . GC . Server ": true }
620 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Core MVC
В листинге 20.10 приведен класс Sta r tup, который конфигурирует средства, пре
доставляемые пакетами NuGet, и использует метод AddS ing leton () ,чтобы настро
ить отображение службы для хранилища объектов модели.
Арр UR : [hitP:/11ocalhost:7000/
0 EnaЫe SSL
URL:
~ ЕnаЫе Anonymous Aulhentication
D i!ESTfulC0ttttol!~rs х
~ f\ES1ful Coritrol:ers
Namo;
!· :.m~~~C~~:'h~~~~~-~:-:= ~:~ ~-::;-:::~~~·-:::-=:._::~ J
Jacqu1
l ocalion:
Mooting Rooni 7 1 Loci11ion:
I~
ll __,.._
!
'
ю
1
Clicnl
Аl1св
ВоЬ
l ocalion
Вoard Room
Leclur• Hall
j
1 ID
•
l ocation
ВoordRoom !
1 1
_ _ •.... !
L.--
Проблема скорости
В настоящий момент пример приложения представляет собой сuнхрон.н.ое веб
приложение . После того как пользователь щелкает на кнопке Add, браузер отправля
ет серверу запрос POST, ожидает ответа и затем визуализирует полученную НТМL
разметку. В течение этого промежутка вр емени пользователь ничего не может делать,
а только ждать. Период ожидания может быть незначительным на этапе разработки,
когда браузер и сервер располагаются на одной машине; тем не менее, развернутые
приложения подпадают под реальные характеристики ограничений и задержек , по
этому время, в течение которого синхронное приложение вынуждает пользователя
ложении, где все клиенты подключаются через быструю и надежную локальную сеть,
тогда проблема может не возникать. С другой стороны, если пишется приложение для
мобильных клиентов на площадках со скудной инфраструктурой, то проблема скоро
сти может быть существенной.
622 Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC
Совет. Некоторые браузеры позволяют эмулировать разные типы сетей, что является удоб
ным инструментом для выяснения, склонны ли пользователи согласиться на работу с син
хронным приложением в определенном диапазоне сценариев. Скажем, браузер Google
Chrome предлагает средство под названием сетевое регулирование, которое доступно в
разделе Network (Сеть) инструментов, вызываемых по нажатию <F12>. Доступен диапа
зон предопределенных сетей или же можно создать собственную сеть, указав скорости
выгрузки и загрузки, а также задержку.
Проблема эффективности
Проблема эффективности проистекает из способа, которым синхронное веб-прило
жение трактует браузер как механизм визуализации НТМL-разметки, используемый
только для отображения отправляемых сервером НТМL-документов.
Когда пользователь впервые запрашивает стандартный URL примера приложения,
отправленный обратно НТМL-документ содержит все, в чем браузер нуждается для
отображения содержимого приложения, включая перечисленную ниже информацию:
Пример приложения прост, и начальный запрос приводит к тому, что сервер по
сылает клиенту около 1,3 Кбайт данных. Однако когда пользователь отправляет
форму, клиент перенаправляется снова на действие Index, в результате давая еще
1,3 Кбайт данных, чтобы отразить добавление одиночной строки таблицы. Браузер
уже визуализировал форму и таблицу, но они были отброшены и заменены полно
стью новым представлением того, что является практически совершенно тем же са
мым содержимым.
Может показаться, что объем в 1,3 Кбайт данных не особенно велик и, несомнен
но, вы будете правы. Но если учесть соотношение между полезным и дублированным
содержимым, тогда вы увидите, что подавляющее большинство данных, посылаемых
браузеру, оказывается излишним. К тому же пример приложения намеренно упро
щен; очень немногие реальные приложения требуют настолько мало НТМL-разметки,
и по мере возрастания сложности приложения объем дублированного содержимого
значительно увеличивается.
Проблема открытости
Последняя проблема традиционного веб-приложения связана с тем, что его про
ектное решение закрыто, означая возможность доступа к данным в модели только
/api/reservations/1
Первая часть URL - api - используется для отделения части данных приложе
ния от стандартных контроллеров, которые генерируют НТМL-размет1<у. Следующая
часть - reservations - указывает коллекцию объектов, с которой будет произво
диться работа. Финальная часть - 1- задает индивидуальный объект внутри коллек
ции reservations. В примере приложения это значение свойства Reservationid,
которое уникальным образом идентифицирует объект, и будет применяться в URL.
Идентифицирующие объект URL объединяются с методами НТГР для указания
операций. В табл. 20.3 перечислены наиболее часто используемые методы НТГР и
описано, что они представляют, когда комбинируются с URL примера. Также приведе
ны детали о том, какие данные (т. е . полезная нагрузка) включаются в запрос и ответ
для каждой комбинации метода и URL. Контроллер АР!, который обрабатывает такие
запросы, с помощью кода состояния ответа сообщает об исходе запроса.
Следование соглашению REST не является требованием, но содействует упроще
нию работы с приложением, потому что один и тот же подход широко прим еняется
во многих уже установившихся веб-приложениях.
Таблица 20.З. Объединение методов НТТР с URL для указания веб - службы REST
Ко м анда URL Описание Полезная нагрузка
чение 1
POST /api/reservation Эта комбинация со- За п рос содер ж ит значения
здает новый объект для друг их свойств, которые
Reservat i on требуются для создания
объекта Reservation.
Ответ содержит объект, ко-
торый был сохранен , гаран-
тируя получение клиентом
сохраненны х данны х
данных
чение 1
Совет. Вспомните , что классы контроллеров могут определяться где угодно в прое кте, а не
только в папке Con t ro ll ers. Для крупных и сложных проектов может быть удобно опре
делять контроллеры API отдельно от обычных контроллеров HTML и помещать их в какую
нибудь подпап ку и ли даже в выделенную папку.
[HttpGet]
puЫic IEnumeraЫe<Reservation> Get( ) => repository . Reservat i ons ;
[HttpGet ( 11 { id} 11 ) ]
puЫic Reservation Get(int id) => repository[id ];
[HttpPost]
puЫic Reservation Post( [ FromBody ] Reservation res) =>
repository . AddReservatio n (new Reservation {
ClientName = res . Cli entName ,
Location = res . Location
}) ;
[HttpPut]
puЫic Reservation Put( [FromBody ] Reservat i on r es) =>
repository . UpdateReservation(res) ;
[HttpDelete ( " { id} " ) J
puЬlic void Delete(int id) => repository . DeleteReservation(id) ;
Подобно всем паттернам REST представляет собой отправную точку, предлагающую удоб
ные и полезные идеи . Это не жесткий стандарт, который дол жен соблюдаться любой ценой ;
единственная важ ная цель в том, чтобы писать понятный, тестируемый и сопровождаемый
код. Учет природы приложений MVC и проектного решения, положенного в основу храни
лища , способствует получению более простого приложения и по-пре ж нему предоставляет
практичный АРl-интерфейс для потребления клиентами. Я советую рассматривать паттерны
к ак ру ководящие принципы, которые вы подгоняете под собственные нужды, что справед
ливо в отношении как REST, так и MVC в целом .
626 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
Определение маршрута
Маршрут, посредством которого достигаются контроллеры АР!, может определяться толь
ко с использ ованием атрибута Route , но не конфигурации приложения в классе Start up.
По соглашению для контроллеров API применяется маршрут, предваренный префиксом
ap i , за которым следует имя контроллера, так что контроллер Reservat i onCont r oller
из листинга 20.11 достигается через URL вида / api / r eserva t i on :
[ Route( " ap i / [ controller ]" )J
puЬlic cla ss ReservationController Contro l le r {
Объявление зависимостей
Объекты контроллеров API создаются так же, как и объекты обычных контролле
ров, а это значит, что они могут объявлять зависимо сти , которые будут распознав ать
[HttpGet]
pu Ы ic IEn u m era Ьl e<Re s erva tio n> Ge t() => r epo sit or y. Rese r vations ;
Имя Описание
HttpGet Этот атрибут указывает, что действие может быть вызвано только
НТТР-запросами, которые используют команду GET
HttpPos t Этот атрибут указывает, что действие может быть вызвано только
НТТР-запросами, которые применяют команду POST
Http Dele t e Этот атрибут указывает, что действие может быть вызвано толь ко
НТТР-запросами, которые используют команду DELETE
Ht t p Pu t Этот атрибут указывает, что действие может быть вызвано только
НТТР-запросами, которые применяют команду PUT
HttpPatch Этот атрибут указывает, что действие может быть вызвано только
НТТР-запросами, которые используют команду РАТСН
HttpHead Этот атрибут указывает, что действие может быть вызвано только
НТТР-запросами, которые применяют команду HEAD
AcceptVerbs Этот атрибут используется для указания множества команд НТТР
Глава 20. Контроллеры API 627
Маршруты могут дополнительно конкретизироваться за счет включения фраг
мента маршрутизации в качестве аргумента для атрибута метода НТГР:
[H ttpGet]
puЫic IEnumeraЫe<Reservation> Get() => repository . Reservations ;
Формат JSON
Формат JSON (JavaScript Object Notatioп - система обозначений для объектов JavaScript)
стал ста ндартным форматом данных для веб-приложений . Формат JSON популярен бла
годаря простоте, лаконичности и легкости в работе с ним . Данные JSON особенно легко
обрабатывать в коде JavaScript, потому что формат JSON подобен способу выражения ли
теральных объектов на языке JavaScript. Современные браузеры включают встроенную под
держку для генерации и разбора данных JSON , а популярные библиотеки JavaScript, такие
как jQuery, будут автоматически преобразовывать в и из формата JSON.
Хотя формат JSON эволюционировал из JavaScript, его структура легка в чтении и понима
нии для разработчиков на языке С# . Вот как выглядит ответ из контроллера API в примере
приложения:
[ { " reservationid ": О , " c l ie n tName " : " Alice ", " location ": "Board Room " } ,
{ " reservation i d " : 1 , " c li entName ": " ВоЬ ", " location ": " Lectur e Ha ll"},
{" reservationid ": 2 , " cl ientName ":" Joe ", " location ":" Meeting Room 1 " }]
Однако обратите внимание, что MVC заменяет соглашение С# по записи имен свойств , на
чиная с заглавной буквы (например, ClientNarne), соглашением JavaScript (clientNarne
начинается со строчной буквы).
Контроллер Reservation пр едоставляет две операции GET. Когда запрос GET от
правляется на URLвида /api/reservation , возвращается ответ, содержащий все
объекты в м одели . Чтобы извлечь одиночный объект, его значение Reservationid
указывается как финальный сегмент в URL, наприм е р:
Invoke-RestMethod http://localhost:7000/api/reservation
-Method POST -B ody (@
{ clientName = "Anne "; location = "Meeti ng Room 4"} 1 ConvertTo-Json)
- ContentType " application/json"
В приведенной команде с помощью аргумента -Body указывается тело запроса,
которое кодируется как JSON. Аргумент -ContentType используется для установки
заголовка Content-Type в запросе. Команда даст следующий результат:
reservationid clientName locati on
1 Во Ь Medi a Room
Чтобы взглянуть на эффект от запроса POST , отправьте еще один запрос GE T на
URL вида /api/reservation:
Invo ke - RestMe t hod http : //localho s t : 7000/api/reservation - Method GET
Возвраще нны е данные содержат добавленный новый объект Res ervation:
reser·vationid clientName l ocatio n
------------- ----------
--------
о Al i ce Board Room
1 ВоЬ Media Room
2 Joe Meeti ng Room 1
3 Anne Meet i ng Room 4
$(document) . ready(function () {
$( " form " ) .submit(function (е)
e . preventDefault() ;
$ . ajax({
url: " api/reservation ",
content Type: " application/json ",
method: " POST "'
data : JSON.stringify({
clientName : this.elements [" ClientName "] . val ue,
location : this.elements[ " Location"] . value
}) '
success : function(data)
addTaЬleRow(data) ;
}
})
}) ;
}) ;
Листинг 20.14. Добавление ссылок на файлы JavaScript в файле _ Layou t. csh tml
<html>
<head>
<meta name= "viewport " content="width=device - width " />
<t itle >RESTful Controllers</title>
<link asp-href - include= "l iЬ/bootstrap/dist/css/*.min . css "
rel= " stylesheet" />
<script src="l.iЬ/jquery/dist/jquery.js"></script>
<script src="js/client.js"></script>
</head>
Форматирование содержимого
Когда метод действия возвращает объект С# в качестве своего результата, инф
раструктура МVС обязана выяснить, какой формат данных должен использоваться
для кодирования объекта и отправки его клиенту. В настоящем разделе объясняется
стандартный процесс, а также влияние на него запроса, отправляемого клиентом, и
конфигурации приложения. Для содействия прояснению работы процесса добавьте в
папку Contro lle rs файл класса по имени ContentController . cs и определите в
нем контроллер, приведенный в листинге 20 . 15.
text/plain ; c ha r s e t = ut f - 8 Th i s is а s tr i ng r es pon se
Ту ж е с амую команду можно также использовать для отображения результата в
формате JSON. изменив лишь запрашиваемый URL:
Invoke - WebRe ques t http : //loca l ho s t :7 000/ api/content/object
select @{n = ' Content-T ype '; e = {
$ .Headers ." Con t e nt - Type " }}, Conten t
636 Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC
appl i cation /j so n; cha r set utf-8 {" rese r va ti onid ": l OO,
'' clientName '' : '' Joe '',
" loca t ion ":" Board Room " }
Согласование содержимого
Большинство клиентов будут включать в запрос заголовок Accept , ука з ывающий
набор фор м атов, которы е они готовы получать в ответе , выр аженный в виде множ ес
тва типов MIME. Ниже прив еден заголовок Accep t, который посылает в з апросах бр а
узер Google Chrome:
Acc ept : text / html, appl ic ation/xhtml + xml , app licat i on/xml ;
q = 0 . 9,image/webp , */* ; q = 0 . 8
Заголовок указывает, что Chrome может обраб атывать форматы HTML и XHTML
(ХНТМL - это совместимый с ХМL диалект НТМ L), ХМL . а также формат и з ображени й
WEBP. Значения qв заголовке задают относительные предпочт ения, где зн ачение 1. О
является стандартным. Указание знач ения q, равного О . 9, для application/xml со
общает серверу о том, что Chrome будет прини м ать данные XML. но пр едпочита ет
и м еть дело с HTML или XHTML. Последний элем е нт, * / *. уведомляет сервер о том,
что Chrome будет принимать любой формат, но его значение q указывает наименьшее
предпочтени е с реди п е р е численных типов. В итоге это означает, что заголово1t Accept.
отправляемый браузером Chrome, снабжает сервер следующей информацией.
1. Бр аузер Chrome предпочитает получать данные HTML или ХНТМL л ибо изобр а
жения WЕВР.
Вы мож ете предположить . что для изменения формата з апроса, получ е нного от
приложения MVC, достаточно установить заголовок Ac cept , но он не работает подоб
нь1м образом - точнее он не работает подобным образом из-за того, что требуется
определенная подготовка. Для начала вот команда PowerShell, посылающая GET м е
тоду GetObj ect () запрос с заголовком Accept , в котором указано, что клиент будет
принимать только данные ХМL:
Когда инфраструктуре MVC доступен только формат JSON , у нее нет никакого вы
бора кроме кодирования ответов как JSON. Теперь . когда выбор есть, м о жно пон аблю
дать за более полной работой процесса согласования содержимого.
services.AddSingleton<IRepository, MemoryRepository>() ;
services . AddMvc()
. AddXmlDataContractSerializerFormatters()
. AddMvcOptions(opts => {
opts . FormatterMappings . SetMediaTypeMappingForFormat( " xml ",
new MediaTypeHeaderVa l ue( "appli cation/xml" )) ;
opts.RespectBrowserAcceptHeader = true;
opts.ReturnHttpNotAcceptaЬle = true;
}) ;
Ниже показана команда PowerShell, которая отправляет запрос GET на URL вида
/ap i /content/object с заголовком Accept , указывающий тип содержимого, кото
рый приложение не мож е т предоставить:
[HttpPost]
[Consumes("application/xml")]
puЫic Reservation ReceiveXml([FromВody] Reservation reservation) {
reservation.ClientName = "Xml";
return reservation;
Резюме
В настоящей главе объяснялась роль, которую контроллеры API играют в при
ложениях МVС. Было продемонстрировано, каким образом создавать и тестировать
контроллер АР! и как выполнять НТТР-запросы с применением jQuery. Вдобавок был
описан процесс форматирования содержимого. В следующей главе более подробно
рассматривается работа представлений и механизмов визуализации.
ГЛАВА 21
Представления
Вопрос Ответ
Что это такое? Представления - это часть паттерна MVC, применяемая для отобра
жения содержимого пользователю. В приложении ASP.NET Core MVC
представление является файлом, содержащим НТМL-элементы и код
С#, который обрабатывается для генерации ответа
"dependencies ": {
"Microsoft . NETCore . App ":
"version ": "1. 0 . 0",
" type ": "platforrn"
} 1
} 1
} '
646 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
Создайте папку Cont r ollers, добавьте в нее файл класса по имени HomeController.
cs и определите контролл е р. приведенный в листинге 21.3.
Имя Описание
ния при визуализации ответов: наиболее полезные свойства такого рода приведены
в табл. 21.4.
Имя Описание
if (viewName == "DebugData" ) {
retur n ViewEngine Resu l t .Found(vie wName, new Deb ugDa t aView( ));
[j localhost55775
,~~- ~ @>\;~st:.55775
1 ---Ro ut i п g Data---
1 кеу: cont roller, val ue : ноте
Ке у :act ion , val ue : Index
--- Vie1<1 Data---
кey : Message, Value : Hel l o, world
ке у: Time, Val ue : 16 : 09 : 05
[___ _______ !
1 :nun:~~~~~~~::~~:cu~~-w~~~~~=~n~~I
request.
lnvalidOperationExceprion: The vie1v 'List' was oor found. The follo1ving
locations wеге searcl1ed:
{Debug Viev.1 Engiпe - GetVievv)
(DebLJ9 Vievv E ngiпe - FiпdViev1)
Nievvs7Home/ List.cs11tml
/Vie1vs/Sl1a1·ed/List.csl1tml
Mkrosoft.AspNl?tCore.Mvc. Vie~v En gi ne s.V ie\ч EngineResu l t.En sureSucce ss1u l (IE11un1erab l e· ·1
~1nallocations)
Если з а пустить прил ожение и снова перейти на URL вида / Home/L i st , то легко
з ам етить , что используется только специальный механизм визуализации (рис . 21.3).
f- С ГФio:~~l10~;557~5/Ho~e(!::st -··---------~-==--- ~
1-;n·-~nha-;;dled·~·
1 request.
xce~b-on o~curr~d whil~ ~;~~~ssing ~h-;--::-
·
1 lпvaliclOperationException: The vie\.Y 'List' 1.vas поt foL1пd. Tl1e follo~vi11g
1 l ocatioпs v1ere sea rched:
1 (Debug View Engi11e - GetVievv>)
. (Debug V iev{ Е пgiпе - FiпdVie\N)
M icrosolt.AspNetCore.Mvc.Vie1\•Engil\es.Vie1•1EngineResult.EnsureSuccesslul(I En un1eraЬle ' 1
1 originallocations)
1,_. __________ .
Онеrу Cookies
.
Heade1s
----·--------------------
w~"'·' . " - . . ~
J .
using System;
using Microsoft . AspNetCore . Mvc;
namespace Views . Controllers {
puЫic class HomeController : Controller
puЫic ViewResul t Index () =>
View(new string[] { "Apple", "Orange", "Pear" }) ;
puЫic ViewResult List() => View() ;
@model string []
@{ Layout = null;
< !DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Razor</title>
<link asp-href- include= 11 liЬ/boots trap/d ist/css/* . min. css 11 rel= 11 stylesheet 11 />
</head>
Глава 21. Представления 655
<body class= "panel - body " >
This is а list of fruit narnes:
@foreach (str i ng narne in Model)
<span><b>@narne</b></span>
</body>
</htrnl>
app . UseStatusCodePages();
app.UseDeveloperExceptionPage() ;
app .UseStaticFiles();
app . UseMvcWithDefaultRoute();
Код этого класса бьm приведен в порядок, чтобы облегчить его чтение. Кроме того,
были удалены операторы С#, которые Razor добавляет с целью оснащения инстру
ментами при генерации класса. В последующих разделах этот класс разбивается на
части, и приводятся объяснения работы скомпилированных представлений.
На за м етку! Прежде было легко просматривать классы, созданные более ранними версиями
Razor, т.к. для каждого представления генерировался дисковый файл С#, который затем
компилировался и использовался в приложении. Изучение класса сводилось просто к на
хождению правильного файла. Текущая версия Razor опирается на усовершенствования
компилятора С# , которые позволяют коду генерироваться и компилироваться в памяти,
что обеспечивает улучшение производительности, но затрудняет выяснение происходя
щего. Чтобы получить показанный выше класс, пришлось изменить предназначение ряда
модульных тестов, входящих в состав исходного кода ASP.NET Core MVC, которые предо
ставили фиктивные реализации классов, применяемые Razor для нахождения и обработки
файлов представлений. Это не то, что вам придется предпринимать при повседневной
разработке, но такой процесс позволяет выявить очень многие детали о том, как работа
ют представления .
Имя класса
Лучше всего начать с имени класса, который создается Razor:
Механизму Razor нужен какой-нибудь способ для трансляции имени и пути к фай
лу CSHTML в кл асс, который он создает, когда производит разбор файла, и он делает
это за счет кодирования информации в имени к11асса. Механизм Razor снабжает имя
класса префиксом AS PV, за которым следует имя проекта, имя контроллера и, нако
нец, имя файла представления: такая комбинация облегчает проверку доступности
кл асса при запрашивании инфраструктурой МVС представления через интерфейс
I Vi ewEng in e , описанный ранее в главе.
Базовый класс
Многие основны е средства Razor, такие как ссылка на модель представления в
виде @Model , возможны благодаря базовому классу, производными от которого явля
ются сгенерированные классы:
658 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Core MVC
Имя Описание
Механи зм Razor такж е предлагает ряд вспо м огательных свойств , которые м ож
но прим енять в представлениях для генерации содержимого (табл. 21 .7).
Глава 21. Представления 659
Таблица 21. 7. Вспомогательные свойства Razor
Имя Описание
Визуализация представлений
В дополнение к свойствам и методам, предоставляющим средства для разработчи
ков, в обязанности класса
RazorPage также входит генерирование содержимого от
ветов через его метод
ExecuteAsync () .Этот метод показывает, каким образом Razor
обрабатывает файл Index. cshtml с помощью набора операторов С#:
@section Footer {
<div class="bg-success">
This is the footer
</div>
}
<body class="panel-body">
@RenderSection("Header")
<d iv class="bg-info">
This is part of the layo ut
</ div>
@RenderBody ()
<d iv class="bg-info">
This is part of the layout
</div>
@RenderSection("Footer")
<div class="bg-info">
This is part of the layout
</div>
</body>
</html>
l
This is the footer 1
This is part of the layout
</div>
@section Body
This is а list of frui t names:
@foreach (string name in Model)
<span><b>@name</Ь></span>
@section Footer {
<div class= " bg-success " >
This is the footer
</div>
@RenderSection ( "Body")
<div class= " bg -inf o " >
This is part of the layout
</div>
@RenderSection ( " Footer " )
<div class= " bg-info " >
This is part of the layout
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta name= " viewport " content= " width=device - width " />
<title>@ViewBag .Ti tle</title>
<l ink asp - href - include= " liЬ/bootstrap/dist/css/* .min. css "
rel= " stylesheet " />
</head>
<body class= " panel -body " >
@RenderSection( " Header " )
<div class= " bg - info " >
This is part of the layout
</div>
@RenderSection( " Body " )
<div class= " bg-info " >
This is part of the layout
</div>
@if (IsSectionDefined("Footer"))
@RenderSection (" Footer")
} else {
<h4>This is the default footer</h4>
}
раздел Footer.
<!DOCTYPE html>
<html>
<head>
<meta name= " viewport" content= " width=device - width " />
<title>@ViewBag.Title</title>
<link asp-href-include=
"liЬ/bootstrap/dist/css/*.min.css" rel="stylesheet " />
</head>
<body class="panel -b ody " >
@RenderSection("Header ")
<div class= " bg -i nfo">
This is part of the layout
</div>
@RenderSection("Body " )
<div class= " bg-info " >
This is part of the layout
</div>
@if (IsSectionDefined("Footer"))
@RenderSection("Footer " )
else {
<h4>This is the default footer</h4>
@RenderSection ( "scripts")
<div class="bg-info " >
This is part of the layout
</div>
</body>
</html>
С ~ locall1o>t:SS775 . '{:: J• 1
Гдn-~~handled
request.
exception occu~~~d \tVhil~ pгocessin; th;
'· 1
1 Move№xt
исключения.
@( Layout = null ;
< ! DOCTYPE html>
<html>
<head>
<meta name= "viewport " content= " width=device - width " />
<title>Razor</title>
<link asp-href-include= 11 liЬ/boo tstrap/di st/ css/* .min . css 11 rel= 11 stylesheet 11 />
</head>
<body c l ass= " panel - body " >
This is the List View
@Html . Partial( "MyPartial ")
</body>
</html>
Совет. Механизм Razor ищет частичные представления там же, где и обычные представле
ния (в папках -/Viеws/<контроллер> и -/Views/Shared). Другими словами, мож
но создавать специализированные версии частичных представлений, специфичные для
контроллера, и переопределять частичные представления с таким же именем из папки
Shared.
С ~~os1:55775/Home/list R- 1 .
----- ----· J :
n 1is is the List View
This is the. message from the partial view.
This is а link to the lndex action
</ul>
</div>
С испол ьзованием стандартного выражения @mod e l опр еделяется тип модели пред
ставл ения , а с помощью цикла@f o re a c h отображается содержимое объе1па модели
представления в виде элементов списка HTML. Чтобы задействовать новое частично е
представление, модифицируйте содержимое файла /V ie ws/Common/Lis t. cshtml,
как показано в листин ге 21.23.
@{ Layou t = null ;
<!DOCT YP E html>
<html >
<head>
<meta name ="viewport " cont e nt= "width=de vic e-width " />
<ti t le>Razor</t it le>
<link asp- hre f - include=" liЬ/boots t rap/dis t / css/* .mi n . css " rel= " s tylesheet " / >
</head>
Глава 21. Представления 669
<body class="p a nel-bo dy">
This is the List View
@Html.Partial("MyStronglyTypedPartial",
new string[] { "Apple", "Orange", "Pear" })
</ b o dy >
< / html >
"name": "asp.net",
"priva te ": true ,
"dependencies" : {
"bo o tstrap": "З.3.6",
"jquery": 11 2.2.4 11
@{ Layout = null;
< ! DOCTYPE html>
<html>
<head>
<meta name="viewport" content= "width=device - width " />
<title>Razor</title>
<link asp-href-include=" liЬ/bootstrap/dist/ css/* .min. css " rel= " stylesheet " />
<script id="jsonData" type="application/json">
@Json.Serialize(new string[] { "Apple", "Orange", "Pear" })
</script>
</head>
<body class= " panel - body " >
This is the List View
<ul id="list"></ul>
</body>
</html>
Чтобы можно было работать с данными JSON, к представлению List . cshtml до
бавляется библиотекаjQuеrу и встраиваемый код JavaScript, который прим е няет ее для
разбора данных JSON и динамического создания НТМL-элементов (листинг 21.26).
Листинг 21.26. Использование данных JSON в файле List. cshtml
@{ Layout = null;
<!DOCTYPE html>
<html >
<head>
<meta name="viewport" content= "width=device - width " />
<title>Razor</title>
<link asp-href - include= "liЬ/ bootstrap/dist/css/*.min . css " rel="stylesheet" />
<script id= "j sonData" type= " app l ication/json " >
@Json.Serialize(new string[ ] { "Apple ", "Orange" , "Pear" })
</script>
<script asp-src-include="lib/jquery/dist/*.min.js"></script>
<script type="text/javascript">
$(document) .ready(function () {
var list = $ ("#list")
JSON. parse ( $ ( "# j sonDa ta" ) . text () ) . f orEach ( function (val) {
console. log ( "Val: " + val) ;
list. append ($ ( "<li>") . text (val)) ;
}) ;
}) ;
</script>
</head>
Глава 21 . Представления 671
<body class= " panel - body " >
This is the List View
<ul id="l i st"> </ul >
</body>
</html>
По сле запуска приложения и запроса URL вида /Horne/L ist отобразится содержи
мое, показанное на рис. 21.9. Это не самая впечатляющая работа с данными JSON, но
она демонстрирует, как их можно включать в представления.
~е_а_г~~--~~·
Рис. 21.9. Использование данных JSON в представлении
Имя Описание
using System;
using Microsoft.AspNetCore . Mvc;
namespace Views .Controllers {
puЫic class HomeContr ol l er : Controller
puЬlic ViewResul t Index () =>
View("MyView", newstring[] { "Apple", "Orange", "Pear" });
puЫic ViewResult List() => View() ;
Глава 21. Представления 673
Запустив приложение и запросив стандартный URL, вы увидите, что в сообщении
об ошибке отображаются стандартные местоположения поиска представления:
/Views/Horne/MyView.cshtml
/Views/Shared/MyView.cshtrnl
using Systern.Collections.Generic;
using Microsoft . AspNetCore.Mvc.Razor ;
namespace Views . Infrastructure
puЫic class SimpleExpander : IViewLocationExpander
puЫic void PopulateValues(ViewLocationExpanderContext context) {
//ничего не делать - метод не требуется
IsMainPage Это свойство возвращает false , если ме х анизм Razor ищет час
тичное представление, и tru e в противном случае
Values Это свойство возвращает объект IDi ctionary<stri ng , string>,
к к оторому расширитель местоположений представлений добавляет
пары "ключ-значение " , уникально идентифицирующие категорию за
проса, ка к объясняется ни же
пол оже ний пр ед ставл е н ий . Л ег че вс его это объяснить с помощью прим ера, так что
доб авьте в п апку Infrastructu r e файл класс а по имени ColorExpander . cs с опр е
дел ением из листинга 21 .30.
string color ;
context . Values .T ryGet Va lu e( " co l or", out color) ;
foreach (string location in viewLoca t ions) {
if ( !str i ng . IsNullOrEmpty(color)) {
yield return location . Rep l ace( " {O) ", color) ;
else {
yield r etur n loca t ion ;
Метод Pop ulateVa l ues () исполь зу ет объект Act i onContext для получ е ния дан
ных маршрутизации и ищет значение сегмента id в URL. Если есть сегмент id и его
значени ем является red, green или Ыuе , тогда расширитель м естополож е ний пр ед
ставлен ий доб авляет в словарь Va l ues свойство color . Это процесс категори заци и:
з апрос, сегмент id которого соответствует цвету , категоризируется с кл ючо м color,
чье значение выводится из значения сегмента.
Совет. Ме х анизм Razor вызывает метод Popu lateValues () для каждого запроса пред
ставления, но кеширует набор местоположений поиска, возвращаемый методом
ExpandViewLocat i ons () .Таким образом , последующие запросы, для к оторы х метод
PopulateVa l ues () генерирует тот же самый набор ключей и значений категоризации,
не требует вызова метода ExpandVi ewLocat i on s ().
/V i ews/Home/Red . cshtml
/Views/Common/Red . csh t ml
/V i ews / Legacy /Home / Red / View . c s h tml
Аналогично запрос URL вида /Home/Index /g r een заставит Razor искать в таких
м е стополож е ниях:
/Views/Home/Green.cshtml
/Views/Common/Green . cshtml
/Views/Legacy/Home/G r een/Vi e w. cs h tm l
Порядо1<, в 1<отором зарегистрированы расширители местоположений пред
ставл е ний, важен, потому что набор местополож е ний, генерируемый методом
ExpandVie wLocat i on s ( ) одного расширителя, применяется в качестве аргумента
viewLocations для следующего расширителя в списке . Это можно заметить в по
казанных ран е е местополож ениях, где Vi e ws/ Common и Views/Le g a c y генерирова
лись классом SimpleExpa nde r, 1<оторый находился перед Co l or Expander в классе
Startup .
Ре з юме
В настоящей главе было продемонстрировано, 1<а1< создавать специальный ме
ханизм визуализ ации , и объя с нялось, каким образом работает механизм Razor при
трансляции файлов CSHTML в классы С# . Вы узнали, как исполь:Зовать разделы
компоновки и частичные пр едставления, и научились изменять местоположения, в
которых Razor ищет файлы представлений. В следующей главе будут описаны ком
поне нты пр едставлений, которые прим еняются для обеспечения логи1<и поддержки
частичных представлений .
ГЛАВА 22
Компоненты
v
представлении
ставлений - это классы, предлагающие логику в стиле действий для поддержки час
тичных представлений. Это означает возможность встраивания сложного содержимо
го в представления и вместе с тем простое сопровождение и модульное т е стировани е
кода С#, который его поддерживает. В табл. 22. l приведена сводка, позволяющая по
местить компоненты представлений в контекст.
Вопрос Ответ
},
" tools ":
"Microsoft . AspNetCore.Server . IISintegration . Tools ": "1. 0 . 0- preview2 - final ",
"Microsoft.AspNetCore .Razor.Tools": "l.0.0-preview2-final"
},
},
" buildOptions ":
" emitEntryPoint ": true ,
" preserveCompilationContext": true
},
" runtimeOptions": {
" configProperties " : { " System . GC .Se rver ": true }
Чтобы создать хранилище для объектов Product, добавьте в папку Models файл
по имени ProductReposi tory. cs и определите в нем интерфейс и класс реализа
ции, как показано в листинге 22.3.
Глава 22 . Комnоненты nредставлений 681
Листинг 22.З. Содержимое файла ProductReposi tory. cs из папки Models
using System.Collections . Generic;
namespace UsingViewComponents . Models
puЬlic interface IProductRepository {
IEnumeraЫe<Product> Products { get;
void AddProduct(Product newProduct);
Интерфейс ICi tyReposi tory предлагает огранич ен ный набор средств храни
лища. а класс MemoryCi tyReposi tory реали зует этот интерфейс с использованием
объекта List. находящегося в памяти .
Контроллер Home прим еняет свой конструктор для объявления зависимости от ин
терфейса IProductReposi tory. которая будет распознаваться по ставщиком служб,
когда контроллер станет использоваться при обработке запросов. Действие Index из
влекает все объекты Product из хранилища и визуализирует их с применением стан
дартного представления. Два метода Crea te ( ) задействуют паттерн Post/Redirect/
Get при добавлении новых объектов в хранилище с использованием данных формы.
передаваемых клиентом.
Глава 22. Комnоненты nредставлений 683
Пр едставления в рассматриваемом примере будут разделять общую компоновку.
Создайте папку Views/Shared и добавьте в нее файл по имени La yout . cshtml с
разметкой из листинга 22 .7.
<tаЫе class= " taЫe taЬle - condensed taЬle -st riped taЫe -b ordered
11
>
<thead>
<tr><th>Name</ th><th>Price</th></tr>
</thead>
<tbody>
@foreach (var product in Mode l )
<tr>
<td>@product .N ame</td>
<td>@product .P rice</td>
</tr>
}
</tbody>
</tаЫе>
<а asp - action ="Create" class= 11 Ьtn Ьtn - primary " >Crea te</a>
684 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Соге MVC
Финальным элементом в пр едставлении I ndex является элемент а. который сти
лизован как :кнопк а и нацеле н на действие Create , так что поль з ователь мож ет со
здавать новый объе 1<Т Product в хранилище. Чтобы со здать форму . которую будет
заполнять пользователь. добавьте в папку Views/Horne файл Create . cshtrnl с раз
меткой из листинга 22.9.
D Pro<ivcis )(
Products
Name: Name Price
275
Общая идея всех упомянутых примеров состоит в том, что данные, требуемые для
отображения встроенного содержимого, не являются частью данных модели, которые
передаются из действия в представление. Именно по этой причине в примере прило
жения были созданы два хранилища: мы собираемся отображать содержимое, генери
руемое с использованием хранилища объектов Ci ty, что нелегко делать в представле
нии, которое получает от своих действий данные из хранилища объектов Product.
В главе 21 было показано , каким образом частичные представления применя
ются для создания многократно используемой разметки, которая требуется в при
ложениях. избегая дублирования одного и того же содержимого во множестве мест
приложения. Частичные представления - полезное средство, но они просто содер
жат фрагменты НТМL-разметки и директивы Razor, а данные, с которыми они имеют
дело , получаются от родительского представления. Если нужно отображать другие
данные, тогда возникает проблема. Можно было бы получать доступ к необходимым
данным прямо из частичного представления, но это нарушит принцип разделения
Листинг 22. 14. Применение компонента представления в файле _ Layou t. csh tml
<!DOCTYPE htrnl>
<html>
<head>
<rneta narne="viewport" content="width=device - width " />
<title>@ViewBag . Title</title>
<link asp-href-include="liЬ/bootstrap/dist/css/*.rnin . css" rel="stylesheet" />
</head>
<body class="panel-body">
<div class="bg - prirnary panel -body " >
<div class="row">
<div class="col-xs-7"><hl>Products</hl></div>
<div class="col-xs-S">@await Component. InvokeAsync ( "Росо") </div>
</div>
</div>
<div class="panel-body">@RenderBody()</div>
</body>
</htrnl>
688 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Core MVC
Products ><
>С l.so_1ocalt1ost:S7584
----·--------·- -----·-·-------l
_ ~
Name
Имя Описание
Vi ewComponent не предусмотрены
Имя Описание
указывается
namespace UsingViewComponents.Models
puЫic class CityViewModel {
puЬlic int Cities { get ; set; }
puЫic int Population { get; set ;
Если имя не указано, тогда Razor ищет файл Defaul t . cshtml . В поисках частич
ного пр едставления механизм Razor просматрива ет два местоположения. П е рво е мес
тоположение учитывает имя контроллера, обрабатывающе го НТТР-з апрос, что позво
ляет каждому контроллеру иметь собственное пр едставл ение. Второе местоположение
ра зделяется м ежду всеми контроллерами.
С) Products Х
!-·-~ - ·.--с-----------
[ @ 1~57584
---·
repository = repo ;
~ - С ~ localh_ost:S7584 - - - - -
·---...-----~-----"...- -----·--"'-------··----------------··
Name Price
~
Имя Описание
[j Products х
Рис. 22.5.
Price
-....~~/-.•-" .....
Возвращение фрагмента незакодированной НТМL-разметки
-.
с использованием компонента представления
Имя Описание
Данные контекста могут использоваться любым спо собом, который помогает ком
поненту представления выполнять свою работу, включая варьирование метода вы
бора данных или визуализацию другого содержимого либо представлений. В листин
ге 22.22 данные маршрутизации применяются для суж ения выборки объектов Ci ty.
Глава 22 . Компоненты представлений 697
Листинг 22.22. И спользование данных к онтекста в файле Ci tySummary. cs
[':) Products х
<tr>
<th>Total : </th>
<td class= "t ext - right " >
@Model . Sum(p => p . Popu l ation) .T oString( " # , ### ")
</td>
</tr>
</tаЫе>
Если аргумент showList метода Invoke () равен true, тогда компонент пред
ставления выбирает CityList и передает все объекты City из хранилища как мо
дель представления. Если аргумент showList равен fals e, тогда выбирается стан
дартное представление с указанием объекта Ci tyViewModel в качестве модели
представления.
D Products Х
~ " С [ ф local~os~~S75~/Hoine/lndex/US~-·---·· -~
- · - - - - - - - · - - - - - - _ _ _4_, __ _
; 1
11 Организация
var mockRepository = new Moc k<ICityRepos i tory>();
mockRepository.SetupGet(m => m.Cities) . Returns(new List<City> {
new City { Population 100 } ,
new City { Population 20000 ) ,
new City { Populat i on 1000000 } ,
new City ( Popula t ion 500000 }
) ) ;
var viewComponent
= new CitySummary(mockRepository.Object) ;
Глава 22 . Компоненты представлений 701
11 Действие
ViewViewComponentResult result
= viewComponent . Invoke(false) as ViewViewComponentResult ;
11 Утверждение
Assert . IsType(typeof(CityViewModel) , result . ViewData . Model) ;
Assert .Equal(4 , ((CityViewModel)result . ViewData . Model) . Cities) ;
Assert . Equal(l520100 ,
( (CityViewModel)result . ViewData . Model) . Population) ;
"dependencies ": {
"Microsoft . NETCo r e . App ":
"version " : "1. 0 . О ",
"System.Net.Http": "4.1.0"
) 1
702 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
Чтобы создать это представление, добавьте в проект папку Views / Home / Componen ts /
PageS i ze и поместите в нее файл представления по имени Default . cshtml с содер
жимым, приведенным в листинге 22.28.
!
1
J
i
:
l.-.. -·-- -----·---·---------------·-------------......!
Рис. 22.8. Создание асинхронного компонента представления
тинге 22.30.
704 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Core MVC
<form method= "post " asp - action= " Create " >
<div class= " form - gro up " >
<label asp-for= " Name " >Name : </label>
<input class= " form - control " asp - for= " Name " />
</div>
<div class= " form - group " >
<label asp - for= "Country " >Co untry : </label>
<input c l ass= " form-control " asp - for= "Country " />
</div>
<div class= " fo r m- group " >
<label asp - for= " Pop ulation " >Popula ti o n: </ l abel>
<input class= " form - control " asp-for= " Population " />
</div>
<button type= " submit " class= " Ьt n ьt n- p r i m ary " >Create</b u tton>
<а class= " Ьtn Ьtn - default " asp - controlle r = " Home "
asp - action= " Index " >
Cancel
</а>
</form>
@mode l IEnumeraЫe<City>
<tаЫе class= " taЫe taЬle - condensed taЬle - bordered " >
<tr>
<td>B i ggest City : </td>
<td>
@Model . Orde r ByDescendin g(c => c .Popu latio n ) . First() . Name
</td>
</tr>
</tаЫе>
<а class= " Ьtn Ьtn - sm Ьtn - info " asp - cont r oll er= "City " asp - action= "Create " >
Create City
</а>
706 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Соге MVC
<!DOCTYPE html>
<html>
<head>
<meta name= "viewport " content= "width=device -w idth " />
<title>@ViewBag . Title</title>
<link asp-href -i nclude="liЬ/bootstrap/dist/css/* . min.css" rel= "stylesheet " />
</head>
<body class= "panel - body " >
<div class= "bg-p rimary panel-body">
<div class ="r ow " >
<div class= " col - xs - 7"><hl>Products</hl></div>
<div class ="c ol - xs - 5 " >
@awai t Component . InvokeAsync ( "ComЬoComponent")
</div>
</ d iv>
</div>
<div class =" panel - body " >@RenderBody()</div>
</body>
</html>
с [~ ~~~!~!ё~~~~~~--=-~-==~=--;=ю. - ~ -!
№rne:
Bei1lng
Counlry:
Chlna
/ Kayak 275
Lile/ackel 48.95
" \ Can~~ 1
, Soccer ba!I 19 .50
Резюме
В этой главе было дано введение в компоненты представлений, которые являют
ся новым средством ASP.NET Core MVC и заменяют собой средство дочерних дейс
твий из предыдущих версий MVC . Вы узнали, каким образом создавать компоненты
представлений РОСО и как применять базовый класс ViewComponent . Были проде
монстрированы три разных типа результатов, которые могут выпускать компонен
д ASP.NET
ескрипторные вспомогательные классы -
Core MVC.
это новое средство, появившееся в
Они являются классами , которые трансформируют НТМL
элеме нты в пр едставлении. Распространенные случаи использования дескрипторных
вспомогательных классов включают генериров ание URL для форм с применен ием
конфигурации маршрутизации приложения, обеспечение согласованной стилизации
элементов специфического типа и замена специальных СОI{ращающих элементов хо
довыми фрагментами содержимого. В настоящей главе будет показано, как работают
дескрипторные вспомогательные классы и каким обра зом создавать и использовать
специальные деск рипторные вспомогательные классы. В главе 24 будут описаны
встроенные деск рипторны е вс помогател ьные классы, которые поддерживают НТМL
Вопрос Ответ
Что это такое? Дескрипторные вспомогательные классы - это классы , которые мани
пулируют НТМL- элементами с целью их изменения каким - либо обра
зом, дополнения добавочным содержимым или полной замены новым
содер жим ым
гательного класса
"dependencies ": {
"Microsoft . NETCore . Ap p":
" version ": 11
1.0 . 0 11 ,
"Microsoft.AspNetCore.Razor.Tools":
11
version": 11
1. О. 0-preview2-final. 11 ,
"type": "build"
}/
11
t ools 11 :
"Microsoft . AspNetCore .Server.IISintegration . Tools ": "l. 0.0 - preview2 - final ",
"Microsoft.AspNetCore.Razor.Tools 11 : 11 1.0.0-preview2-final"
}/
11
frameworks 11 : {
11
netcoreappl . 0 11 :
imports 11 : [ " dotnetS.6 11 , " portaЬle - net45+win8"]
11
}'
" buildOptions 11 :
" emi tEntryPoint 11 : true, " preserveCompilationContext 11 : true
}/
11
runtimeOptions 11 : {
11
configProperties 11 : { "S ystem.GC . Server ": true}
Чтобы создать хранилище для объектов Ci ty, добавьте в папку Models файл по
имени Reposi tory . cs и определите в нем интерфейс и класс реализации, как пока
зано в листинге 23.3.
ку. Создайте папку Views/ Shared и поместите в нее файл по имени_ La yout. cshtrnl
с разметкой из листинга 23.5.
712 Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC
@model IEnumeraЫe<City>
</tbody>
</tаЫе>
<а href=" /Home/Create " class= "Ьtn Ьtn-primary">Create</a>
namespace Citie s {
D Cities D Citios Х
г;::.:-------
С [ ф l; all1ostS21
с 1~~.1_21
11--------
•
---- -- L
-- --- ---
--- ~---
:1
·-- ---·- =-=- =-=- ,
Name: ~ ' с jФ~;smJ ----~ 1
Beijing
1 - •
1
, London
1 Ne1v York Country: London UK 8,539,000
'°;,~:"00
San Jose USA 998,537
--1-~:_:_~,:J
Paris France
1: , 1
Beijing China
1----·-----·-------1.
L ___ ._ _
Рис . 23.1. Выполнение примера приложения
Глава 23. Дескрипт ор ные вспомогательные классы 715
Создание дескрипторного
вспомогательного класса
Как и со многими средствами МVС, пониманию дескрипторных вспомогательных
классов лучше всего способствует создание одного такого класса, что позволит вы
явить особенности их работы и согласование с остальными частями приложения.
В приведенных далее разделах вы ознакомитесь с процессом создания и исполь
зования дескрипторного вспомогательного класса, который будет применять СSS
<button type= " submit " bs-button-co lor= "danger " >Add</ bu tton>
Совет. Переопределяя метод ProcessAsync () вместо Process (), можно создавать асин
хронные дескрипторные вспомогательные классы, но для большинства дескрипторных
вспомогательных классов это не требуется, т.к. обычно они вносят в НТМL-элементы не
большие и специализированные изменения. Стандартная реализация ProcessAsync ()
в любом случае вызывает метод Process () . Пример асинхронного дескрипторного
вспомогательного класса приведен в главе 24.
Имя Описание
<inpu t c l a s s= " form - cont r ol " asp - fo r="P opu latio n" / >
output .Att rib ut es.SetAtt r ibute (" cl ass", $" btn Ьtn- ( BsButtonColor}" ) ;
Генерирование вывода
Метод Р r о се ss ( ) трансформирует элемент путем конфигурирования объ
екта Tag He lpe rOu tp u t, который получается в качеств е аргу м ента. Объект
TagHelpe r Ouput начинает сво е существовани е с описания НТМL-элемента в то м
виде, как он появляется в представлении Razor, и модифицирует его посредством
свойств и метода, которые описаны в табл. 23.4.
Имя Описание
PreEl ement Это свойство возвращает объект TagHe lperContext, который при
меняется для вставки содержимого в представление перед вы ходным
PreCont ent Это свойство возвращает объект TagHe l perContext, который при
меняется для вставки содержимого перед содерж имым выходного
элемента. См. раздел "Вставка содержимого перед и после элемен
тов" далее в главе
Pos t Content Это свойство возвращает объект TagHe l perContext, который ис
пользуется для вставки содержимого после содерж имого вы ходного
Population:
l."--·-·--·--· ----"·-·-·-- -·
Рис. 23.2.
·-
Применение дескрипторного вспомогательного класса для стилизации кнопки
Population:
Reset
Имя Описание
Attributes Это свойство используется для указания на то, что дескрипторный вспо
могательный класс должен применяться только к элементам, которые
имеют заданный набор атрибутов, предоставляемый в виде списка с раз
делителями-запятыми. Элемент должен располагать всеми указанными
атрибутами. Имя атрибута, которое заканчивается символом звездочки,
будет трактоваться подобно префиксу, так что bs-button-* соответ
ствует bs-button-color, bs-button-size и т.д.
ParentTag Это свойство используется для указания на то, что дескрипторный вспо
могательный класс должен применяться только к элементам, которые
содержатся внутри элемента заданного типа
Population:
В листинге 23.16 для атрибута Htm l TargetE l emen t тип эл ем ен та не указан, а по
тому дескрипторный вспомогательный класс будет применяться к любому элементу ,
который имеет атрибут bs - b u tto n- co lo r , независимо от типа эл е мента . В листин
ге 23.17 элемент а внутри формы модифицирован так , что он использует такой же
набор стилей Bootstrap, как и элементы b u t ton, поэтому он будет трансформирован
дескрипторным вспомогательным классом .
< / div>
<div class= 11 form - g r oup 11 >
<labe l for= 11 Pop u la t ion 11 >Populat i on : </label>
<input c l ass= 11 form - cont r o l 11 name= 11 Popu l ation 11 />
< /d i v >
<buttoп type= 11 s ubmi t " bs - button - c ol or = da n ger >Add</buttoп>
11 11
пр едоставляют.
Совет. Обратите внимание в листинге 23.20 на установку атрибута type выходного элемен
та . Причина в том , что любой атрибут, для которого имеется свойство , определенное де
с к рипторным вспомогательным классом, в выходном элементе опускается . Обычно это
хорошая идея, т.к . предотвращает появление атрибутов , применяемых для конфигуриро
вания дескрипторных вспомогательных классов, в НТМL-разметке, отправляемой брау
зеру. Тем не менее, в данном случае атрибут type используется для конфигурирования
дескрипторного вспомогательного класса, поэтому необходимо, чтобы он присутствовал
также в выходном элементе.
Установка сво йств а TagName важна из-за того, что выходной элемент по умолча
нию записывается в таком же стил е, как специальный элеме нт. В листинге 23.19 при
менялся самозакрывающийся дескриптор:
<f ormb utto n t ype= "submit " bg- col or= "da nger " />
<f ormbutto n type= " reset " />
трансформировались следующим образом:
<bu t ton clas s = " Ьt n Ьt n -da n ge r" type= " s ubmi t" >Add</bu tton>
<butt on c l as s = " Ьtn Ьtn -prim a r y " t ype= "reset " >Reset</b utton>
содержимое (табл. 23.7). Далее будет показано. как добавлять содержимое вокруг и
внутрь целевого элемента.
Имя Описание
Cities
Nвmв
London
New York
San Jose
. . ·
-
UK
USA
USA
. Populвtion
8,539,000
8,406,000
998,537
-
Paris France 2,244,000
1 Cities
L. _ ---·-·. ------
Рис. 23.5. Вставка НТМL-элементов с помощью дескрипторного вспомогательного класса
Глава 23. Дескрипторные вспомогательные классы 731
</tbody>
</tаЫе>
<а href="/Home/Create" class="Ьtn Ьtn-primary">Create</a>
732 Часть 11. Подробные сведения об инфраструктуре ASP.NET Соге MVC
<tr>
<td wrap><b><i>London</i></b></td>
<td>UK</td>
<td class= " text - r i ght " >B , 539 , 000</td>
</tr>
Совет. Обратите внимание, что атрибут wrap был оставлен на выходном элементе. Причина
в том, что в дескрипторном вспомогательном классе не было определено свойство, соот
ветствующее упомянутому атрибуту. Если вы хотите предотвратить попадание атрибутов
в вывод , тогда определите для них свойства в дескрипторном вспомогательном классе ,
даже когда не собираетесь применять их значения.
главах 24 и 25, является трансформация элементов так , что они содержат детали теку
щего запроса или текущей модели представления. Добавьте в папку Infrastructure/
TagHelpers файл класса по имени FormTagHe lper. cs , содержимое которого приве
дено в листинге 23.25.
Листинг 23.25. Содержимое файла FormTagHelper. cs
из папки Infrastructure/TagHe lpers
using Microsoft . AspNetCore . Mvc ;
using Microsoft . AspNetCore . Mvc . Rende ring ;
using Microsoft . AspNetCore . Mvc . Routing ;
using Microsoft .AspNetCore . Mvc . ViewFea tures ;
using Microsoft . AspNetCore.Razor .T agHelpers ;
namespace Cities.Infrastructure . TagHe lpe rs {
puЫic class FormTagHelper : TagHe l per {
private IUrlHelperFactory urlHelperFact ory ;
puЫic FormTagHelper(IUrlHelperFactory factory)
urlHelperFac t ory = factory ;
[ViewContext]
[HtmlAttributeNotBound]
puЬlic ViewContext ViewContextData get; set ; }
puЫic str ing Controller { get ; set;
puЫic string Action { get ; set ; }
puЬl i c override void Process(TagHelperContext context ,
TagHelperOu t put output) {
Глава 23. Дескрипторные вспомогательные классы 733
I UrlHe lpe r ur l Helper = u rl Helpe rFactory.Ge t Ur lHe l per(ViewContext Data) ;
ou tput.A tt rib ut es.SetAt t ribu t e("action", urlHelp e r.Act ion(
Ac t ion ??
Vi e wC ontextData.Ro u teData . Val ues[" ac ti on "] .T oS t ring() ,
Cont r oller ??
Vi e wContextDat a.Ro u t eDa ta . Va lue s [ " co n t r o ll e r"] .ToSt ri ng ())) ;
[Vi.ewContext]
[HtmlAttributeNotBound]
puЫ i c Vie wCo n text ViewCon te xtDa ta get; s e t ; }
Свойство Name возвращает имя свойства модели. Вторая возможность свя з ана с
получением типа свойства модели, что позволяет изменить значение атрибута type
элементов i nput :
мента div .
Второй дескрипторный вспомогательный :класс, ButtonThemeTagHelper, опери
рует на элементах but ton и а, находящихся внутри элемента di v. Он использует зна
чение атрибута theme из словаря Items, чтобы установить стиль Bootstгap для своего
выходного элемента. В листинге 23.30 приведен набор элементов, к которым будут
применены эти дескрипторные вспомогательные классы.
Запустив приложение и запросив URL вида / Home/C rea te , вы заметите, что груп
па кнопок стилизована одинаково. Если вы замените значение атрибута theme эле
м ента di v друго й настройкой темы Bootstrap. такой как info , danger или primary,
и затем перезагрузите страницу , то увидите , что изменение отразилось в стилях кно
•Е+Н'
~.. -. - J
1
:";
t J.P"·
<!DOCTY PE html>
<html >
<he a d >
<meta name= " viewpo r t " content= " wi dt h=device - wi d t h " />
<t i tle>C i t i es</t i t le >
<l i nk hr ef= " /li Ь/bootstrap/dist/ c ss /bootstrap . css " rel= " st yl es he et " />
</ head>
Глава 23. Дескрипторные вспомогательные классы 739
<body class="panel-body">
<div show-for-action="Index" class="panel-Ьody Ьg-danger">
<h2>Important Message</h2>
</div>
<div title="Cities">@RenderBody()</div>
</body>
</html>
. - --·· х
1
··~ -~ -~ lo_.:all ~st _:~::::.'~~~~':cle~ -·-\ ~- ----' С' 1.J focall1ost:SS652/Home/Cr~at"
Резюме
В этой главе рассматривалось использование дескрипторнъrх вспомогательных
классов, которые являются новым дополнением инфраструктуры ASP.NET Core MVC.
Была объяснена роль, которую они играют в представлении Razor, а также продемонс
трировано создание, регистрация и применение специальньrх дескрипторньrх вспо
вспомогательных
и
нфраструктура MVC предлагает набор встроенных дескрипторных вспомога
тельных кла ссов, кот орые используют ся для выполнения часто требующих
ся тр а нсфор ма ц ий в отнош ении НТМL- элементов . В этой глав е рас сматриваются
де скрипторны е в сп о м огат ельны е клас сы, оперирующие на НТМL- фор м ах, которые
пр едн аз н ач е ны дл я эл ементов form , i nput, label , select , option и textarea.
В гл а ве 25 будут опи са ны други е в с тро е нные де скрипторные вспо м огательные клас
с ы , н е относящие ся к форм ам . В табл . 24. l приведе на сводка, позволяющая пом е с
тить де скриптор ны е в спомогательные классы для форм в конт е кст.
Таблица 24. 1. Помеще ние дескрипторных вспомогательных классов для форм в контекст
Вопрос Ответ
классами.
<td class="text-right">@city.Population?.ToString("#,###")</td>
</tr>
</tbody>
</tаЫе>
<а href= " /Home/Create " c l ass= "Ьt n Ьtn-primary">Create</a>
D Cit", х D Citios Х
Namв:
Nam~ ·" ". · ' ' · еоuПtТУ ,,.·..,:'' .· -'.
'
·J\Jpulation
London UK 8,539,000 ,,
: •
L__ ____________________ J, 111!'*!1!_________ ---
1
asp- action Этот атрибут применяется, чтобы указать системе маршрутизации ме
тод действия для URL в атрибуте action. Если он опущен, тогда будет
использоваться действие, визуализирующее представление
ли стинг е 24.6.
Листинг 24.6. Проверка маркеров противодействия подделке в файле HomeController. cs
Листи нг 24.7. Включение средства противостояния атакам CSRF в файле Create. cshtml
@rnodel City
@{ Layout = " Layout "; }
<formmethod="post" asp-controller="Home" asp-action="Create"
asp-antiforgery="true">
<di v class= " forrn - group " >
<label for= "Narne " >Narne : </ l a bel>
<input clas s=" forrn - con t rol " narne=" Narne " />
</div>
<div class= " forrn - g r oup " >
<l abel for= "Country " >Coun t ry : </labe l >
<input class= " forrn - control " narne=" Country " />
</div>
<div class= " forrn- group " >
<labe l for= " Population " >Populat i on: </ l abe l >
<i np ut class= " forrn - control " narne=" Popu l a t ion " />
</div>
<button type= " subrnit " c l ass= "btn btn - pr i rnary " >Add</button>
<а class= "btn btn - prirnary " href=" / Horne/Index " >Cancel</a>
</forrn>
asp - for Этот атрибут применяется для указания свойства модели представления,
которое олицетворяет элемент i nput
asp-format Этот атрибут используется для указания формата, применяемого при отоб
ражении значения свойства модели представления, которое олицетворяет
элемент inpu t
В табл . 24.5 опис ан способ примен ения различных типов св ойств С# для уст ано в
ки атрибута type элем е нтов input .
bool checkЬox
табл. 24.6.
@model City
@{ Layout = " Layout"; }
<form method="post" asp-controller="Home" asp - action="Create"
asp-antiforgery="true">
<div class="form-g roup">
<label for="Name " >Name : </label>
<input class="form - control " asp-for="Name" />
</div>
<div class="form- group " >
<label for="Country " >Country : </label>
<input class="form - control " asp-for="Country" />
</div>
Глава 24. Использование дескрипторны х вспомогательных классов для форм 753
<div class= " form - group">
<label for="Population " >Population : </label>
<input class="form-control" asp-fo="Population" asp-format="{O:#,###}" />
</div>
<button type= " submit " class= " btn btn - primary " >Add</button>
<а class= " btn btn - primary" href="/Home/Index">Cancel</a>
</form>
using System.Componentмodel.DataAnnotations;
namespace Cities.Models {
puЫic class City {
puЫic string Name { get; set; }
puЫic string Country { get ; set;
[DisplayFormat(DataFormatString = "{0:F2}", ApplyFormatinEditмode = true)]
puЫic int? Population { get; set; }
754 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Соге MVC
Атрибут asp - format имеет преимущество перед атрибуто м Disp l ayForma t, поэ
тому он удален из пр едставл ения (листинг 24.12).
Листинг 24.12. Удаление атрибута форматирования в файле Crea te. csh trnl
@mod el City
@{ La yout = " Layout "; }
<fo r m method= " post " asp - control l er= " Home " asp - action= " Create "
asp - ant i forger y = " true " >
<di v class= " fo r m- group " >
<l abe l for= " Name " >Name : </label>
<input class= " form - control " asp - for= " Name " />
</ di v >
<div c lass= " form - group " >
<l a bel for= "C ountry " >Country : </ l ab e l>
<i nput clas s = " form - control " asp - for = " Coun try" />
</di v >
<div c lass= " form - group " >
<label for= " Popu l ation " >Popu l ation : </ l abel>
<input class="form-control" asp-for="Population" />
</div>
<butt o n type= " s ubmit " class= " Ьtn Ьtn - primary " >Add < /button>
<а cl a ss = " Ьtn Ьtn - primary " href= " /Home/Index " >Cancel</a>
< /form >
<input class= " form - contro l" type =" number " i d =" Population "
name =" Populatio n" value =" 8539000 . 00 " />
asp- for Этот атрибут применяется для указания свойства модели представления,
которое олицетворяет элемент label
Дескрипторный вспомогат ельный класс будет использовать имя сво йства модели
представления для установки значения атрибута for и содержимого элемента label .
В листинге 24. 13 атрибут asp - for применяется к элементам label формы, которы е
будут трансформированы дескрипторным вспомогательным классом.
Глава 24. Использование дескрипторных вспо мо гательных классов для форм 755
Листинг 24. 13. Использование дескрипторного вспомогательного класса
в файле Create.cshtml
@model City
@{ Layout = " Layout "; }
<form method= " post " asp - controller="Home " asp - action= " Create "
asp - antiforgery= "t rue">
<div class= " form - group">
<laЬel asp-for="Name"></laЬel>
<input class= " form-control " asp-for="Name" />
</div>
<div class = " form-group " >
<laЬel asp-for="Country"></laЬel>
<input class= "f orm- contro l" asp - for= " Country " />
</div>
<div class= " form-group " >
<label asp-for="Population"></laЬel>
<input class= " form - control " asp-for="Population" />
</div>
<button type= " submit " class= " btn btn - p rimary " >Add</button>
<а class= " Ьtn Ьtn - primary " h ref= " /Home/ In dex " >Cancel</a>
</form>
<form method= " post " action ="/H ome/ Create" >
<div class= " form-group " >
<label for="Name">Name</laЬel>
<i nput class= "f orm- control " typ e= "text" id="Name"
name= " Name " val u e= "London " />
</div>
<div class= " form - group " >
<laЬel for="Country">Country</laЬel>
<input class= " form - control " type= " text" id= " Country "
name= " Country " va lu e ="U K" />
</div>
<div class= "form - group " >
<laЬel for="Population">Population</laЬel>
<input class= " form - control " type= "number " id="Population"
name= " Population" va l ue= " 8539000 . 00 " />
</div>
<button type= " submit " class= "Ьtn Ьtn - pr im ary " >Add</button>
<а class= " Ьtn Ьtn -pr ima ry" hr ef= " /Home/ Inde x " >Cancel</a>
</form>
Переопределить значени е, используемое как содержимое элемента label, можно
Аргум ент Name указыв ает знач е ние для испол ь з ования вм е сто им е ни сво й с
тв а . Запустив приложение , запросив URL вида /Home/Create и заглянув в НТМL
разметку , которая отправлена браузеру , вы обнаружите, что содерж и м ое элемент а
label и з менилось:
Имя Описание
asp- for Этот атрибут используется для указания свойства модели представления,
которое олицетворяет элемент select
a s p - iterns Этот атрибут применяется, чтобы указать источни к значений для элемен
тов option, содержащихся внутри элемента se l ect
Глава 24. Использование дескрипторных вспомогательных классов для форм 757
Атрибут asp-for устанавливает значения атрибутов for и id, чтобы отразить
свойство модели, которое он получает. В листинге 24.15 элемент inpu t для свойства
Country заменен элементом select , определяющим атрибут asp -fo r .
Листинг 24.15. Использование элемента select в файле Crea te. csh tml
@model City
@{ Layout = "_Layout "; }
<form method= "post " asp-controller= "Home" asp-action="Create"
asp - antiforgery= " true " >
<div class= " form - group " >
<label asp - for= " Name " ></label>
<input class= "form- control " asp -fo r="Name" />
</div>
<div class= " form -g roup ">
<label asp-for="Country"></label>
<select class="form-control" asp-for="Country">
<opti.on di.saЬl.ed sel.ected val. ue=" ">Sel.ect а Coun try</ opti.on>
<option>UK</option>
<opti.on>USA</opti.on>
<option>France</option>
<option>China</option>
</select>
</div>
<div class= " form-group " >
<label asp - for= " Population " ></label>
<input class= " form-control" asp - for =" Population" />
</div>
<button type =" submit " class= "Ьtn Ьtn-primary">Add</button>
<а class= "btn Ьtn - primary " href=" /Home/Index">Cancel</a>
</form>
using Microsoft.AspNetCore.Mvc ;
using Cities . Models;
using System . Linq ;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace Cities.Controllers (
puЬlic class HorneController : Controller
private IRepository repository;
puЬlic HomeController ( IReposi tory repo) {
repository = repo;
760 Часть 11. Подробные сведения об и нфраструктуре ASP.NET Core MVC
[HttpPost ]
[ValidateAntiForgeryToken]
puЫic IActionResult Create(City city)
reposi t ory . AddCity(city) ;
return RedirectToAction( "Index " ) ;
Листинг 24. 19. Применение объекта ViewBag для заполнения элементов option
в файле Crea te . csh tml
@model Ci ty
@{ Layou t = " Layout "; }
< f orm method= "post " asp - control l er= " Home " a s p - action= " Create "
asp - antiforgery= " true " >
<div class= " form-gro u p " >
<labe l asp - for= " Name " ></ l abel>
<i nput c l ass= " form - control " a s p - for= " Name " />
</div>
<div class= " form - group" >
<labe l asp - for= " Country " ></ l abel>
<select class="form-control" asp-for="Country"
asp-items="ViewBag.Countries">
<option disaЬled selected value= "" >Se l ect а Co un try</opt i on>
</se l ect>
</div>
<div class= " form - group " >
<label asp - for= " Population " ></ l abe l >
<input class= " form - contro l" asp -for= " Popu l ation " />
</div>
<button type= " submit " class= " Ьtn Ьtn - pr im ary " >Add</button>
<а c l ass= " Ьtn Ьtn - pri mary " href= " /Home/ Index " >Ca n cel</a>
</form>
Глава 24. Исnользование дескриnторных всnомогательных классов для форм 761
Запустив приложение и запросив URL вида/Home/Createor /Home/Edi t, вы об
наружите, что были созданы такие элементы option:
<select class="form- control" id="Country " name="Country">
<op t ion disaЫed selected value="">Select а Country</option>
<option selected>UK</option>
<option>USA</option>
<option>France</option>
</s e l e c t >
using System;
using System . Collections.Generic;
using System .L inq;
u sing System.Reflection ;
using System.Threading . Tasks;
using Cities . Models;
u sing Micro s oft . AspNetCore.Mvc . Rendering;
u sing Microsoft.AspNetCore.Mvc.TagHelpers;
u sing Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore . Razor.TagHelpers;
namespace Cities . Infrastructure.TagHelpers {
(HtmlTargetElement("select", Attributes = " model -for")]
puЫic class SelectOptionTagHelper : TagHelper {
private I Repository repository;
puЬlic SelectOptionTagHelper(IRepository repo)
repository = repo;
@model City
@addTagHelper Cities.Infrastructure.TagHelpers.SelectOptionTagHelper, Cities
@{ Layou t = " Layout "; }
<form method= " post " asp - contro l ler=" Home " asp - action= " Create "
asp - antiforgery= " true " >
<div class= " form - group " >
<label asp - for =" Name " ></ l abel>
<input class =" form - control " asp - for =" Name " />
</div>
<div class= " form - group " >
<label asp - for= " Country " ></label>
<select class="form-control" asp-for="Country"
asp-items="ViewBag.Countries">
<option disaЫed selected valu e = "" >Select а Country</option>
</select>
</div>
Глава 24. Использование дескрипторных вспомогательных классов для форм 763
<div class= " form - group " >
<label asp-for="Population"></label>
<input class= " form-cont rol " asp-for="Population" />
</div>
<button type= " submit " c l ass= " Ьtn Ьtn -p rimary " >Add</button>
<а class= "btn btn-primary " href= " /Home/Index ">Cancel</a>
</form>
Запустив приложение и запросив URL вида /Ноте/ Crea te или / Н оте/ Edi t . вы
заметите, что НТМL-разметка, отправленная браузеру, включает следующий элемент
textarea:
Имя Описание
Рез ю м е
В данной главе были описаны встро енные дескрипторные вспомогательные клас
сы, которые применяются для трансформации элементов НТМL-форм . Эти дескрип
торные вспомогательные классы гарантируют, что формы генерируются напрямую из
класса модели, сокращая возможности возникновения ошибок и предлагая согласо
ванный подход к написанию представлений Razor. В следующей главе рассматрива
ются оставшиеся встроенные дескрипторные вспомогательные классы , которы е име
десв:рипторных
вспомогательных Rлассов
тов изображений и подде рживают ке ширование данных . Вдобавок будет описан де
скрипторный вспомогательный класс, обеспечивающий доступ для URL, относитель
ных к приложению, которые помогают гарантировать, что браузеры могут обращаться
к статическому соде ржимо му, когда приложение развернуто в среде, разделяемой с
другим и приложениями. В табл. 25.1 приведена сводка для настоящей главы.
Файл изображения ci ty . png входит в состав кода примеров для книги, доступ
ного для загрузки на веб-сайте издательства. При желании м ож ете воспользоваться
собственным изображением .
Еще одно изменение, которое потребуется сделать, связано с добавлением в проект
пакетаjQuегу (листинг 25.1). Щелкните на кнопке Show All Files (Показать все файлы)
в верхней части окна Solution Explorer, чтобы отобразился файл bower . j son.
~ J:!
· С LФ -i~~-;;~~7711н;;~;~~~.-------·--Q
·- - -- ·- -·--"'==·'"='·"'==--::-:===-с=~"==-·-----·-·11,
· POpulalion City
:1
l ondon uк 8 ,539.000 11
№1vYork USA
8,406.ООО :.·.•!
-
Paris France 2 ,244,000
Population
!\ Notes
1 ~ А
L-~-------- ;1 111•
"____,L_______________________J!
Рис. 25.2. Выполнение примера приложения
Использование вспомогательного
класса для среды размещения
Класс EnvironmentTagHelper применяется к специальному элементу environment
и определяет, включается ли область содержимого в НТМL-разметку, отправляемую
браузеру, на основе среды размещения, как было показано в главе 14. Это может не
казаться самым захватывающим местом, чтобы начинать с него, но данный вспомога
тельный класс необходим для эффективного использования ряда связанных средств,
которые будут описаны позже. Элемент environment полагается на атрибут names,
который описан в табл. 25.2 для ссьшки в будущем.
Имя Описание
names Этот атрибут применяется для указания списка разделенных запятыми имен
сред размещения, для которых содержимое, находящееся внутри элемента
1
! +- ·)> С . [J locallюs t:62495/Home/
,---··------·--------
!
1 Loodoo UK Loodon UK
New ,УШ/(
1
~ --·r
New York
"~
USA USA
r-#,__. ~·- -.#r...,.....,.~ .~
Использование вспомогательных
классов для JavaScript и CSS
Следующая категория встроенных вспомогательных классов применяется для уп
равления файлами JavaScrlpt и таблицами стилей CSS посредством элементов script
и link, которые обычно вюrючаются в разделяемую компоновку. Как будет показано
в последующих разделах, эти дескрипторные вспомогательные классы являются мощ
Имя Описание
asp- src- exclude Этот атрибут применяется для указания файлов JavaScript,
которые будут исключены из представления
asp- append- version Этот атрибут используется для аннулирования кеша, как
описано во врезке " Понятие аннулирования кеша" далее
в главе
asp- fallback-s rc- exclude Этот атрибут применяется для исключения файлов
JavaScript, чтобы предотвратить их использование в случае
возникновения проблемы с сетью доставки содержимого
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width " />
<title>Cities</title>
<script asp-src-include="/lib/jquery/dist/**/*.js"></script>
<link href = " /liЬ/bootstrap/dist/css/bootstrap.css" rel="stylesheet" />
</head>
<body clas s ="panel-body">
<div>@RenderBody()</div>
</body>
</html>
<h ead>
<meta name="viewport" content= "width=device - width" />
<title>Cities</title>
<script src="/lib/jquery/dist/jquery.js"></script>
<script src="/lib/jquery/dist/jquery.min.js"></script>
<link href="/liЬ/bootstrap/dist/css/bootstrap.css" rel="stylesheet" />
</head>
Класс ScriptTagHelper генерирует элемент
script для каждого файла, имя кото
рого соответствует шаблону, указанному в атрибуте
asp - src-include. Вместо выбора
только файла jquery . js имеется также элемент script для файла jquery.min . j s.
который является минифицированной версией файла j query . j s и будет использо
ваться большинством приложений, поскольку он содержит тот же самый код, выра
женный в более компактной, но менее читабельной манере. Вы можете даже не подоз
ревать, что дистрибутив jQuery вЮiючает минифицированный файл, т.к. Visual Studio
его по умолчанию скрывает. Чтобы отобразить полное содержимое папки wwwroot/
liЬ/jquery/dist, понадобится раскрыть элемент jquery. j s в окне Solution Explorer
и затем сделать то же самое с элементом j query. min. j s (рис. 25.4).
772 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Саге MVC
Файл j q u ery . min . map является отображением на исходный код , которое рядом браузе
ров используется для того, чтобы помочь в отладке минифицированного кода , предоставляя
соответствие между минифицированным кодом и читабельным полным исходным кодом.
<div>@RenderBody()</div>
</body>
</html >
Статическое содержимое, такое как изображения, таблицы стилей CSS и файлы JavaScript,
часто кешируется, чтобы предотвратить попадание на серверы приложений запросов к
редко изменяющемуся содержимому. Кеширование делается разными способами: браузер
может сообщить серверу о необходимости кеширования содержимого, приложение может
использовать серверы кешей как дополнение к серверам приложений или содержимое мо
жет распространяться с использованием сети доставки содержимого. Не все кеширование
будет находиться под вашим контролем. Например, в крупных корпорациях кеши часто ус
танавливаются для снижения требований к полосе пропускания, поскольку значительный
процент запросов направляется к одним и тем же сайтам или приложениям.
Упомянутая проблема решается с помощью аннулирования кеша. Идея состоит в том, чтобы
позволить кешам обрабатывать статическое содержимое, но незамедлительно отражать лю
бые изменения, которые произошли на сервере. Дескрипторные вспомогательные классы
поддерживают аннулирование кеша за счет добавления к URL для статического содержимо
го строки запроса , которая включает контрольную сумму, действующую в качестве номера
версии. Скажем , для файлов JavaScript класс ScriptTagHelper поддерживает аннулиро
вание кеша через атрибут asp - append- version :
Указание CDN означает, что никакие запросы для jQuery не будут попадать на сер
веры приложения . Пробл ема с с етями CDN связана с тем, что они не находятся под
контролем вашей организации , т.е. они могут отказывать, ост авляя приложение фун
кционирующим, но неспособным работать ожидаемым образом из- за недоступности
содержимого CDN. Чтобы решить проблему, JUiacc Sc ri pt TagHelper предлагает воз
можность обратиться за помощью к локальным файлам, когда клиент не может загру
зить содержимое из CDN (листинг 25.8).
Глава 25. Использование други х встроенных дескрипторны х вспомогательных классов 777
Листинг 25.8. Использование обхода загрузки из CDN в файле _Layout. cshtml
<!DOCTYPE html>
<html>
<head>
<meta name="view.port " content= " width=device - width" />
<tit le >Cit ies< /tit le>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.2.4.min.js"
asp-fallback-src-include="/lib/jquery/dist/**/*.min.js"
asp-fallback-test="window.jQuery">
</script>
<link href= " / liЬ/bootstrap/dist/css/bootstrap. css" rel="stylesheet " />
</head>
<body class ="panel-body">
<div>@RenderBody()</div>
</body>
</html>
Внимание! Средство обхода CDN полагается на то, что браузеры загружают и выполняют
содержимое элементов script синхронным образом в порядке, в котором они опре
делены. Существует несколько приемов ускорения загрузки и выполнения сценариев
JavaScript за счет того, что они делают процесс асинхронным. Однако такой подход мо
жет привести к тому, что проверка обхода выполнится перед тем, как браузер извлечет
файл из CDN и выполнит его содержимое, в результате порождая запросы для запасных
файлов даже при нормальной работе CDN и ставя под сомнение вообще использование
CDN . Не смешивайте асинхронную загрузку сценариев со средством обхода CDN.
Здесь должно уделяться аналогичное внимание деталям, как и при выборе файлов
JavaScript, потому что довольно легко сгенерировать элементы link для нескольких
версий того же самого файла или файлов, которые не нужны. Чтобы управлять выби
раемыми файлами, можно следовать тем же трем подходам , которые были описаны
для файлов JavaScript в предыдущем разделе: сужать шаблон универсализации имен,
исключать файлы с применением атрибута asp - href - exclude и использовать эле
мент env ironment для выбора между дублирующимися наборами файлов.
<!DOCTYPE htrnl>
<htrnl>
<head>
<rneta narne= "viewport " content= "wid th=device-width " />
<t i t le >C i tie s</title >
<link href=
"https://maxcdn.bootstrapcdn . com/bootstrap/3.3.6/css/bootstrap . min.css"
asp-fallback-href-include="/lib/bootstrap/dist/**/*.min.css"
asp-fallback-test-class="btn"
asp-fallback-test-property="display"
asp-fallback-test-value="inline-Ыock"
rel= "s tylesheet " />
</head>
<body class= "panel-body " >
<di v>@RenderBody() </div>
</body>
</htrnl>
Совет. Простейший способ понять, каким образом проводить проверку для сторонних паке
тов вроде Bootstrap, предполагает применение инструментов <F12> браузера. Чтобы оп
ределить тест в листинге 25. 1О, элементу назначается класс Ьtn , после чего в браузере
инспектируются индивидуальные свойства CSS, которые этот класс изменил. Такой прием
проще попытки просмотра длинных и сложных таблиц стилей.
Глава 25. Использование других встроенных дескрипторных вспомогательных классов 781
Имя Описание
asp- action Этот атрибут указывает метод действия, на который будет нацелен URL
asp - con tro lle r Этот атрибут указывает контроллер , на который будет нацелен URL
a sp - area Этот атрибут указывает область, на которую будет нацелен URL
a sp - fragme n t Этот атрибут используется для указания фрагмента URL
(который находится после символа # )
asp - host Этот атрибут указывает имя хоста, на который будет нацелен URL
asp- protocol Этот атрибут указывает протокол, который будет применять URL
asp- route Этот атрибут указывает имя маршрута, который будет использоваться
для генерации URL
asp - route - * Атрибуты с именами, начинающимися на asp- rou t e -, применяются
для указания дополнительных значений, относящихся к URL, так что
атрибут a sp- rout e-id используется для предоставления системе
маршрутизации значения сегмента id
</tbody>
</tаЫе>
<а asp-action="Create" class="Ьtn Ьtn-primary">Create</a>
Запустив прилож ение и запросив URL вида /Horne/ I ndex , вы з а м етите , что де
скрипторный вспомогат ельный класс трансформировал эл еме нт а следующим
образом:
для изображений через атрибут src элементов img, давая приложению возможность
применять в своих интересах к е ширование и гар а нтируя н е м е дл е нное отр аж е ние
asp- append - vers i on Этот атрибут используется для включения аннулирования кеша,
как объяснялось во врезке "Понятие аннулирования кеша"
ранее в главе
<img src=
" /images/city . png ?v=KaМNDSZFЬzNpE8PkЬЗOEXcAJufRcRDpKhOK_IIPNc7E" />
Глава 25. Использование других встроенных дескрипторных вспомогательных классов 783
Как и со средствами аннулирования кеша для файлов JavaScript и таблиц стилей
CSS, контрольная сумма, включенная в URL, остается неизменной до тех пор, пока
файл не будет модифицирован .
Имя Описание
enaЫ ed Этот атрибут типа bool используется для управления тем, будет
ли кешироваться содержимое элемента c ache . Отсутствие дан
ного атрибута означает включение кеширования
Ti meSpan
vary- by- heade r Этот атрибут применяется для указания имени заголовка за
проса, который будет использоваться при управлении разными
версиями кешированного содержимого
vary-by- query Этот атрибут применяется для указания имени ключа строки за
проса, который будет использоваться при управлении разными
версиями кешированного содержимого
784 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
vary- by- cookie Этот атрибут применяется для указания имени сооkiе-набора ,
который будет использоваться при управлении разными версия
ми кешированного содержимого
vary-by-user Этот атрибут типа bool применяется для указания, будет ли имя
аутентифицированного пользователя использоваться при управ
лении разными версиями кешированного содержимого
содержимого
Чтобы посмотреть, как работают атрибуты кеша, создайте папку Cornponents, до
бавьте в нее файл класса по имени TirneViewCornponent. cs и определите компонент
представления, приведенный в листинге 25. 13.
Листинг 25.13. Содержимое файла TimeViewComponen t. cs из папки Componen ts
using Systern;
using Microsoft.AspNetCore.Mvc ;
namespace Cities.Components {
puЬlic class TirneViewComponent : ViewCornponent
puЫic IViewComponentResult Invoke()
return View(DateTime.Now) ;
@rnodel DateTime
<div class= "panel -b ody bg-info">
Rendered at @Model . ToString ( "НН: rnm: ss")
</div>
Глава 25. Использование других встроенных дескрипторны х вспомогательных классов 785
Объект модели DateTime применяется для отображения текущего времени с точ
ностью до секунды. В листинге 25.1 5 элемент img, добавленный в предыдущем разде
ле, заменяется выражением @await Component . InvokeAsync, которое обращается к
компоненту представления.
London UK 8.539.000
New York USA
New York USA 8,406.000
Sa11 Jose USA
Sал Jose USA 998,537
Paris France
. ! Paris France 2,244.000
1 !
l lВI
1
!
1
L--·----------·-··---··--t
Рис. 25.6. Отображение времени в примере приложения
Совет. Применяемый классом CacheTagHelper кеш основан на памяти, т.е. его емкость
ограничивается доступным объемом ОЗУ. При нехватке пространства содержимое из
кеша начинает удаляться, а в случае останова или перезапуска приложения все содер
дующих 15 секунд.
Здесь указано , что данные должны кешироваться до 2100 года. Это не особенно
полезная стратегия кеширования, поскольку приложение наверняка будет перезапу
щено до того, как начнется следующее столетие, но иллюстрирует способ указания
фиксированного момента в будущем вместо выражения времени истечения относи
тельно момента, когда содержимое попадает в кеш.
классов .
а href
applet archive
area href
audio src
base href
Ьlockquote cite
button formaction
del cite
emЬed src
form action
html manif est
iframe src
img src, srcset
input src, formaction
ins cite
link href
menuitem icon
object archive,data
q cite
script src
source src, srcset
track src
video src, poster
Резюме
В настоящей главе рассматривались встроенные дескрипторные вспомогательные
классы, которые не связаны с НТМL-формами . Эти дескрипторные вспомогательные
классы помогают управлять доступом к файлам JavaScript и таблицам стилей CSS,
создавая URL для якорных элементов, выполняя аннулирование кеша для изображе
ний, кешируя данные и трансформируя URL, относительные к приложению . В сле
дующей главе будет представлена система привязки моделей, которая используется
для обработки данных в НТТР-запросах, так что их можно легко потреблять внутри
приложения МVС .
ГЛАВА 26
Привязка моделей
п
ривязка моделей - это процесс создания объектов .NET с использованием дан
ных из НТГР-запроса для снабжения методов действий аргументами, в которых
они нуждаются. В настоящей главе будет описана работа системы привя зки моделей,
показано , как она привязывает простые типы, сложные типы и коллекции. а также
продемонстрировано, каким образом взять на себя контроль над проце ссом, чтобы
указывать часть запроса, которая предоставляет значения данных, требующиеся ме
тодам действий. В табл. 26.1 прив едена свод1\а, позволяющая пом естить привязку
моделей в контекст.
Вопрос Ответ
Что это такое? Привязка моделей представляет собой процесс создания объектов ,
которые требуются методам действий в качестве аргументов, с приме
нением значени й данных, получаемых из НТТР-запроса
Чем она полезна? Привязка моделей позволяет методам действий объявлять параметры,
используя типы С#, и автоматически получать данные из запроса без не
обходимости в инспектировании, разборе и обработке данных напрямую
Как она В своей простейшей форме методы действий объявляют параметры, имена
используются? которы х применяются дпя извлечения значений данных из НТТР-запроса.
Часть запроса, используемая для получения данных, может быть сконфигу
рирована за счет применения атрибутов к параметрам методов действий
структурирована
} 1
}'
"buildOptions":
" emitEntryPoint ": true, " preserveCompilationContext ": true
}'
"runtimeOptions": {
" configProperties ": { " System . GC.Server ": true}
using System ;
namespace MvcMode l s . Models
puЬlic class Person {
puЫic int Personld { get ; set ; }
puЫic string FirstName { get ; set;
puЫic string LastName { get ; set; }
puЫic DateTime BirthDate { get ; set ; }
set {
pe ople[i d ] val ue ;
В интерф ейсе IReposi tory определено свойство Peop l e для извлечения всех объ
ектов модели и индексатор, который позволяет извлекать или сохранять отдельные
объекты Person. Кл асс MemoryReposi t ory реализует этот интерфейс и прим еняет
словарь со стандартным содержимым . Реализация хранилища не поддерживает пос
тоянство, так что после останова или перезапуска состояние приложения во з вр аща
@model Person
@{ Layout = " Layout"; )
<div class= " bg - primary panel -body "><h 2>Person</h2></div>
<tаЫе class= " taЫe taЬle - condensed taЬle-bordered taЫe - striped " >
<tr><th>Personid : </th><td>@Model . Personi d</td></tr>
<tr><th>First Name : </th><td>@Model . FirstName</td></tr>
<tr><th>Last Name : </th><td>@Model . LastName</td></tr>
<tr><th>Role : </th><td>@Mode l.Role</td></tr>
</tаЫе>
Конфигурирование приложения
В листинге 26.9 показан код класса Startup, который конфигурирует средства,
предоставляе мые пакетами NuGet, и настраивает службу хранилища.
J lcx:alhost:S1861/Home/lr Х
~ -+ С []
----'===:::
localhost:51861/Home/lndex/1 _______ '{::s , -__,
Personld:
First Name: В оЬ
/ Home / Index / 1
Инфраструктура MVC транслировала эту часть URL и применила ее в качест
ве аргумента, когда вызывала метод Index () контроллера Home, чтобы обслужить
запрос:
• переменные маршрутизации;
• строки запросов.
800 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
Совет. В разделе "Указание источника данных привязки моделей" далее в главе объясняет
ся, как можно указать источник данных привязки моделей с использованием атрибутов .
Это позволяет установить, что значение данных должно получаться, скажем, из строки
запроса, даже если подходящее значение также есть в данных формы или переменных
маршрутизации.
Знание порядка поиска значений данных важно из-за того, что запрос может со
держать несколько значений , например:
/ H om e/ Ind e x / З?id = l
Personld: 3
Personld:
First Name: Joe
First Name: ВоЬ
LastName: АЫе
Last Name: Smith
Role: Admin
1-·~-----~-l-~,:~~-'~~;~~~~~~:1:;;~~,::_,.:,===·==:=.:==-=-
IAn
1
Linhaпdl ed exception оссштеd \1Vhile processing the
request. ·
1
*l -.:_
1
~~~e~9l>~e~~~bl~~~:~~~1. a~~
Рис. 26.3. Ошибка обраб отки св о йства м одел и
Это означает, что м етоды действий должны быть написаны так, чтобы справиться
со стандартными знач ениями , предоставляемыми систе мой привя з ки м одел ей, что
может быть сделано несколькими способами. Можно добавить стандартные значе
ния в шаблоны URL маршрутов (как было описано в главе 15), присвоить стандар
тные значения параметрам методов действий или обесп еч ить , чтобы методы де й с
твий не передавали недопустимые значения данных в качестве части своих ответов .
Предпочтительный подход будет зависеть от того , что предприним ает метод действия;
в листинге 26.10 избран последний подход , который предусматривает изм е н е ние м е
тода действия так, что он всегда передает методу View ( ) объект Person, даже если
аргумент id не соответствует какому-то объекту в модели данных.
Стандартным значением для типов, допускающих null, является null. Оно поз
воляет проводить различие между запросами, не содержащими значение, которое
Чтобы посмотреть, как это работает, добавьте в контроллер Horne два метода действия
(листинг 26.12).
Листинг 26.12. Добавление методов действий в файле HomeController. cs
using Microsoft.AspNetCore.Mvc;
using MvcModels.Models;
namespace MvcModels.Controllers
puЫic class HorneController : Controller
private IRepository repository;
804 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
В ерсия метода
Create () без пар аметров создает новый объект Person и п ере
дает его методуView (), который выбирает стандартное представление, ассо цииро
ванное с действием. Добавьте в папку Views/Home файл пр едставления по имени
Create . cshtml и пом естите в н его разметку, прив еденную в листинге 26.13.
1~ Cr•at• Pe~on Х
1 r-·-------------
t-+- -+ _ С . : [J~call1ost:~~~:.~~:~~~~-::r-=-
Personld
l ocal host: S186 1 /Нomo/C Х
FirstName
Peter
LastName
Personld: 100
James First Name: Peter
1
~~'~-·--·----.. ~ ~:::№me
James
Guest
l--~~-~~----·
'--------
---_]
Рис . 26.4. Применение методов действий Crea te ()
using System ;
namespace MvcModels . Models
puЫic class Person {
puЫic int Personid { get ; set ; }
puЫic string FirstName { get; set;
puЬlic string LastName { get ; set; }
puЫic DateTime BirthDate { get; set ; )
puЫic Address HomeAddress { get; set; }
При поиске значения для свойства Linel связыватель модели ищет значение для
HomeAddress. Linel, т.к. имя свойства в объекте модели комбинируется с именем
свойства во вложенном типе модели.
@model Person
@{
ViewBag.Title = "Create Person";
Layout = " _Layout";
@model Person
@{ Layout = " Layout ";
<div class = " bg - primary panel - bo d y " ><h2>Person</h2></div>
808 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Core MVC
Запустив приложение и перейдя на URL вида / Home /Create , можно ввести значе
ния для свойств Ci ty и Country , после чего отправить форму и убедиться, что зна
чения были привязаны к объекту модели (рис . 26.5).
1 Creatt P~rson Х
Е- ~ СD localhost:51861 /Home/Create
---- - ·---- ----~ --~-------_,__::.__ ____
Personld
FirstName
+- ~ с iёi"i~;~ih;;-s~s18.611н;;~;ё;;;;t;-".---·-···-········ --:& · =1
John
LastName
_ _ _ _ _____
City City: Paris
Paris Country: France
Country г __J
Frar1ce
iiN 1
·---------··-____J
Рис. 26.5. Привязка свойств в сложных объектах
AddressSummary .
Чтобы исправить проблему, к параметру метода действия можно прим е нить ат
рибут Bind, в котором указывается префикс , подлежащий испол ьзованию во время
привязки модели (листинг 26.20).
r Cte.:ite Pt ( SOn х
1 ~ -+ С ·с;- locaih-;;~·-t-
:5-'18=6=1/=H=onie/Cr;;\;-
1--·---~----·----·-
· ··-·-·~-· ·-·--·-----·--'-"-------~---·
Personld
100
"l
t Dtspl&ySummг.ry
FirstName
John
" ~•lh~"s""/Homo/D~pl•,Som~ <:,
LastName
Pelers
City: Paris
Role
С~у
User
Country: France
_J
Paris
1 D»pl•ySumm•<y Х
City: Paris
Country:
using Microsoft.AspNetCore.Mvc;
namespace MvcModels . Models {
[Bind(nameof(City))]
puЫic class AddressSummary
puЫic string City { get ; set;
puЫic string Country { get; set;
Совет. Имеется также атрибут BindRequired, который сообщает процессу привязки моде
лей о том, что запрос должен включать значение для свойства. Ели запрос не содержит
требуемого значения, тогда возникает ошибка проверки достоверности модели, как будет
объясняться в главе 27.
Привязка массивов
@if (Model.Length == 0)
<form asp - action= " Names" method="post " >
@for (int i = О ; i < 3 ; i++ ) {
<div class= " form - group " >
<label>Name @(i + 1) :</ l abe l >
<input id= " names " name="names" class= " form - control " />
</div>
<bu t ton type =" submit" class= "b tn btn - primary " >Submit</button>
</form>
else {
<tаЫе c lass="t aЫe taЬl e- conden sed taЬle - bordered taЬle - striped " >
@foreach (strin g name i n Model) {
<tr><th>Name : </th><td>@name</ td ></t r >
</tаЫе>
<а a sp-a ctio n="Names " class= " btn btn-primary">Back</a>
имеют такое же имя, как у параметра метода действия. В настоящем примере это
означает, что все значения из элементов input с атрибутом name, установленным в
names, будут собраны вместе для создания массива и его использования в качестве
аргумента при вызове метода действия. Чтобы взглянуть на результат, запустите при
ложение, перейдите на URL вида /Home/Names и заполните форму. Отправив форму,
вы заметите, что отображаются все введенные значения (рис. 26.8).
1 +- ~ С D loca lhost:51861/Home/Names
1--:-m~-1:--·------IBNalo,l"llllBBll
·,1 N
::',. 1 <- ~ С ~6'oco1h;;;,;-i0i/Нo;;of";~::°' -=~ -
Joe Name. Joe
i 1 Nam~ Реtе г
NameЗ: ~
l -;;e ler_________ ------·J •· ·
L______ .
i
ifi' L____ ___·~~~---------~--~---------~
Привязка коллекций
Процесс привязки моделей способен создавать не только массивы . Он также
поддерживает классы коллекций. В листинге 26.26 тип параметра метода действия
Narnes () изменен на строго типизированный список.
816 Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC
Листинг 26. 27. Применение коллекции в качестве типа модели в файле Names . csh tml
@model IList<string>
@{
ViewBag.Title = "Names ";
Layout = "_Layout ";
<button type= "submit " class= "Ьtn Ьtn-pr ima ry " >Submit</button>
</form>
else {
<tаЫе class= " taЫe taЫe - condensed taЫe - bordered t aЫe - striped">
@foreach (string name in Model) {
<tr><t h>Name : </th><td>@name</td></t r>
</tаЫе>
<а asp-action= "Names " class= "btn btn- primary " >Back</a>
<button type= " submit " clas s ="b tn bt n-p rima r y " >Submi t</button>
</form>
818 Часть 11 . Подробные с ведения об и нфрастру ктуре ASP.NET Core MVC
else (
<tаЫе class= " taЫe taЫe - condensed taЫe - bordered taЫe - striped">
<tr><th>City</th><th>Country</th></tr>
@foreach (var address in Model) {
<tr><td>@address . City</td><td>@address . Country</td></tr>
</tаЫе>
<а asp - action="Address" c l ass= " btn btn - primary " >Back</a>
Пр едста вле ни е визуализи рует эле м ент form , е сли элеме нты в коллекци и м одели
отсут ствуют. Эл еме нт form с о стоит из пары элеме нтов input, атри бут ы name кото
рых имеют префи ксы в виде индексов м ассива :
<form method= " post " action= " /Home/Address " >
<fieldset class= " form - group " >
<legend>Address l</legend>
<div class= " form-group " >
<label>City : </label>
<input name="[O] .City" class="form-control" />
</div>
<div class= " form - group " >
<label>Country : </label>
<input name="[O] .Country" class="form-control" />
</div>
</fieldset>
<fieldset class= " form - group " >
<legend>Address 2</legend>
<div class= " form - group " >
<label>City:</label>
<input name=" [ 1] . Ci ty" class=" form-control" />
</div>
<div class= " form - group " >
<label>Country : </labe l >
<input name=" [1] .Country" class="form-control" />
</div>
</fieldset>
<fieldset class= " form - group " >
<legend>Address 3</legend>
<div class= " form - group " >
<label>City : </label>
<input name=" [2] .City" class="form-control" />
</div>
<div class= " form - group">
<label>Country:</label>
<input name=" [2] .Country" class="form-contr ol" />
</div>
</fieldset>
<button type= " submit " class= " Ьtn Ьtn - primary " >Submit</button>
</form>
Глава 26. Привязка моделей 819
Когда форма отправляется, связыватель модели выясняет, что необходимо создать
коллекцию объектов AddressSummary, и использует префиксы индексов массива в
атрибутах name, чтобы получить значения для свойств этих объектов. Свойства с
префиксом [О ] применяются для первого объекта AddressSumary, свойства с пре
фиксом [ 1] - для второго объекта AddressSumary и т.д.
Представление Address . cshtml определяет элементы input для трех таких
индексированных объектов и отображает их, если коллекция модели содержит эле
менты. Перед тем, как можно будет продемонстрировать это в работе, понадобится
удалить атрибут BindNever из класса модели AddressSummary, иначе связыватель
модели проигнорирует свойство Country (листинг 26.30).
Листинг 26.30. Удаление атрибута BindNever в файле AddressSurnmary. cs
1 .AddttSS Х
Address 1
City:
London
~-~ ::____с _D !.0 :·~~~~:~1:~у~~'::~/д!~'.':'~ ·- _ -~ "-- ='
1
1
Country: City Country
Paris Fraпce
Berlin Germany
1
Country: 1111
Germeny 1
L----------------~--------~
_ __________J
Рис. 26.9. Привязка коллекций специальных объектов
820 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
Имя Описание
FrornForrn Этот атрибут используется для выбора данных формы в качестве источника
данных привязки. По умолчанию для нахождения значения данных формы
применяется имя параметра, но такое поведение можно изменить с исполь
FrornQue r y Этот атрибут используется для выбора строки запроса в качестве источника
данных привязки. По умолчанию для нахождения значения строки запроса при
меняется имя параметра, но такое поведение можно изменить с использовани
FrornHeader Этот атрибут применяется для выбора заголовка запроса в качестве источ
ника данных привязки. По умолчанию имя параметра используется как имя
заголовка, но поведение можно изменить с применением свойства Narne,
которое позволяет указывать другое имя заголовка
FrornBody Этот атрибут используется для указания, что в качестве источника данных
привязки должно применяться тело запроса. Требуется в ситуациях , когда
из запросов необходимо получать данные, не закодированные как данные
формы, что происходит в контроллерах API
Листинг 26.31. Выбор строки запроса для привязки модели в файле HomeController. cs
using Microsoft . AspNetCore . Mvc ;
using MvcMode l s . Models ;
namespace MvcModels . Contro l lers
puЫic class HomeController : Contro l ler
private IRepos i tory repository ;
puЫic HomeController(IReposito ry r epo) {
repository = repo ;
Здесь атрибут FromQuery пр именяется к параметру id, т.е . во время поиска про
це ссо м привязки моделей з начения данных для id будет использоваться только стро
ка з апрос а .
Совет. При указании источника данны х привязки моделей , такого как строка запроса, по
пре ж нему можно осуществлять привязку сложных типов. Для каждого простого свойства
в типе параметра процесс привязки моделей будет искать ключ строки запроса с тем же
самым именем .
Листинг
26.32. Привязка модели из заголовка НТТР-запроса в файле
HomeController.cs
using Microsoft . AspNetCore . Mvc ;
using MvcModels . Models ;
namespace MvcModels . Control l ers
puЫic class HomeController : Controller
private IRepository repository ;
822 Часть 11 . Подробные сведения об инфраструктуре ASP.NET С аге MVC
Метод действия Header () опр еделяет параметр accept , знач ение которого будет
бр аться из заголовка Accept в текущем запросе и возвращаться в качестве р езульта
та метода. Запустив приложение и запросив URL вида /Home/Header, вы получите
прим е рно такой результат (точный результат может отличаться в зависимости от ис
пользуем ого бр аузе ра):
Header: en - US , en ; q=0 . 8
e l se {
return NotFound() ;
<tаЫе class= "t aЫe taЬle - condensed taЬle -b ordered taЫe - striped " >
<tr><th>Accept : </th><td>@Mode l. Acce pt</ t d></ t r>
<tr><t h >Accept - Encoding : </t h >< t d>@Mode l.Accep t Encoding</td></tr>
<tr><t h >Accept -La n guage : </th ><td>@ Model . Acce p t Lang u age</td></tr>
</tаЫе>
Процесс привязки моделей будет исследов ать свойства сло жных типов в поис
ке атрибутов , опи с анных в табл . 26.3. Это дает возможность посредством атрибута
FromHeader определить сложный тип, свойства которого являются моделью , привя
зываемой и з заголов1юв, в ч ем л егко удостов е риться, запустив приложение и запро
сив URL вида / Home/Header (рис . 26.10).
1 Head•rs Х
+- . . ;., С [j locall1ost:s1861/н-;~;;н-;ade;:------------------·--~ Е 1
·---·--
Ассер!: text/html.application/xhtml+xml,application/xml;q=0.9,image/webp,•1•;q=0.8
Accept-Encoding: gzip, deflate. sdch
Accept-Language: en-US,en;q=0.8
листинге 26.39.
826 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
@section scripts {
<script src="/liЬ/jquery/dist/jquery.min.js"></script>
<sc r ipt type= " text/javascript " >
$(document) . ready(function () {
$( " button " ) . click(function (е)
$ . ajax( " /Home/Body ", {
method : " post ",
contentType : " app l ication/ j son ",
data: JSON . stringify({
firstName: " ВоЬ ",
lastName : " Smith "
) ) ,
success: function (data)
$("#firstName") .tex t(data .f irstName) ;
$( " #lastName " ) . text(data . lastName) ;
)
) ) ;
) ) ;
) ) ;
</script>
Совет. Не весь клиентский код JavaScript требует использования атрибута FromBody. В рас
смотренном примере намеренно не применялся удобный метод jQuery для отправки Аjах
запросов POST, потому что он кодирует информацию как данные формы. Взамен исполь
зовался другой метод, который позволил послать данные JSON.
Атрибут FromBody можно прим енять для привязки только одного пар аметра ме
тода действия, и в случае использования этого атрибута более одного раза в отдельно
взятом методе генерируется исключение. Если нужно получить несколько объектов
модели из тела запроса, тогда придется создать простой класс п е редачи данных, ко
торый имеет все необходимые свойства и применя ет содержащиеся в нем данные для
создания объ ект ов, требуемых внутри метода действия.
Глава 26 . Привязка моделей 827
1 Addrш х
First Name:
First Name: В оЬ
Last Name:
Smilh
1
[•+1 ________,
'-------·- -----------------
Рис. 26.11. Использование тела запроса для привязки моделей
Резюме
В настоящей глав е рассматривался процесс привязки моделей, который позволя ет
предоставлять методам действий необходимые аргументы, используя значения дан
ных из обрабатываемого НТГР-запроса. Было показано, как привязывать простые и
сло жные типы, каким образом иметь дело с массивами и коллекциями, а также как
управлять процессом привязки моделей за счет применения атрибутов к параметрам
методов действий или свойствам классов моделей. В следующей главе будет описано
средство проверки достоверности моделей.
ГЛАВА 27
Проверка достоверности
v
моделеи
Вопрос Ответ
Что это такое? Проверка достоверности моделей - это процесс выяснения, что
данные, предоставленные в запросе, пригодны для применения
в приложении
Вопрос Ответ
Генерация ошибок проверки Используйте объект ModelState для регист 27.15, 27.16
достоверности на уровне рации ошибок проверки достоверности , которые
модели не ассоциированы со специфическими свойства
ми , и укажите значениеModelOnly для атрибу
та asp- validation - summary в элементе div
Определение самопроверя Применяйте атрибуты проверки достоверности 27.17, 27.18
емой модели данных к свойствам модели
"dependencies ": {
"Microsoft.NETCore.App":
"version ": " 1.0.0 ",
"type": "pl atform "
},
"Microsoft . AspNetCore . Diagnostics ": "1.0.0",
"Microsoft.AspNetCore.Server.IISintegration": "1.0. 0 ",
" Microsoft .AspNetCore . Server . Kestrel ": " 1 . 0 . 0 ",
" Microsoft .Ex tensions . Logging .Conso l e ": " 1 .0. О ",
"Microsoft.AspNetCore.Mvc": "1.0.0",
"Microsoft.AspNetCore.StaticFiles": 11 1.0.0 11 ,
"Microsoft.AspNetCore.Razor.Tools": {
"version": "1.0.0-preview2-final",
"type": "build"
},
" tools":
"Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final",
"Microsoft . AspNetCore .Se rver .IISintegration.Tools ": "1 .0 .0-preview2-final "
},
"frameworks": {
"net coreappl.0 ":
"imports ": [ "do tnet5 . б ", "p ortaЫe -n et45+win8"]
},
"b uildOptions ":
"emi tEntryPoint ": tr ue, "preserveCompilationContext ": true
},
Создание модели
Создайте папку Models и добавьте в нее файл класса по имени Appointment. cs
с определением, приведенным в листинге 27.3.
using System;
using System . ComponentModel . DataAnno t ations ;
namespace ModelVa lida tion . Models {
puЫic class Appointment {
Создание контроллера
Создайте папку Controllers, добавьте файл класса по имени HomeController. cs
и поместите в него определение контроллера из листинга 27.4, который оперирует с
классом модели Appointment .
@mode l Appointment
@{ Layout = " _Layo ut";
<div class= " bg-primary panel - body " ><h2>Book an Appointment</h2></div>
Глава 27 . Проверка достоверности моделей 833
<form class= "panel - body " asp-action="MakeBooking " method=" post " >
<div class= "form-g roup ">
<label asp - for= "ClientName " >Your name : </labe l >
<input asp - for= " Client Name " clas s=" for m-con trol " />
</div>
<div class= " form-group " >
<label asp -f o r = "Date " >Appointme nt Date : </label>
<input asp- for="Date " type="text " asp- format= "{ O:d)" class="form- control " />
</div>
<div class= " ra dio form-group " >
<input asp - fo r="T ermsAccepted " />
<labe l asp - for="TermsAccepted"> I accept the terms & cond iti ons</label>
</div>
<but t on type= " submit " class ="Ьtn Ьtn-primary " >Make Booking</button>
</fo rm>
@model Appointment
@{ Layout = " Layout ";
<div class= "bg -su ccess panel-body " >< h2 >Your Appo intment</ h2 ></div>
<tаЫе class= "taЫe taЬle - bordered">
<tr>
<th>Your name is : </th>
<td>@Mode l. Cli entName</t d>
</tr>
<tr>
<th>Your appontment date i s : </t h >
<td>@Mode l. Date . ToString("d")</td>
</tr>
</tаЫе>
<а class= "btn btn-success " asp-action= "I ndex " >Make Anothe r Appointment</a>
Cj Mod•I Volidotion
С 10 localhost:S5750
Your name:
ВоЬ
Appointment Date:
12/12/2020
12/12/2020
----·- _ _J
]
Make.Booking
' ~ ~ ~
----------------·-·------
Рис. 27 .1. Запуск примера приложения
if (!appt.TermsAccepted) {
ModelState.AddМodelError(nameof (appt.TermsAccepted),
"You rnust accept the terms");
11 Вы должны принять условия
836 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
if (ModelState.IsValid) {
return V.iew ( 11 Completed 11 , appt) ;
else {
return View();
Имя Описание
Ski pped Это значение указывает, что свойство модели не было обработано.
Обычно это говорит о наличии настолько большого количества ошибок
проверки достоверности , что продолжать проверку не имеет смысла
if (ModelState.GetValidationState("Date") == ModelValidationState .
Valid
&& DateTime . Now > appt . Da t e) {
ModelState . AddMode l Error(nameof(appt . Date) ,
" Please enter а date in the fut ure " ) ;
if (ModelState. IsValid) {
return View ( "Complete d", appt) ;
e l se (
return View () ;
838 Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC
Совет. Использовать код JavaScript подобного рода может быть неудобно, из-за чего возни
кает соблазн применять специальные стили CSS даже при работе с СSS-библиотеками
типа Bootstrap. Однако цвета, используемые классами проверки достоверности в
Bootstrap, можно переопределять посредством тем или путем настройки пакета и опре
деления собственных стилей. Это означает, что вам придется обеспечить соответствие
между любыми изменениями в теме и изменениями в любых специальных стилях, которые
вы определили. В идеальном случае разработчики из Microsoft сделают имена классов
проверки достоверности конфигурируемыми в будущем выпуске ASP.NET Соге MVC , но
до тех пор написание кода JavaScript для применения стилей Bootstrap является более
надежным подходом, чем создание специальных таблиц стилей.
Добавленный код jQuery выполняется, когда браузер завершает разбор всех эле
ментов в НТМL-документе, и результатом будет выделение элементов input , которые
назначены классу input -val idaton - error. Запустив приложение и отправив форму
без з аполнения всех полей, можно получить результат, представленный на рис. 27.2.
Когда форм а о тп равляется без ввода каких-либо данных, все три свойства
выделяются как содержащие ошибки. Пользов атель не увидит представление
Completed . cshtml до тех пор, пока форма не будет отправлена с данными, которые
могут быть разобраны связывателем модели и успешно проходят явные проверки до
стоверности в методе MakeBooking (). Пока это н е произойдет, отправка формы будет
приводить к визуал изации представления MakeBooking . cshtml с текущими ошиб
ками проверки достоверности.
б Modtl Volidotion Х
~- _:_ ? L~-105·;~~~-~~/н~~~:~4~~;8~=~~--~=-°':~:::~=~~:=~--=~=--==-:~=-С -С ~) 1
-1
1
1
1
Yourname: i
г
1
х
·-
!1
1
i Appointment Date:
i
1
E oos xj
1 О 1accept the terms & coпditions
i
1
'1'if1Мid
1L_____ _ 1
·---·-·-----------·--"-----·---·-----~-·-"--------------·------·-------.!
@model Appointment
@{ Layout = " Layout ";
@section scripts {
<script asp - src - include= "/l iЬ/jq uer y/dist/* .m in . js " ></script>
<script type= " text/ j avascript " >
$(document) . ready(funct i on () {
$( "i nput . input - validation -e rror " )
. closest (". form - group ") . addClass ( "has - error " ) ;
}) ;
</script>
Имя Описание
All Это значение применяется для отображения всех ошибок проверки до
стоверности, которые были зарегистрированы
ModelOnly Это значение используется для отображения только ошибок проверки
достоверности, относящихся ко всей модели, за исключением тех, кото
рые были зарегистрированы для индивидуальных свойств, как описано
в разделе " Отображение сообщений об ошибках проверки достовер
ности на уровне модели" далее в главе
[j Model V•liclation Х
1 ~ с [~i;;i1.~~~~}soi~~~~~~~~-~;~~~~--=~=-~~~~~~~~:~~:~------=-~~:~==~--=-~~l __ i
1 1
J Your name: 1
1 c-----------------------XJ 1
1 1
• Appointmenl Date: !
1
i
~"'"°'___ ~ 1
1
О 1accept the terms & conditlons
, diiiilr'·I
1
1
Имя Описание
Имя Описание
В се функции. пр исв аиваемые с в о йств ам из табл . 27.6 , получают стр01~у. с оде ржа
щую з начени е дан ных из запрос а , и во звраща ют стро ку с сообще нием об ошибке .
В кла с се Startup специ ал ьные функции можно сконфигуриров ать как п а р аметры,
27 .1 3 ,
что пр о илл юстр и ровано в л исти н г е где прои зв одится замена ст андартно й
~ M<>del Volidotion
~ С Г(i)l~s750/Home/МakeBoo;;;;;-;;-------------···--
1 Yourname:
j J:::;;~
Рис. 27.4. Эффект от изменения функции генерации сообщений при привязке моделей
Youгname:
Appointment Date:
Please enter а value
using System;
using Microsoft . AspNetCore.Mvc ;
using ModelValidation.Models ;
using Microsoft.AspNetCore.Mvc . ModelBinding ;
namespace ModelValidation . Controllers {
puЫic class HomeController : Controller
puЫic IActionResult Index() =>
View ( "MakeBooking ", new Appointment () Date = DateTime.Now )) ;
[HttpPostJ
puЫic ViewResult MakeBooking(Appointment appt)
if (string.IsNullOrEmpty(appt . Cl ientName)) {
ModelState . AddMode l Error(nameof(appt.C li entName),
" Please enter your name " ) ;
if (ModelState.GetValidationState(nameof(appt.Date))
== ModelValidationState.Valid
&& ModelState.GetValidationState(nameof(appt.ClientName))
== ModelValidationState.Valid
&& appt . ClientName. Equals (" Joe" , StringComparison . OrdinalignoreCase)
&& appt . Date . DayOfWeek == DayOfWeek .Monday) {
ModelState.AddМodelError("",
"Joe cannot book appointments on Mondays");
i f (ModelState . IsValid) {
return View ( " Completed ", appt) ;
else {
return View () ;
Код выглядит более запутанным. чем есть на самом деле , что отража ет саму при
роду проверки достоверности данных. Допустимо сть значений ClientName и Date
проверяется путем инспектирования состояния модели перед выяснением, выпада
<div class= " bg - primar y panel - body " ><h2>Book ап Appointment</h2></ d iv>
<form class= " panel - body " asp - action= " MakeBooking " method= " post " >
<div asp-validation-sununary="ModelOnly" class="text-danger"></div>
848 Часть 11. Подробные сведения об инфраструктуре ASP.NET Соге MVC
Yourname:
' Joe
Appointment Date:
01 /18/2027
_,.· M~ke_~~ki~g -~
1
L..•·---·------·------·----·-··-·--·-··- ·---·····-·--·-····--·"-·--···-·---·-···--·-··---·-··----·-···-·------·-"-·- -·J
using System ;
using System . ComponentModel .DataAnnotat i ons ;
namespace ModelValidation . Models
puЫic class Appointment {
[Required]
[Display (Name = "name")]
puЬlic string Clien t Name { get; set; }
[UIHint ( "Da te" ) ]
[Required (ErrorMessage = "Please enter а date")]
puЬlic DateTime Date { get ; set ; }
[Range(typeof(Ьool), "true", "true",
ErrorMessage = "You must accept the terms")]
puЫic bool TermsAccepted { get ; set ; }
[UIHint("Date " ) ]
[Required(ErrorMessage = "Please enter а date")]
puЬlic DateTime Date { get ; set ; }
850 Часть 11. Подробные сведения об инфраструктуре ASP.NET Саге MVC
[Required]
[Display (Narne = "narne")]
puЫic string ClientName { get; set ; }
using System;
using Microsoft . AspNetCo re.Mvc ;
using ModelValidation.Models;
using Microsoft . AspNetCore . Mvc . ModelBinding ;
namespace ModelValidation . Controllers {
puЫic class HomeControl ler : Controller
if (ModelState.IsValid) {
return View ( Completed appt) ;
11 11
,
else {
return View();
D MO<i•I Validation х
Your name: 1
Appoinlmenl Date:
i
1
Please enter а value
1
YoL1 musl accept lhe lerms !
О 1accepl tl1e terms & conditions '
1
1
*Иi=iid 1
-·----------·-·---- --- --------------- -·--- -- -·- ___ J
Рис. 27. 7. Использование атрибутов проверки достоверности
using System;
using System .ComponentModel .DataAnnotati ons ;
using ModelValidation.Infrastructure;
namespace ModelValidation.Models
puЬlic class Appointment {
[Required]
[Display(Name = "name")]
puЫic string ClientName { get; set; }
[UIHint("Date")]
[Required(ErrorMessage = "Please enter а date")]
puЫic DateTime Date { get; set; }
[MustBeTrue (Erro rMessage = "You must accept the terms")]
puЫic bool TermsAccepted { get; set; }
<input asp -for= " ClientName " class= " form - control " />
</div>
<div class= " form- group " >
<label asp -for= "Date " >Appointment Date : </ label>
<div><span asp-validation-for= " Date " class= " text -d anger " ></s pan>
</div>
<input asp-for=" Date " type="text " asp- format= " {О : d}" class=" form-control" />
</div>
<span asp - validation-for= "TermsAccepted " class= " text - danger " ></span>
<div class= " radio form- group " >
<input asp - for= "TermsAccepted " />
<label asp - for= "TermsAccept ed " >I accept the terms & conditions</label>
</div>
<button type= " submit " class= " Ьtn Ьtn-p r imary">Make Booking</button>
</fo rm>
<input class= " form - control " type= " text " data-val="true"
data-val-required="The name field is required." id= "ClientName "
name= " Cl i entName" value= "" />
Код JavaScript ищет элементы с атрибутом data - val и выполняет локальную про
верку достоверности в браузере. когда пользователь отправляет форму. не посылая
НТГР-запрос серверу. Чтобы увидеть эффект, запустите приложение и отправьте фор
му, одновременно используя инструменты <F12> для наблюдения за тем, что сообще
ния об ошибках проверки достоверности отображаются, хотя никакие НТГР-запросы
серверу не отправлялись.
щью стандартных средств проверки н а стороне клиента.) В листинге 27.23 прив еден
код метода действия ValidateDate () ,добавленного в контроллер Home .
Листинг 27.23. Добавление метода действия для проверки достоверности
в файле HomeController. cs
using System ;
using Micro s of t. As pNetCore . Mvc ;
using ModelVal i dat i on . Models ;
using Microso f t . AspNetCore . Mvc . ModelBinding ;
name s pace Mode l Val i dation . Control l ers {
pu Ы ic class HomeC ontrol le r : Contro l le r
puЫic I ActionResu l t In dex () =>
View ( "MakeBook i ng ", new Appo i ntment () Date = DateTime . Now } ) ;
[HttpPost]
puЫic ViewRes ul t MakeBooking(Appointment appt)
i f (Mode l State. GetVa li da ti onS t ate(name of(appt . Date))
== ModelVa l idationState . Val i d
&& Mode l St ate . GetVa l idat i o nState(name of(appt . Cl i entName))
== Mode l ValidationSta t e . Va l id
&& appt . ClientName . Equals( " Joe", St ringCompari s on . OrdinalignoreCase)
&& appt . Da te. DayOfWeek == DayOfWeek . Monda y) {
ModelState . AddModel Erro r ("",
" Joe cannot book appoin t ments on Mondays " ) ;
Совет. Можно было бы воспользоваться привязкой моделей, так что параметром метода дейс
твия стал бы объект Dat eTime, но тогда метод проверки достоверности не вызывался бы в
ситуации, когда пользователь ввел бессмысленное значение вроде apple . Причина в том,
что связывателю модели не удается создать объект DateT i me из app l e и генерируется
исключение . Средство удаленной проверки достоверности не способно обработать такое
исключение , поэтому исключение молча отбрасывается . В итоге возникает нежелательный
эффект: поле данны х не подсвечивается, создавая впечатление , что введенное пользовате
лем значение является допустимым. Как правило, наилучший подход к удаленной проверке
достоверности предусматривает получение методом действия параметра string и явное
выполнение любого преобразования типа, разбора или привязки модели .
.
С1 Mod~1 Valld-'tion х
1 Yoшname: 1
1 Joe 1
1 Appointment Date: ,
1 ,.,,,,,,.
1
1
L---··- ---·-·· - - - --·-- - - -······--·-· - - - - ··-·-· -- . ---··--. ·- -· -- _\
Рис. 27 .8. Выполнение удаленной проверки достоверности
Внимание! Метод действия для проверки достоверности будет вызываться, когда пользова
тель впервые отправляет форму, и затем каждый раз , когда данные редактируются . Для
элементов ввода текста любое нажатие клавиши будет иметь следствием обращение к
серверу. В некоторых приложениях это может вылиться в значительное количество запро
сов, что должно быть учтено при описании требований к производительности сервера и
ширине полосы пропускания в производственной среде . Кроме того, может быть принято
решение отказаться от удаленной проверки достоверности для свойств, проверка кото
рых сопряжена с высокими затратами (например, если выяснение уникальности имени
пользователя требует обращения к медленному серверу).
Резюм е
В настояще й главе был представлен широкий спектр приемов , доступных для
выполнения проверки достоверности моделей, которая гарантирует, что предостав
ляемые пользователем данные удовлетворяют ограничениям, налагаемым на модель
данных. Пров е рка достоверности моделей является важной темой, и наличи е в при
ложении подходящих средств проверки жизненно важно для обеспечения пользова
телям комфортных условий работы. В следующей главе будет показано, к ак защитить
приложение МVС с применением ASP.NET Соге Identity.
ГЛАВА 28
Введение в ASP.NET
Core Identity
На заметку! Настоящая глава требует наличия установленного средства SQL Server LocalDB
для Visual Studio. Чтобы добавить LocalDB, запустите программу установки Visual Studio и
установите компонент Microsoft SQL Server Data Tools (Инструменты данных Microsoft
SQL Server) .
Вопрос Ответ
Что это такое? Система ASP.NET Core ldentity - это АРl-интерфейс для управления
пользователями и запоминания пользовательских данных в хра
"dependencies ": (
"Microso f t . NETCo r e.App ":
"version " : " 1 . 0 . О ",
" type ": "platform"
} 1
"type": "build"
},
"tools ":
Мicrosoft.AspNetCore.Razor.Tools : 11 1.0.0-preview2-final 11 ,
11 11
"Mi crosoft .AspNetCore . Server . I I Si ntegration .Tools ": "l . O. O-preview2 - fi nal "
}'
11
f r ameworks 11 : {
"netcoreappl . O":
" imports 11 : [ "dotnetS. 6", 11
porta Ы e - net4 5+win8 " ]
) '
"buildOptions ":
11
emitEntryPoint 11 : true , 11
preserveCompilationContext ": true
) '
" runtimeOpt i ons ": {
" conf igProperties ": { 11
Sys tem . GC . Server ": true }
using Microsoft.AspNetCore.Builder;
using Microsoft .Extensions.Dependencyinjection ;
namespace Users {
puЬlic class Startup
puЫic void ConfigureServices{IServiceCol lecti on services) {
services.AddМvc();
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace Users .Controllers {
puЫic class HomeController : Controller
puЫic ViewResul t Index () =>
View(new Dictionary<string, object>
{ [ " Placeholder "] = "Placeholder" } ) ;
</tаЫе>
Глава 28. Введение в ASP.NET Саге ldentity 865
Пр едс тавлен ие отображает содержимое словаря модели в таблице . Для подде
ржки пр едставл ения создайте папку Views/ Shared и добавьте в не е файл по им е ни
_ Layout . cs h t ml с раз меткой , показанной в листинге 28 .5.
Use rs х
r::--
..:;..- ~ С CJ localhost:53221
Placeholder Placeholder
} 1
"buildOptions ":
"emitEntryPoint ": true ,
"preserveCompi l ationCo ntext ": t r ue
} 1
У1шз анны е п акеты добавляют в проект ASP.NET Core Identity и Entity Framework
Core. Доб а вл ени е в разделе too l s уст анавливает инструм е нты командной строки,
котор ы е по зволяют настр а ивать базу данных , используемую для хран е ния данных
Identity, что вскор е будет сделано.
На данный м о мент имеется все, что нужно, хотя мы еще вернемся к классу
AppUser в главе 30 при рассмотрении способа добавления свойств пользовательских
данных, специфичных для приложения.
@using Users.Models
@addTagHelper *, Microsoft . AspNetCore . Mvc . TagHelpers
Глава 28. Введение в ASP.NET Core ldentity 869
Класс контекста базы данных может быть расширен для изменения способа, ко
торым база данных настраивается и используется, но в случае элементарного прило
жения ASP.NET Core Identity простого определения класса вполне достаточно, чтобы
начать и получить заполнитель для любой настройки в будущем.
На заметку! Не пере живайте, если роль та ких классов не особенно ясна. Если ранее вы не
имели дела с инфраструктурой Entity Framework Core, тогда можете трактовать ее как
"черный ящик ". После того, как основные строительные блоки окажутся на месте (и вы
можете их копировать в свои проекты, чтобы все работало), потребность в их редактиро
вании будет возникать редко .
Имея строку подключения к базе данных, можно обновить класс Startup для чте
ния конфигурационного файла и об е спечения доступности настроек (листинг 28.14).
pu Ыi cvo id Con figu reSe rvi ces (IServ iceCo llec ti on s e r v i ces ) {
services . AddMvc();
Внесенные в код изменения загружают файл a pp s ett i ngs . j son и предст авляют
соде ржимое через свойство Conf ig ura t i on , которое применяется в следующем раз
дел е при настройке служб и промежуточного ПО ASP.NET Core Identity.
телями где-то в других местах приложения. Необходимые изме нения поназаны в лис
тинге 28.15.
Глава 28 . Введение в ASP.NET Core ldentity 871
Листинг 28.15. Конфигурирование служб и промежуточного ПО ASP.NET Core ldentity
в файле Startup. cs
Для создания базовой установки ASP.NET Core Identity требуются три набора изме
нений. Сначала настраивается инфраструктура Entity Framework (EF) Core, которая
предоставляет приложениям МVС службы доступа к данным:
Понадобится также настроить службы для ASP.NET Core Identity, что делается сле
дующим образом:
services.Addidentity<AppUs e r, IdentityRole>()
.AddEntityFramewo rkSt ores<Appiden t ityDbCont ex t >() ;
app . Useidentity() ;
Update - Database
Выполнение команды может занять некоторое время, а после ее завершения база
данных будет создана и готова к применению.
)
</tаЫе>
<а class= " btn Ьtn - primary " asp-action= " Create " >Create</a>
+- -i" С 1
[j localhost:53221/Adinin
ID Name Email
No User Accounts
Запустив приложение и перейдя на URL вида / Admin, вы заметите небольшую паузу перед
отображением содержимого, визуализируемого из представления . Причина в том, что инф
раструктура Entity Framework Core должна создать и подготовить базу данных к ее первому
при менени ю.
Увидеть созданную базу данны х можно, открыв окно SQL Server Object Explorer (Проводник
объектов SQL Server) в Visual Studio. SQL Server
Если вы впервые используете окно
Object Explorer, то должны выбрать в меню Tools (Сервис) пункт Connect to Database
(Подключиться к базе данных) , чтобы сообщить среде Visual Studio о базе данных, с кото
рой нужно работать. В качестве источника данных выберите Microsoft SQL Server, для имени
сервера ука жите ( localdЬ) \mssqllocaldЬ , оставьте отмеченным флажок Use Windows
Authentication (Использовать аутентификацию Windows) и щелкните на стрелке, раскрыва
ющей поле Select Or Enter а Database Name (Выберите или введите имя базы данных) .
Спустя несколько секунд отобразится список доступных баз данных LocalDB, в котором
дол ж на быть возможность выбора базы данных IdentityUsers, относящейся к примеру
приложения . Щелкните на к нопке ОК , после чего в окне SQL Server Object Explorer поя
вится новая запись. Среда Visual Studio запомнит базу данных, так что описанный процесс
необходимо выполнить толь ко один раз .
Для просмотра базы данных понадобится раскрыть элемент ( localdЬ) \ms sqll ocaldb<>
Databases <>Identi tyUsers в окне SQL Server Object Explorer. Вы получите воз
можность увидеть таблицы , которые были созданы файлами миграции, с именами вроде
AspNetusers и AspNetRoles . После добавления пользователей, как будет показано в
следующем разделе, в базу данных можно отправлять запросы для просмотра содержимого
таблиц.
Чтобы удалить базу данны х, щелкните правой кнопкой мыши на элементе Identi tyusers
в окне SQL Server Object Explorer и выберите в контекстном меню пункт Delete (Удалить) .
В диалоговом окне Delete Database (Удаление базы данных) отметьте оба флажка и щелк
ните на кнопке ОК для удаления базы данных.
Чтобы заново создать базу данных , откройте окно консоли диспетчера пакетов и введите
следующую команду:
Update - Database
База данны х будет воссоздана и готова к применению, когда вы снова запустите приложение .
Создание пользователей
В отношении входных данных, получаемых приложением, будет использоваться
проверка достоверности моделей MVC, и легче всего это сделать за счет создания про
стых моделей представлений для каждой операции, поддерживаемой контроллером .
Создайте в папке Models файл IOJacca по имени UserViewModels . cs с содержимым,
приведенным в листинге 28.18.
if (ModelState.IsValid) {
AppUser user = new AppUser
UserName = model.Name,
Email = model . Email
};
IdentityResult result
= await userМanager.CreateAsync(user, model.Password);
if (result.Succeeded) {
return RedirectToAction("Index");
else {
foreach (IdentityError error in result.Errors)
ModelState.AddМodelError("", error.Description);
}
return View(model);
Глава 28 . Введение в ASP. NET Core ldeпtity 877
Важной частью листинга является метод действия Crea te () , принимающий
аргумент CreateModel, который будет вызываться, когда администратор отправ
ляет данные формы. Свойство ModelState. IsValid применяется для проверки
того, что данные содержат обязательные значения, и если это так, тогда создает
AppUser user = new AppUser { UserNarne = rnodel . Narne , Ernail = rnodel . Ernail };
IdentityResult result = await userМanager.CreateAsync(user, model.Password);
Имя Описание
if (result.Succeeded)
return RedirectToAction("Index");
else {
foreach (Iden t ityError error in result . Errors)
ModelState . AddModelError( "", error .Descr iption) ;
@model CreateModel
<div class= "bg - prirnary panel - body "><h4>Create User</h4></div>
<div asp - validation -s ummary= " All " class= " text - dange r" ></div>
878 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Core MVC
Имя Описание
Name Joe
Email joe@example . com
Password Secretl23$
После ввода значений щелкните на кнопке Create. Система ASP.NET Core ldentity
создаст пользовательскую учетную запись, которая будет отображаться после перена
правления браузера на метод действия Index () , что видно на рис. 28.3 . (Вы получите
другой идентификатор, т.к. идентификаторы для пользовательских учетных записей
генерируются случайным образом.)
Щелкните на кнопке Create еще раз и введите в элементах формы значения из
табл. 28.5. На этот раз после отправки формы вы получите ошибку, о которой сооб
щается в сводке по проверке достоверности модели (рис. 28.4).
Глава 28. Введение в ASP.NET Core ldentity 879
+- ~ С [J localhost:532.21/Admin ~: =
ю Name Email
75c90f9b-aff7-4818-ab52-Зa640b4501 fd Joe joe@example.com
1ЫМ1
+- ~ С ' CJ localhost:53221/Adniin/Create
Name
Joe
Проверка паролей
Одним из наиболее распространенных требований, особенно в корпоративных
приложениях, является принудительное применение политики проверки парол ей.
Чтобы взглянуть на стандартную политику, запустите приложение, запросите URL
вида /Admi n/Create и заполните форму данными, приведенными в табл. 28.6; важ
ное отл ичие их от данных из предыдущего раздела связано со значением. вводимым
в поле пароля.
880 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Соге MVC
Имя Описание
Name Alice
Ernail alice@example.com
Password secret
U s~_r s х
__________
+- 7 С :a1ocal host: sз2 211Atimi11/Cre;;;----------~: :
--,.:.;.:..;_."-..-·---------· ···-·- --- ...__.:._
.:.:.:........---..:.:. , ~.:.....;.:.:.:_:__:,._ _______ __
• Passwords must have at least опе поп letter and non digit character.
• Passwords musl have at least one digit ('О'-'9'} .
• Passwords must have at Jeas! one uppercase ('A'-'Z').
Name
Alice
Имя Описание
RequiredLength Это свойство типа int применяется для указания мини
мальной длины паролей
[') Us.ers х
г---·--·-------- --·----·-·------ ~
f- С 1 ф localhost:623 i 1/Adn1in/ Create '(:(
--· --------- ·-~-=:=·=::о.=.:---==-==-::::==-..::;:::-=:-.:::=:.:.~-=:-·.=::.===-=--- - -
Name
ВоЬ
using System.Threading.Tasks ;
namespace Microsoft . AspNetCore.Identity {
puЫic interface IP asswordVa lidator<TUser> where TUser : class {
Task<IdentityResult> ValidateAsync(UserManager<TUser> manager,
TUser user , string password);
if (password.Contains("l2345"))
errors . Add(new IdentityError {
Code = "PasswordCon ta insSequence",
Description = "Pas sword cannot contain numeric sequence "
}) ;
Глава 28 . Введение в ASP.NET Core ldentity 883
return Task . FromResult(errors . Count ==О?
IdentityResult . Success : IdentityResul t .Failed(errors.ToArray()) );
К.ласе
CustomPasswordValidator проверяет, не содержит ли пароль имя поль
зователя или последовательность 12345 . В листинге 28.23 класс CustomPassword
Validator р е гистрируется как средство проверки паролей для объектов AppUser.
Имя Описание
Narne ВоЬ
С) Users х
Name
ВоЬ
Имя Описание
Name ВоЬ
Email bob@example.com
Password 12345
[J Usш х
J ~ С ГQ-~~lhos~:S1296/дdn11ri/Cr,;;;;----- -------~
г ---'==--::-:::-:--==-=-=--"=-.-=-===:--_;__-=-::---·--'
1
• Pass\vords must Ье at least 6 characters.
1 • Pass\vord canno1 con tain nLJmeric sequence
Name
ВоЬ
Имя Описание
Name В оЫ
Ema il al i ce@example . com
Passwo rd secret
С) Uиrs
Name
ВоЫ
Email
.~· ~ ..,,,,....,, ~"~.~.,,.,.,,..~ -#~- - ~.
Имя Описание
opts . Password.RequiredLength = 6;
opts.Password . RequireNonAlphanumeric = false;
opts.Password.RequireLowercase = false;
opts.Password . RequireUppercase = false;
opts.Password.RequireDigit = false;
}) . AddEntityFrameworkStores<AppidentityDbContext>() ;
services.AddMvc();
D Users х
f- с . €]
·--- -------·-------------·--------------------
Name
ВоЬ!
вращается объект того же самого класса Identi tyResul t . который применялся при
проверке паролей. Чтобы продемонстрировать работу специального масса проверки,
добавьте в папку Infrastructure файл класса по имени CustomUserValidator . cs
с определением, представленным в листинге 28.26.
Листинг 28.26. Содержимое файла CustomUserValidator. cs из папки Infrastructure
us i ng System . Threading . Tasks;
using Microsoft.AspNe tCore . Ident i ty ;
using Users.Mode l s ;
namespace Users . Infrastr uc ture {
puЬlic class CustomUserValida t or : IU serValidator<AppUser> {
puЬlicTask<IdentityResult> Va lidateAsync(UserManager<AppUser> rnanager ,
AppUser user) {
if (user . Email . ToLower() . EndsWith( " @exarnple . com " ))
return Task .F rornResult(IdentityResult . Success) ;
else {
return Task . FrornResult(IdentityResult . Failed(new IdentityError
Code = "ErnailDornainError ",
Description = "Only exarnple.com emai l addresses are allowed "
}) ) ;
Класс Cus t ornUs erValidator проверяет домен адреса электронной почты, чтобы
удостовериться в том, что он является частью домена examp le . com. 28.27
В листинге
Имя Описание
Name ЬоЬ
1
+- ;".! С . D localhost:S3221/Ac!mio/Create
Name
ЬоЬ
,,_...... ,~ ~.,....
Рис. 28.11.
"".,,. . ~~.
Выполнение специальной проверки пользователей
890 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
using System.Collections.Generic;
using System.Linq;
using System . Threading . Tasks ;
using Mi crosoft .AspNetCore .I dentity ;
using Users . Models;
namespace Users .I nfrastructure {
puЬlic class CustornUserValidator : UserValidator<AppUser> {
puЫic override async Task<IdentityResult> ValidateAsync(
UserManager<AppUser> manager ,
AppUser user) {
IdentityResult result = await base.ValidateAsync(manager, user);
List<IdentityError> errors = result.Succeeded?
new List<IdentityError>() : result.Errors.ToList();
if (!user.Email .T oLower().EndsWith( " @example . com " ))
errors . Add(new IdentityError {
Code = "EmailDomainError ",
Description = " Only example. сот email addresses are allowed "
}) ;
@model IEnumeraЫe<AppUser>
</tаЫе>
<а class= " Ьtn Ьtn-primary" asp - action= " Create " >Create</a>
Admin.
[HttpPost]
puЫic async Task<IActionResult> Delete (string id)
AppUser user = await userМanager.FindВyidAsync(id);
892 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Core MVC
if (user != null) {
IdentityResult result = await userмanager.DeleteAsync(user);
if (result.Succeeded) {
return RedirectT0Action( 11 Index");
} else {
AddErrorsFromResult(result);
}
else {
ModelSta te. AddМodelError ( 11 11 ,
11
User Not Found 11 ) ;
l~ u,..,.
J
г-·-
~ ---- ----- -·--- -------------
с [Ф ~~~~~~~1n -.~- - - - - -·- -----------·--'
j
Na~
ID Name Email
1 ID
j 45Ьdf470-Оd6З-441 5· 80 9е-dЬВ9Зсб d7еЫ le•tuser testeexample.com 1111 ! Ь908Ь741-7аа5-4е70-9367-06dа226467ЗЬ AI~
ii Ь90ВЬ74f - 7 аа5 ·4 е70-9З67 -06d а226467З Ь Alice alicelij)example.cшn 18118 1i еаЬсd121 -ЗЬЗ6-4Jа2-а298-еdЗd857аЗ485 Joe(
1 еаЬСd12 1-ЗЬЗб-4fа2-а298-еdЗd857а3485 Joe
joet'texampJecom " " !" [ )
!i 1111 1: _________________,
l---- ----- - --- ----------·--- ------- ---- _ _j
Рис. 28.12. Удаление пользовательской учетной записи
}
[HttpPostJ
puЫic async Task<IActionResult> Edit(string id, string email,
st:r:ing password) {
AppUser user = await userManager.FindВyidAsync(id);
if (user != null) {
user.Email = email;
IdentityResult validEmail
= await userValidator.ValidateAsync(userМanager, user);
if (!validEmail.Succeeded) {
AddErrorsFromResult(validEmail);
} else {
Add.ErrorsFromResult(validPass);
else
ModelState.AddМodelError("", "User Not Found");
}
return View(user);
user.Email = email;
IdentityResult validEmail = await userValidator.
ValidateAsync(userмanager, user);
i f ( ! validEmail . Succeeded) {
AddErrorsFromResu lt (validEma il ) ;
Кконструкторуконтроллерадобавленазависимостьот IUserValidator<AppUser>,
чтобы можно было проверять новый адрес электронной почты. Обратите внимание ,
что перед выполнением проверки значение свойства Email должно быть изменено,
т.к. метод ValidateAsync () принимает только экземпляры класса пользователя.
Глава 28. Введение в ASP. NET Core ldentity 895
Вторая задача связана с изменением пароля, если он был предоставлен. Система
ASP.NET Core Identity сохраняет хеши паролей, а не сами пароли . Целью является пре
пятствование похищению паролей. Следующая задача заключается в получении про
веренного пароля и генерации хеш-кода. который будет сохранен в базе данных. так
что пользователь м ожет быть аутентифицирован, как демонстрируется в главе 29.
Паролипреобразуютсявхешичерезреализациюинтерфейса IРаsswоrdНаshеr<АррUsеr> ,
которая получается за счет объявления зависимости конструктора, распознаваемой
посредством внедрения зависимостей. В интерфейсе IPasswordHasher определен
метод HashPassword () , который принимает строковый аргумент и возвращает его
хешированное значение :
if (!string . IsNullOrEmpty(password))
validPass = await passwordValidator.ValidateAsync(userМanager,
user, password) ;
if (validPass . Succeeded) {
user.PasswordHash = passwordHasher.HashPassword(user, password);
else {
AddErrorsF r omResu l t(validPass) ;
Соэдание представления
Финальным компонентом является представление, которое будет отображать теку
щие значения для пользователя и позволит отправлять контроллеру новые значения.
@rnodel AppUser
<div class= "bg - prirnary panel - body " ><h4>Edi t Use r </h4></div>
<div asp - validation-surnrnary= "Al l " c las s =" text - danger "></div>
<forrn asp - action= " Edit " rnethod= "post " >
<div class= " form - group " >
<label asp - for= "I d " ></ l abel>
<input asp-for= " Id " class= " form -contr ol" d is aЫed />
</div>
896 Часть 11 . Подробные сведения об инфраструктуре ASP. NET Core MVC
<div class="form-group">
<l abel asp- for= " Ema il" ></label>
<input asp-for="Email" c lass="form-con trol " />
</di v>
<div class= " form - group " >
<l abe l for= " password " >Password</ l abe l >
<inp ut name = "p asswo rd" c las s= "form-contr o l" />
</di v>
<b utt on type="submit " class= " btn Ьtn-primary">Save</button>
<а asp-action="Index" class= " Ьtn Ьtn-default">Cancel</a>
</form>
----·~·-·--'"'---·----"---·~~~---.....;.;:.,_·-·---··--·--·---·-~ __
__._.._-_.. ..
ld
1 f26621ЗО-З26З4се7-Ьс4а·З4dсЗd663715
Password
Cancel
Резюме
В главе было показано, как создавать конфигурацию и классы , требующиеся для
использования системы ASP.NET Core ldentity, а также проде монстрировано , каким
образом их можно применять для создания инструмента администрирования поль
зователей. В следующей главе вы узнаете, как выполнять аутентификацию и автори
зацию с помощью ASP.NET Core ldentity.
ГЛАВА 29
Применение ASP.NET
Core Identity
тодам действий
1· Users Х
,...---- - - - -·- ·-----·-----·--------·- - - · -----, !
1_!"~1:· С · [') ::_j
loc~lhost:625.::_:/дd_n~~~-..:...;;:...:......~·-·-·-·".~-~;::."..:..........=...."--·---·--."~ .'CJ·.•
ID Name Email
1 4fc1 f959-Ыc8-4ede-ad41-6bc4d6953011
d4ca5981 -Зf2c-4Cff-83ee-75cOeb2919a3
ВоЬ
Joe
bob@example.com
joe @example.com ••
,.,
1
L.~~~~~-~~- _J
Аутентификация пользователей
Наиболее фундаментальной работой для системы ASP.NET Core ldentity является
аутентификация пользователей. Основной инструмент для ограничения доступа I<
методам действий - атрибут Authorize, который сообщает инфраструктуре МVС о
том , что обрабатываться должны только запросы от аутентифицированных пользова
телей. В листинге 29.1 атрибут Authorize применяется к действию Index контрол
лера Home.
Имя Описание
Система ldeпtity не может полагаться на систему маршрутизации при генерации своих URL,
так что цель перенаправления должна указываться буквально. В случае изменения схемы
маршрутизации, используемой приложением, потребуется также обеспечить изменение на
стройки ldeпtity, чтобы URL по-прежнему достигал целевого контроллера.
Совет. В реальном проекте для выяснения, предоставил ли пользователь значения для име
ни и пароля, перед отправкой формы серверу можно использовать проверку достовер
ности на стороне клиента, которая была описана в главе 27 .
[HttpPost ]
[Al l owAnonymous]
[ValidateAntiForgeryToken]
puЫic async Task<IActionResult> Login(LoginModel details,
string returnUrl) {
return View(details);
пользователям, такую как сброс пароля, например. С этой целью к классу контролле
ра был применен атрибут Authorize, а к индивидУальным методам действий - ат
рибут Al l owAnonymous .
В итоге доступ к методам действий по умолчанию ограничивается аутентифицирован
ными пользователями, но пользователям, не прошедшим аутентификацию, разрешено
входить в приложение. Кроме того, применяется атрибут ValidateAntiForgeryToken,
описанный в главе 24, который работает в сочетании с дескрипторным вспомогатель
ным классом для элемента forrn в целях противодействия подделке межсайто вых
запросов.
1 u"" х
~ -:i
---...:.::.·--~-'" ---·. ". ..__.":..::."'·-·"
"~::.....::..._..___. ·---~--~-·-· --1
С :D-loca lh-;;st:62S42iAccou~tfl-;g i11?R-;,t~~11U rJ :%2FHom;%2 Flnd-;,-;---~: S 1
Password
-
~-------------------··
[AllowAnonymous]
puЫic IActionResult Login(string returnUr l ) {
ViewBag.returnUrl = returnUrl ;
return View () ;
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
puЫic async Task<IActionResult> Login(LoginМodel details,
string returnUrl) {
if (ModelState . IsValid) {
AppUser user = await userManager.FindВyEmailAsync(details.Email);
if (user != null) {
await signinМanager.SignOutAsync();
Microsoft.AspNetCore.Identity.SigninResult result
await signinмanager.PasswordSigninAsync(
user, details.Password, false, false);
if (result.Succeeded) {
return Redirect (returnUrl ? ? "/ ") ;
return View(details);
Система ASP.NET Core ldentity поддер жи вает та кже двухфакторную аутентификацию, когда
пользователю нужно кое-что дополнительное, обычно получаемое в момент, когда он желает
пройти аутентификацию. Наиболее распространенными примерами могут быть значение из
маркера SecurelD или код аутентификации, который отправляется в виде сообщения элек
тронной почты или текстового сообщения. (Строго говоря, в качестве двух факторов может
выступать что угодно , включая отпечатки пальцев , результаты сканирования радужной обо
лочки глаз или распознавания голоса , хотя в большинстве веб-приложений такие варианты
востребованы редко.)
Глава 29. Применение ASP.NET Соге ldentity 907
Защита в итоге усиливается, потому что злоумышленнику необходимо знать пароль пользо
вателя и иметь доступ к тому, что предоставляется как второй фактор, наподобие учетной
записи электронной почты или сотового телефона.
Тестирование аутентификации
Чтобы протестировать аутентификацию пользователей, запустите приложение и
запросите URL вида / Home /I ndex. После перенаправления на URL вида /Accoun t/
L og i n введите учетные данные одного из пользователей, перечисленных в начале
главе (скажем. адрес электронной почты j o e @ex amp l e.c om и пароль se c retl 23).
Щелкните на кнопке Log ln (Вход) и браузер будет перенаправлен обратно на /Home /
Index, но на этот раз он отправит сооkiе-набор аутентификации , который предоста
вит доступ к методу действия (рис. 29.4).
1:------------·
! •.
!!
( Us~"
+- -:' С D lo~h-
Email
os-
><
t:б-'2==
54""
,,
21""
дc==co=,"~\1Log-
1n - - - - - - - - - - - - '5' Е
~~ ~;~<:.~-°--~~~~~~~-~------------~
1
joe@example.com 1
J
1
1~'•- - · - - - - - - - - - - - - - - - - -]
Рис. 29.4. Аутентификация пользователя
908 Часть//. Подробные сведения об инфраструктуре ASP.NET Соге MVC
Имя Описание
return View{name) ;
[HttpPost]
910 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Core MVC
else {
Mode l State . AddMode l Error( "", "No role foun d" ) ;
Имя Описание
Rol eEx i stsAs ync(name) Возвращает true, если роль с указанным именем
существует
Чтобы ото б р азить детали рол ей в прилож ении, создайте п апку Views/RoleAdmin
и добавьте в н ее файл Index. csht ml с ра зм еткой из листинг а 29.7 .
</tаЫе>
<а class= " btn btn - pr imary " asp - act i on=" Create " >Create</a>
Этот дескрипторный вспомогательный класс опер ирует на эле ментах t d поср едс
твом атрибута iden ti t y - role, который используется для получ е ния им ени обраба
тываемой роли. Объекты Ro l eManager<IdentityRole> и UserManager <AppUser>
позволяют отправлять запросы базе данных Identlty для построения спи с к а и ме н
пользователей в роли. В листинге 29.9 к файлу импортирования представл ений до
бавляется дескрипторный вспомогательный класс RoleUse r sTagHe l per и выраже
ние @us i ng , чтобы на типы EF Core можно было ссылаться внутри пр едставлени й,
не указывая пространство имен .
@model stri ng
<div class= "bg - primary panel - body " ><h4>Create Ro l e</ h 4></div>
<div asp - validation- summary= "All" class="text-danger"></div>
<form asp - action= "Create " method="post">
<div c l ass="form- group " >
<label for= " name " ></label>
<inp ut name="name" class= "form-cont rol " />
</div>
<button type= " submit " c l ass= "bt n btn-primary">Create</button>
<а asp - action= " Index " class ="Ьtn Ьtn-default">Cancel</a>
</form>
Для создания роли из данных формы требуется только имя - вот почему в Crea te .
cshtml имеется возможность применения string в качестве класса модели представ
1О Name Users
Ьае5а ОП-706Ь-407f- Ь89Ь·4170Ь4437ЬеЬ Admins No Users
111111
ccc2df5c-ЗC1e-4c6c-ae5d - 5db140с45а00
lL---------------------------~
lEI
Users No Users
."
Рис. 29.5. Создание новой рол и
914 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Core MVC
Кл асс RoleEdi tModel пр едставляет роль и детали о польз ователях в системе, ка
тегоризированные по членству в роли . Класс RoleModificationModel представля ет
набор изменений роли . В листинге 29.12 к контроллеру
RoleAdrnin добавляют ся но
вые методы действий, которые используют модели представл ений из листинга 29.11
для управления членством в ролях.
Глава 29. Применение ASP.NET Core ldentity 915
Л и стинг 29.12. Добавление методов действий в файле RoleAdminController. cs
[HttpPost]
puЬlic async Task<IActionResult> Edit(RoleModificationМodel model)
IdentityResult result;
if (ModelState.IsValid) {
foreach (string userid in model.IdsToAdd ?? new string[] { }) {
AppUser user =
await userManager.FindВyidAsync(userid);
if (user != null) {
result = await userManager.AddToRoleAsync(user,
model . RoleName);
if (!result.Succeeded) {
AddErrorsFromResult(result);
if (user != null) {
result = await userManager.RemoveFromRoleAsync(user,
model.RoleName);
if (!result.Succeeded ) {
AddErrorsFromResult(result);
}
if (ModelState. IsValid) {
return RedirectToAction(nameof(Index));
else {
return awai t Edi t (model. Roleid) ;
Большая часть кода в версии метода действия Edi t () для запросов GET отвечает
за генерацию наборов членов и не членов выбранной роли. После того как все поль
зователи категоризированы, новый экземпляр класса RoleEdi tModel передается ме
тоду View (), так что данные могут быть отображены с применением стандартного
представления. Версия метода действия Edi t () для запросовPOST отвечает за до
бавление и удаление пользователей в и из ролей. Класс UserManager<T> предлагает
методы для работы с ролями, которые описаны в табл. 29.7.
Имя Описание
@model RoleEditModel
<div class= " b g- primary panel - body " ><h4>Edit Ro l e</ h 4></ di v>
<div asp - val i dation - summary= "Al l" c la ss = " text - danger " ></div>
<form asp - action="Edit " method= " pos t" >
<input type= " hidden " name= " roleName " value= " @Model . Role . Name " />
<input type= " hidden " name =" roleid " va l ue= " @Model.Role . Id " />
<hб class= " bg -i nfo pane l -body " >Add То @Model.Ro l e . Name</hб>
<tа Ые class= " taЫe taЫe - bordered taЫe - condensed " >
@if (Model . NonMembers . Count() == 0) {
<tr><td col s pan= " 2 " >Al l Use rs Ar e Memb ers</ t d></ tr >
else {
@foreach (AppUser user i n Mode l.No nMembers)
<tr>
<td>@user . Us erName</td>
<td>
<input type= " checkbox " name= " IdsToAdd " value =" @user.Id " >
</td>
</tr>
</tаЫе>
<hб class= " bg -i nfo panel - body " >Remov e From @Model . Role . Name</hб>
<tаЫе class= " taЫe taЫe - bordered ta Ы e - condensed " >
@if (Model . Members . Count() == 0) {
<tr><td colspan= " 2 " >No Users Are Members</td></tr>
else {
@foreach (App User user i n Mode l. Me mbers)
<tr>
<td>@user . UserName</td>
<td>
<input type= " checkbox " name=" IdsTo Delete " value= " @u ser .I d " >
</td>
</tr>
</tаЫе>
<button type= " submit " class= " btn Ьt n - pr i mary " >Save</button>
<а asp - action= "I ndex " class= " btn btn - default " >Cancel</a>
</form>
l Users
~ ~ С t)i.;call,ost:б2542/Rol.;дd;;;-i.n/Edit/f2910ea6-:-;-734.4t;;:i=;;926.0"d1cз641if6f --~( ;:
--~----~.-:.----·---~ - ~ - - _____1
ВоЬ [;J
Joe о
Alice о
- Cancel 1
i......-------------------------·--------·--------___J
Рис. 29.6. Просмотр и редактирование членства в ролях
Отметьте флажки для пользователей Alice и Joe (две из учетных записей, добав
ленных в систему Identity в начале главы) и щелкните на кнопке Save (Сохранить) .
В списке членов роли Users появятся пользователи Alice и Joe, как показано на
рис. 29.7.
! Uш1
!· +--- --·---·-··--·-·.
-1' С' [J localhos t:625 42/RoleAdrnir1
----'-"-'---·"'.:.с......;, __.'-'-_ _ _ . _ _..- · - - ·----
1 ю Name Users
,. .""
1 2ea3d582· 1f5b-4:kЗ-8602-b997e38df992 Admins No Users
"
Рис. 29. 7. Управление членством в ролях
Глава 29. Применение ASP. NЕТ Core ldentity 919
Атрибут Aut horize для метода действия Index () не изменялся, но в случае его
применения к методу OtherAction () было установлено свойство Roles с целью ука
зания на то, что доступ к этому методу должен быть разрешен только членам роли
Users. Кроме того, определен метод GetData (),который добавляет базовые сведения
об удостоверении пользователя с использованием свойств, доступных через объект
HttpContext.
Совет. Атрибут Authorize можно также применять для авторизации доступа на основе
списка индивидуальных пользовательских имен. Это привлекательная возможность в не
больших проектах , но она требует изменения кода в контроллерах всякий раз , когда изме
няется набор авторизуемых пользователей, и обычно означает необходимость в повтор
ном проходе через цикл тестирования и развертывания. Использование для авторизации
ролей изолирует приложение от изменений в отдельных пользовательских учетных запи
сях и позволяет управлять доступом к приложению посредством информации о членстве
</tаЫе>
@if (User?.Identity?.IsAuthenticated ?? false) {
<а asp-controller="Account" asp-action="Logout"
class="Ьtn ьtn-danger">Logout</a>
Чтобы сна бдить действие Acces s Den ied представление м для от ображения, со
здайте в папке Vi ews/Account файл по имени Access Den i ed . cs ht ml с соде ржимым
из л истинг а 29.18.
Совет. Роли загружаются во время входа пользователя, т.е. изменение ролей для пользова
теля, который в текущий м омент аутентифицирован, вступит в силу только после того, ка к
пользователь выйдет и затем аутентифицируется заново.
922 Часть 11 . Подробные сведения об инфраструктуре ASP.NET Core MVC
Action lndex
User ВоЬ
-----------------~
Имея надежную стандартную уч етную запись в базе данных ldentJty, м ожно при
менить атр и бут Authorize для защиты контроллеров Adrnin и RoleAdrnin . В листин
ге 29.22 пр и в едены изменения , внес енные в контроллер Adrnin .
Резюме
В настоящей главе объяснялось. как использовать систему ASP.NET Core Identity
для аутентификации и авторизации пользователей . Вы узнали, каким образом соби
рать и проверять пользовательские учетные данные и ограничивать доступ к методам
1~. Use r ~ х
+- ~ С
------..:...·---:..:.--·---=--
' ••
D localhost:6254 2/Adrп in
... / •• • ' ~ ' '
-
•
:
:" ~ ~
--"'~--------------·..;,._
1О Name Email
""
2fe2bВf9-e1 d9-4da4-a833-c4881ed01358 Admin admin@example.com
""
65 1aзbda-7fe5·45Зc-b32d-f50f1 a5126d2 ВоЬ bob@example.com
."
""
bd71а9е7 -d376-4a2c-abbc-71 b87afcffe2 Joe joe@example.com
*1
Рис . 30 . 1. Н ач альн ы е пользовател и в ба з е да нн ы х ldentity
Роль Чл ены
U s er~ х
г,:;---·----·------ ---·-·---·-------·-----------
ID Name Users
c56fd8dd-3336-4340-a775-e4e57959d235 Admins Admin
адресов - короче говоря, любые данные, которые полезны при выполнении прило
жения и должны предохраняться между сеансами. Поскольку по умолчанию для хра
нения своих данных система ASP.NET Core Identity применяет инфраструктуру Entity
Framework Core, определение дополнительной информации о пользователе означает
добавление свойств в класс пользователя и предоставл ение EF Core возможности со
здать схему базы данных для их сохранения.
В листинге 30.1 приведен код класса AppUser с добавленными двумя простыми
свойствами, предназначенными для представления города, в котором живет пользо
ватель , и уровня его квалификации.
[Authorize]
puЫic IActionRe sult Index() => View(GetData(narneof(Index))) ;
[Authorize(Roles = "Users " ) ]
puЫic IActionResul t OtherAction () => View ( " Index ",
GetData(narneof(OtherAct i on))) ;
private Dictionary<string , object> GetData(string actionName) =>
new Dictionary<string , object> {
["Action"J = actionName , [ "User "J = HttpContext . User.Identity.Name,
[ "Authenticate d "] = HttpContext . User . Identity.IsAuthenticated,
[ "Auth Туре " ] = HttpContext.User .I dentity . Authen t icationType ,
["In Users Role "] = HttpContext . User . IsinRo l e( "Users " ) ,
["City"] = CurrentUser.Result.City,
["Qualification"] = CurrentUser.Result.Qualifications
};
[Authorize]
puЫic async Task<IActionResult> UserProps()
return View(await CurrentUser);
[Authorize]
[HttpPost]
puЫic async Task<IActionResult> UserProps(
[Required]Cities city, [Required]QualificationLevels qualifications)
if (ModelState. IsValid) {
AppUser user = await CurrentUser;
user.City = city;
user.Qualifications = qualifications;
await userManager.UpdateAsync(user);
return RedirectToAction("Index");
@model AppUse r
<div class= " bg - primary pa n el - body " ><h4>@Mode l. UserName</h4></div>
<div asp - validat i on - summary= "Al l" cl as s= " tex t- danger " ></d i v>
<form asp - ac ti o n="U serProps " me t hod="pos t" >
<div c l ass= " form - group " >
<labe l asp - for =" City " ></ l abel>
<select asp - for= " City " class =" fo r m- cont r o l"
a sp- items= " @new Sel e ct List (Enum . GetName s(typeof(Cities) ) ) " >
<option di sa Ыed selected v al ue="" >Select а Ci t y</option>
</se l ect>
</d i v>
<div class ="f orm- group " >
<label asp -for= " Qualifications " ></ l abe l >
<select asp - for= " Qualifications" class= " form - control "
asp- items= " @new SelectList(Enum . Ge t Names(typeof(QualificationLevels) )) " >
<option d i saЫed selected val u e ="" >Se l ect а City</option>
</select>
</div>
<butt o n typ e=" submit " class= " Ьtn Ьtn -p r i mary " >Submit</button>
<а asp - act i o n=" Index " class= " Ьt n Ьt n - de f aul t" >Cancel</a>
</ f orm>
свойств, теперь на месте, и осталось лишь обновить базу данных, чтобы ее таблицы
сохраняли значения специальных свойств.
Первым делом понадобится создать новый файл миграции базы данных, кото
рый будет содержать команды SQL, требуемые для обновления схемы базы данных.
Откройте окно консоли диспетчера пакетов и введите следующую команду:
Внимание! Инструменты Entity Framework Core текущей версии не добавляют в файл миграций про
странство имен, которое содержит классы моделей. Для устранения проблемы придется отре
дактировать в папке Mi grat i o n s файл, имя которого содержит строку CustomPropert ie s ,
и добавить в его начало оператор usin g для пространства имен Us e rs. Model s .
тва можно, запросив URL вида /Horne/UserProps , выбрав новые знач ения и щелкнув
на кнопке Submit (Отправить), что приведет к обновлению базы данных и перенаправ
лению опять на URL вида / Ноте с отображением новых значений (рис. 30.3).
Acllon lndex
Action lndex
User
User Alice
Aullieпli ca te d London
Au t h e п t i ca t ed Tru e
Auth Туре
Oualiflcatlons
Auth Туре Mlcrosofl.AspNet. ldentity.Application
ln Users Role True
Advanced ln Users Role True
J City None
Advanced
L--------l -
l. " --L__
Рис. 30.З. Использование специальных свойств в классе пользователя
Совет. Вы вовсе не обязаны применять заявки в своих приложениях и, как было показано в
главе 29, система ASP.NET Core ldeпtity прекрасно обеспечивает приложения службами
аутентификации и авторизации безо всякой потребности в понимании заявок.
Глава 30. Расширенные средства ASP.NET Core ldentity 933
Понятие заявок
Заявка - это порция информации о пользователе наряду со сведениями о том,
откуда информация поступила. Изучать заявки легче всего на практических демонс
трациях, в отсутствие которых любое обсуждение становится слишком абстрактным,
чтобы быть по - настоящему пол езным. Прежде всего, добавьте в папку Contro ller s
ф айл класса по имени Claims Con t ro ll er . cs и определите в нем контроллер, как
пока з ано в л истинге 30.4.
дите вывод из метода действия и представление, которое будет определено. Это лучше
всего другого содействует пониманию заявок.
последующи х разделах
Имя Описание
Имя Описание
</tаЫе>
<td identity-claim-type="@claim.Type"></td>
if (!foundType) {
output . Content . SetContent(ClairnType . Split( ' /' , '. ' ) .Last( )) ;
936 Часть 11. П одробные сведения об инфраструктуре ASP.NET Core MVC
В табл. 30.6 отраж ен самый важный аспект заявок , который зюиrючается в том,
что они уже применялись при реализации стандартных ср едств аутентификации и
авторизации в главе 29. Здесь видно, что некоторые из заявок относятся к удостове
рению пользователя (заявка это Al i ce , а заявка Name!dent i f ie r -уникаль
Name -
ный идентификатор пользователя Al ice в базе данных ASP.NET Core Identity). Друrие
заявки показывают членство в ролях - есть две заявки Ro l e , свидетельствующие о
том факте, что пользователю Alice назначены роли User s и Emp l oyees.
Когда информация подобного рода выражается как набор заявок , отличие состоит
в том , что появляется возможность определить, откуда поступили данные. Для всех
заявок, приведенных в табл . 30.6, св ойство I ss u e r установл ено в L OCAL AUTHORITY ,
указывая на то, что удостоверение пользователя бьmо установлено приложением .
Глава 30. Расширенные средства ASP.NET Core ldentity 937
Теперь, когда вы увидели примеры заявок, определить, что собой представляет за
явка, гораздо легче: заявка - это любая порция информации о пользователе, которая
доступна приложению, включая удостоверение пользователя и его членство в ролях.
Создание заявок
Заявки интересны тем, что приложение может получать их из многих источников, а
не просто полагаться на локальную базу данных, хранящую информацию о пользовате
лях. В разделе "Использование сторонней аутентификации" далее в главе на реальном
примере будет показано, как аутентифицировать пользователей с помощью сторонней
системы, а пока в проект нужно добавить класс, эмулирующий систему, которая пре
доставляет информацию о заявках. Добавьте в папку Infrastructure файл класса по
имени LocationClaimsProvider. cs с содержимым из листинга 30.7.
Листинг 30. 7. Содержимое файла Loca tionClaimsProvider. cs
из папки Infrastructure
using System . Security . Claims ;
using System . Threading.Tasks ;
using Microsoft .AspNetCore .Au thentication ;
namespace Users. I nfrastructure {
puЫic static class LocationClaimsProvider
puЫic static Task<ClaimsPrincipal> AddClaims(
ClaimsTransformationContext context) {
ClaimsPrincipal principal = context . Principa l;
if (principal != null
&& !principal.HasClaim(c =>с.Туре== ClaimTypes . PostalCode))
Claimsidentity identity = principal.Identity as Cl a imsidentity ;
if (identity != null && identity . IsAuthenticated
&& identity . Name != null) {
if (identity . Name . ToLower() == "alice") (
identity.AddClaims(new Claim[] {
CreateClaim(C l aimTypes .PostalCode , "DC 20500 " ) ,
CreateClaim(ClaimTypes . StateOrProvince , " ОС " )
)) ;
else {
identity . AddClaims(new Claim[ ] {
CreateClaim(ClaimTypes . PostalCode , " NY 10036 " ) ,
CreateClaim(ClaimTypes .StateOrProvince , " NY")
) ) ;
}
return Task.FromResult(principal);
проживает пользователь.
Трансформация заявок
ятно будут данные, и какой вес они должны иметь в приложении . К примеру. данные
о месте жительства, полученные из центральной базы данных человеческих ресурсов,
скорее всего, будут более точными и надежными, чем данные, которые получены от
внешнего поставщика списков рассьmки.
Использование политик
Имеющиеся рабочие заявки можно применять для управления доступом пользо
вателей к приложению бол ее гибким образом, ч е м с помощью стандартных рол е й.
Пробл е ма с ролями в том, что они статичны , и после того, как пользователю была на
значена роль, он остается е е чл еном до тех пор, поr<а не будет явно удален. Вот почему
длительно работающие сотрудники крупных корпораций в итоге обладают букв ал ьно
немыслимым доступом к внутренним системам: им назначаются роли, требуемые для
выполнения каждого нового задания, но старые роли удаляются р едко.
Имя Описание
IEnumeraЫe<string>
Requ ireRole(ro l es) Этот метод требует, чтобы пользователь имел членство
в роли. Множество ролей может быть указано как аргу
менты, разделяемые запятыми, или в виде экземпляра
Политика в листинге 30.9 требует, чтобы пользователь имел членство в роли Users
и заявку StateOrProvince со значением ос. Когда требований н еск олько, тогда все
они должны быть удовлетворены, чтобы авторизация была одобрена.
П е рвы й аргумент метода AddPo li cy () - это имя. посредством которого можн о
ссылаться на политику при ее применении. Именем политики из листинга 30.9 явля
ется DCUsers, и оно используется в атрибуте Authorize для применения политики к
контроллеру Ноте в листинге 30.10.
Листинг 30.1 О. Применение политики авторизации в файле HomeController. cs
[Authorize]
puЫic IActionResult Index() => View(GetData(nameof(Index))) ;
/ / [Authorize (Roles = 11 Users 11 ) ]
[Authorize(Policy = "DCUsers")]
puЫic IActionResul t OtherAction () => View ( Index ,
11 11
GetData(nameof(OtherAction))) ;
11 .. . для краткости другие методы не показаны ...
private Task<AppUser> CurrentUser =>
userManager . FindByNameAsync(HttpContext.User . Identity . Name) ;
Глава 30. Расширенные средства ASP.NET Core ldentity 943
Свойство Policy используется для указания имени политики, которая будет при
меняться, чтобы защитить метод действия. Результатом окажется комбинированная
проверка имеющихся у пользователя ролей и заявок, которая выполняется, когда за
прос нацелен на метод OtherAction ().Правильным сочетанием членства в ролях и
заявок располагает только учетная запись Alice, в чем можно удостовериться, за
пустив приложение, войдя от имени разных пользователей и запрашивая URL вида
/Horne/OtherAction.
Обр аботчик требования в листинге 30. l l пров еряет и мя пол ь зов ателя. чтобы
выяснить, находится ли оно в списке запрещенных пользователей , которы й пре
доставляет объе кт BlockUsersRequ i remen t, и вызывает метод Succeed () ил и
Fail () соответственно. Применение специального требования сопряжено с двумя
и зменениями конфигурации (листинг 30 .12) .
services.AddDbContext<AppidentityDbContext>(options =>
opt i ons . UseSqlServer(
Configuration[ "Da ta : SportStoreidentity :ConnectionString " ] ));
services.Addidentity<AppUser , IdentityRole>(opts => {
opts . User .RequireUnique Email = true;
opts.Password.RequiredLength = б;
opts . Password.RequireNonAlphanurneric = false ;
opts . Password .RequireLowercase = false;
opts . Password .Requ ir eUppercase = false;
opts . Password.RequireDigit = false;
)) . AddEntityFrameworkStores<AppidentityDbContext>() ;
services . AddMvc() ;
11 [Authorize(Roles = "Users")]
[Authorize (Policy = "DCUsers")]
puЫic IActionResult OtherAction() => View("Index",
GetData(nameof(OtherAction)));
[Authorize (Policy = "NotBob") ]
puЫic IActionResult NotBob () => View ("Index", GetData (nameof (NotBob))) ;
Это всего лишь заполнитель для реального до1<умента, а его ключевой аспект в
том, что править каждый д01<умент разрешено только двум лицам: автору и редакто
ру. Реальный документ потребовал бы содержимого, хронологии изменений и многих
других средств, но для примера вполне достаточно и такого простого класса. Добавьте
в папку Controllers файл класса по имени DocumentController. cs и создайте в
нем контроллер, как показано в листинге 30.15.
Глава 30. Расширенные средства ASP.NET Core ldentity 947
Л истин г 30.15. Содержи м ое фа й ла DocumentController. cs из папки Controllers
Title Author
1
аз Budgel Alico Joe 18 i1
Document ediHng feature \•1ould go t1ere".
Allce
В оЬ
111 11111181
L___т·-------"-·---·----~
_J
services.AddTransient<IAuthorizationHandler,
DocumentAuthorizationHandler>();
services . AddAuthorization(opts => {
opts . AddPo l icy( " DCUsers ", policy => {
po li cy . RequireRole("Users ");
policy . RequireClaim(ClaimTypes.StateOrProvince , "DC");
}) ;
opts . AddPolicy ( " NotBob ", policy => {
policy .Requir eAuthenticatedUser() ;
policy.AddRequirements(new BlockUsersRequirement( " Bob " ));
}) ;
opts. AddPolicy ( "Au thorsAnd.Edi tors" , policy => {
policy.AddRequirements(new DocumentAuthorizationRequirement
AllowAuthors = true,
AllowEditors = true
}) ;
}) ;
}) ;
services . AddDbCo nt ext<App identityDbContext>(opt i ons =>
options . UseSqlServer(
Configuration[ "Data : SportStoreidentity : Connect i onString " ]} ) ;
services . Add identity<AppUser , Iden tit yRole>(opts => {
opts . User .RequireUniqueEmail = true;
opts .Password . Required Length = 6 ;
opts . Password . RequireNonAlphanumeric = fa l se ;
opts . Password .RequireLowercase = false ;
opts . Password . RequireUppercase = false ;
opts . Password . RequireDigit = false;
}) . AddEntityFrameworkStores<Appident i tyDbContext>() ;
servi ces .AddMvc ();
Листинг 30. 20. Добавление пакета NuGet в файле proj ect. j son
Для каждой службы, поддерживаемой ASP.NET Core Identity, предусмотрен свой па
кет NuGet, как показано в табл. 30.10.
Имя Описание
@model LoginModel
<d i v c l ass= " bg -p rimary pane l- body " >< h 4> Log In </h 4 ></div>
<div cla s s= " text-danger " asp - validation -s ummary= " All " ></div>
Глав а 30. Расширенные средства ASP.NET Соге ldentity 955
<form asp - action= " Login " method= " post " >
<input type= " hidden " name= " returnUrl " value= " @ViewBag . return Ur l" />
<div class= " form - group " >
<label asp - for= " Email " ></label>
<input asp - for= " Email " class= " form - control " />
</div>
<div class= " fo r m- group " >
<label asp - for= " Password " ></label>
<input asp - for= " Password " class= " form - control " />
</div>
<button class= " btn btn - pr i mary " t ype= " submit " >Log In </ b u t ton>
< а class="Ьtn Ьtn- i nfo" asp-action="GoogleLogin"
asp-route-returnUr1="@ViewBag.returnUr1">Log In With Goog1e
</а>
</form>
[AllowAnonymous]
puЫic async Task<IActionResul t> GoogleResponse (string returnUrl = "/")
{
ExternalLogininfo info = await signinМanager.GetExternalLogininfoAsync();
if (info == nu11) {
r e tur n RedirectToAction(nameof(Login));
return AccessDenied();
Это означает, что когда пользователь щелкает на кнопке Log ln With Google (Во йти
с помощью Google), браузер перенаправляется на страницу аутентификации Google и
затем после успешного прохождения аутентификации направляется обратно на метод
действия GoogleResponse ().
Внутри метода GoogleResponse () детали внешнего входа получаются вызовом
метода GetExternalLogininfoAsync () объекта SigninManager:
Если вход произвести не удалось, то причина в том. что в базе данных отсутствует
запись , которая представляла бы пользователя Google. Для решения проблемы с при
менением следующих двух операторов создается новый пользователь и ассоциируется
с учетными данными Google:
Глава 30. Расширенные средства ASP.NET Соге ldentity 957
На заметку! При создании польз ователя ldentity ис пользуется заявка адреса эл ектронно й поч
ты, предоставленная Google для свойств Email и Us e r Name объе кта App Us er , та к что
никаких конфликтов имен с пользователями , существующими в базе данных, не возникает.
- - - - - ----
-~ - - J'
Рис . 30 . 7. Пр име р исп ользов а ния сторонне й аутентификации
Резюм е
В настоящей главе были описаны некоторые расширенные средства, поддержи
ваемые системой ASP.NET Core Identlty. Здесь демонстрировалось применение специ
альных свойств пользователя и обеспечение их поддержки за счет обновления схемы
базы данных с использованием миграций базы данных. Далее в главе объяснялась
работа заявок и их применение для создания более гибких способов авторизации
пользователей посредством политик. Также было показано , как испол ьзовать поли
тики для контроля доступа к индивидуальным ресурсам, управляемым приложением .
"dependencies ": {
"Microsoft .NETCo r e . App ":
"versio n": " 1 . 0 . 0 ",
" type " : "platfo r m"
}'
"Microsoft . AspNetCore . Diagnostics ": "1. 0 . 0 ",
"Microsoft . AspNe tCo r e . Server . IISintegration ": " 1 . 0 . 0 ",
"Microsoft . AspNetCore . Serve r. Kest re l ": " 1 . 0 . 0 ",
"Microsoft . Extensions . Logging . Con s ole ": " 1 . 0 . О ",
11
Мicrosoft. AspNetCore. Mvc 11 : 11 1 . О. О 11 ,
11
Microsoft.AspNetCore.StaticFiles 11 : 11 1.0.0 11 ,
"Microsoft . AspNetCore.Razor.Tools": {
11
version": "1.0.0-preview2-final 11 ,
"type": "build"
}'
"tools ":
"Microsoft.AspNetCore . Razor.Tools": 11 1.0.0-preview2-final 11 ,
}'
"buildOptions ":
"emitEntryPoin t": true , " p r ese r v e Comp i lationContext ": t ru e
}'
" runtimeOpt i ons ": {
"configPrope rtie s ": { " System . GC. Server ": true }
В л истинге 31.2 приведен код класса Sta rt up, который конфигурирует средства,
пр едо ставляемые пакетами NuGet.
960 Часть 11. Подробные сведения об инфрастру ктуре ASP.NET Core MVC
@model Resu l t
@{ Layout = null ;
<!DOCTYPE html>
<html>
<head>
<meta name="viewport " content="width=device-width" />
<l ink asp -href - include= " liЬ/boo ts tr ap /dis t /css/* . min . css " rel= "stylesheet " />
<title>Result</title>
</head>
<body c l ass= "pane l- body " >
<tаЫе c la ss= "t aЫe taЫe-condensed t a Ы e -b ordered " >
<tr><th>Controller : </th>< td>@Model .Control l er</ t d></tr>
<tr><th>Action : </t h><td>@Model . Action</td></tr>
</tаЫе>
</body>
</html>
Controller: HomeController
Action: lndex
В опрос Ответ
Чем они полезны? Соглашения по модели удобны, потому что они делают
возможным внесение изменений в способ отображения
классов и методов на контроллеры и действия. Могут вы
полняться и другие настройки, такие как ограничение НТТР
методов, которые принимаются действиями, или примене
ние ограничений действий (рассматриваются далее в главе)
Модель приложения
Во время процесса обнаружения инфраструктура МVС создает экземпляр класса
ApplicationModel и заполняет его сведениями о найденных контроллерах и дейс
твиях. Посл е завершения процесса обнаружения применяются соглашения по модели,
чтобы внести все специальные изменения, которые были указаны. Отправной точ1<ой
для поним ания модели приложения является исследование свойств, определяемых
клaccoм Microsoft . AspNetCore . Mvc . ApplicationModels . ApplicationModel,
которые описаны в табл. 31.3.
На заметку! Может показаться, что мы начинаем с очень простого места, особенно если
вы стремитесь немедленно погрузиться в детали, но полезно оценить, насколько полно
Имя Описание
Как видите, классы модели приложения охватьmают часть основной фун1{цион аль
ности MVC. Свойство ControllerName, наприм е р, используется для установки им е
ни , которое будет задействовано системой маршрутизации при сопоставлении URL,
а свойство ControllerType применяется для установки класса контроллера , к кото
рому это имя относится.
Глава 31 . Соглашения по модели и ограничения действий 965
Свойство ControllerProperties возвращает список объектов PropertyModel,
каждый из которых описывает свойство, определяемое контроллером. Самые важные
свойства класса PropertyModel перечислены в табл. 31.5.
Имя Описание
Имя Описание
методом .
Имя Описание
Act i onName Этот атрибут позволяет явно указывать значение для свойства
Act i onName объекта Act i onModel вместо того , чтобы выводить
его из имени метода
[ActionName ( "Details") ]
puЬl i cIActionResul t List () => View ( " Re sul t", new Resul t {
Controlle r = nameof(HomeCon t rol ler ) ,
Act i on = nameof( List)
}) ;
Здесь указано , что для создания действия должно использоваться имя Det ail s ,
заменяя собой стандартное имя List. Чтобы взглянуть на результат, запустите при
ложение и запросите URL вида /Home/Detai l s. Как показано на рис . 31.2, запрос
обрабатывается методом Li s t () .
..::> С [] localhost:65325/Home/Details -
=
Controller: HomeController
Action: List
Имя Описание
Вс е ч етыр е и нте рф ейса работ ают аналогично : меняется только уровень, н а к ото
ром они функционируют внутри модел и предста влен ия . Н апри м ер. вот опр еделе ние
интe pфeйc aIControllerModelConvention :
Controller: HomeController
Action: List
using System ;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System.Linq;
namespace ConventionsAndConstraints . Infrastructure
[AttributeUsage(AttributeTargets.Method, AllowMultiple true)]
puЬlic class AddActionAttribute : Attribute {
puЫic string AdditionalName { get; }
puЫic AddActionAttribute(string name)
AdditionalName = name;
1 . R•sult х
/ +- -t С : D localhost:65325/Home/List
1--::0~:;--- HomeController
~-~~~~L-is-t~-
Controller: HomeController
Action: List
[ActionNamePrefix ( "Do")]
[AddAct i on( " Details " ) ]
974 Часть 11. Подробные сведения об инфраструктуре ASP.NET Core MVC
1. Result х
~-:_:_,~_:r_:_:l-e-r:_____~_i:_~_e_C_o_n_tr-o-ll_e r______J
Рис. 31 .5. Иллюстрация порядка выполнения соглашений по модели
// [ActionNamePrefix ( "Do")]
[AddAction( " Details ") ]
puЫic IActionResult List () => View ( "Result", new Result {
Controller = nameof(HomeController) ,
Action = nameof(List)
) ) ;
1 Result Х
/ Action: List 1
Вопрос Ответ
Чем они полезны? Если обработать запрос способны два или большее число дейс
твий, то MVC необходимы средства для решения, какое дейс
твие подходит лучше других. Ограничения действий использу
ются для предоставления такой информации
Как они используются? Ограничения действий применяются как атрибуты, что позволяет
их многократно использовать повсюду в приложении, а также оз
[ActionName("Index")]
puЫic IActionResult Other() => View("Result", new Result {
Controller =
nameof(HomeController),
Action = nameof(Other)
}) ;
11 [Act i onNamePrefix( "Do " ) ]
[AddAction ( " Details " )]
p u ЫicIActio n Res u lt Li s t () => Vie w ( " Res ult ", new Result {
Contro l ler = nameof( Home Co n t r o ll er) ,
Action = nameof(List)
}) ;
Здесь бьш добавл е н новый метод по имени Oth er ( ) , к которому применен атрибут
ActionName, так что он выпускает действие под названием I ndex . Кро м е того, был
обновлен класс Startup для удаления глобальных соглашений по модели, прим е нен
ных ранее в главе (листинг 31.19).
Таким образом, в контролле ре Ноте есть два действия по имени Index. Запустив
приложение , вы увидите сообщение об ошибке, показанное на рис. 31.7, которо е сви
детельствует о том, что МVС не известно, какое действие должно использоваться.
+- ~ С D loca ll10st:65325
---------------'---'
An unhandled exceptio п оссштеd while processing the request. ~
AmЬiguousActionE xceptюn : lviultiple actions matched. The fo!lo\'Jiпg actions matched
rou е cJata апd hacl all coпstra ints satisfied:
Ambi guousActi on Exc ep t i on : Mul ti ple a cti on s matc he d . The fol l owing
acti ons matched route data and had a ll con s traints sat i sfied :
Conve nti onsAndConstra i nts.Cont r ol lers.H ome Con t r ol l er .Index
Convent i onsAndCons t ra i nt s . Controller s. Home Contro l l e r . Othe r
AmbiguousActionEx cep t ion : соотве т с тв ие нескольким действиям .
Следующи е дейс твия со отв ет ств ую т д а нным маршрута и все их
ограничения удовле тв орены :
Когда инфраструктура МVС проходит через процесс выбора метода действ ия для
обработки запроса, она выясняет, связаны ли с ним ограничения. При нали ч и и ог
раничений они упорядочиваются в последовательность на основе значения свойства
Or de r, и по очереди для каждого ограничения вызывается метод Accept () .Если дл я
любого ограничения метод Accept () возвращает f a ls e, тогда инфраструктура МVС
считает, что метод действия не может применяться для обработки текущего запроса.
Глава 31. Соглашен и я no м одел и и о гран и чения де й стви й 979
Чт обы п ом очь огр ан ичен и я м дей ств и й пр инять р е ш ение , инфраструкту ра MVC
снаб жает их экземпляро м клас с а Act i onConstraintContext для данных контекс
та, который определяет свойства, описанные в табл. З 1.11.
х L ______ Е1 RE!Stllt
+- ·• С : CJ locall1ost:65325
*'
locall1ost.65325 1.·
Controller: HomeController
Controller: HomeController
Action: lndex
Action: Other
Имя Описание
Имя Описание
[UserAgent("Edge")]
puЫic IActionResu l t Li st() => View( "Res ul t ", new Result (
Controller = nameof(HomeController) ,
Action = nameof(List)
}) ;
Глава 31. Соглашения по модели и ограничения действий 983
В приложении имеется только одно действие List и применени е к нему ограниче
ния означает, что его могут использовать лишь запросы, чей заголовок User-Agent
содержит элемент Edge. Например, если вы отправите запрос из браузера Chrome, то
получите ответ 404 - Not Found .
Пользы в этом мало, т.к. пользователи не поймут. почему они получили ошибку, из
за отсутствия поясняющего текста, который бы предложил применить взамен другой
браузер. Ограничения удобны, когда необходимо управлять выбором метода действия
для обработки запроса, но не когда нужно полностью предотвратить использование
специфического действия . Если вашей целью является как раз последнее, тогда при
мен е ние фильтра позволит перенаправить клиента на описательную страницу ошиб
ки, что будет намного более полезным ответом.
Чтобы решить проблему, понадобится обновить класс UserAgentAttribute так,
чтобы ограничение не отклоняло запросы, :когда для обработки запроса доступно
только одно действие-кандидат (листинг 31.23).
us i ng System ;
name s pace Microsof t. AspN et Core . Mvc . Ac t io nCo ns tr ai nt s
puЫ i c interface IActionConstraintFa cto r y : I Ac tionConstraintMetadata
I Ac t ionConstrai nt Create in stance (I Se r v i ceProvider services) ;
bool IsRe u sa Ьl e { get ; }
В классе Use r Age n tComparer опр еделен единственный м етод . который ищет
строку внутри заголовка User - Agent в НТГР-запросе. Это та же самая функциональ
ность, которая использовалась ранее , но упакованная в отдельный класс , так что его
using System ;
using System.Linq ;
using Microsoft . AspNetCore . Mvc . Ac ti o n Constraints ;
using Microsoft.Extensions . Dependencyinjection;
namespace Conven ti onsAndConstra i nt s. Infrast r ucture
puЬlic class UserAgentAttribute : Attribute, IActionConstraintFactory
Create i nstance () . В качестве аргумента м етод Create i nst a nce () получает объ
ект реализации I Servi ceP rovi de r, используемый в примере для получения объекта
Us e r AgentCompare r, поэтому можно создавать экземпляр закрытого класса ограни
чения, который затем будет задействован в процессе выбор а.
Р езюме
В настоящей главе были описаны два средства, которые используются для на
стройки работы MVC. Вы узнали, как применять соглашения по модели для изменения
способа отображения классов и методов на контроллеры и действия . Кроме того. было
пока зано, каким образом использовать ограничения действий для сужения диап азо
на запросов, которые действие может обрабатывать, и как их применять для выбора
действия из списка кандидатов , идентифицированных при поступлении запроса.
Итак. изучение инфраструктуры ASP.NET Core MVC завершено. Оно начиналось с
создания простого приложения, после чего вы совершили комплексный тур по разно
образным компонентам в инфраструктуре, научившись их конфигурировать, настра
ивать или полностью заменять . Желаю вам успехов в раз работке собств енных при
ложений MVC и надеюсь на то, что вы получили не меньшее удовольстви е от чте ния
этой книги, чем я во время ее н аписания .
Предметный указатель
А N
Ajax (Asynchronous JavaSciipt and ХМL). 631 . NEТCore, 140; 351
Arrange/act/ assert (А/А/А). 179 Node.js. 349: 351
АSР.NЕТСоге, 355 Node Package Manager (NPM), 351
ASP.NEТ Соге Identity, 325 NuGet. 204
ASP.NEТ Core Web Application (.NЕТ Core). 140
ASP.NET Саге Web Application о
(.NET Framework). 140 ОRМ (Object-relational mapping). 218
ASP.NEТWeb Forms, 21
R
в
Razor. 115: 147; 654
Browser Link (Ссылка на браузер), 156
с
s
SportsStore, 201: 244: 276: 298: 325
CDN (Content delivery network). 776
CRUD (Create, read. update, delete), 201: 302 т
CSRF (Cross-site request fогgегу) , 7 46
TDD (Thst-Diiven Development), 189
D u
DAL (Data access lауег). 73
URL (Uniform resource locator), 25
Dl (Dependency injection). 543 относительные к приложению. 789
Е v
Entity Framework Core (EF Core), 218 Visual Studio. 151
Visual Studio Code. 348; 353
G
Git, 350 х
GU!D (Globally unique identifier), 566
XUnit.net. 175
J А
JavaScript, 161 Администрирование,298
JSON (JavaScript Object Notation), 377: защита средств администрирования. 325
535;629 Архитектура
МVС , 24
к "модель-представление", 73
Kestrel, 384 Атака CSRF, 7 4 7
L Б
LINQ (Language Integrated Query). 26 База данных
LocalDB. 363 Identity. 326
LТS (LongThrm Support), 349 добавление пакетов в базу данных, 364
миграции баз данных, 225: 343
м переустановка базы данных. 289
расширение базы данных. 288
МVС (Model-view-controller), 20; 24
создание,337:363
МVР (Model-view-presenter). 74
классов базы данных, 220
МWМ (Model-view-view model), 75
988 Предметный указатель
в данных, 783
установка времени истечения для кеша. 786
Веб-сервер Kestrel, 384
Кеширование, 783
Виджет, 281 Класс
Визуализация представлений. 659 ActionExecutedContext. 589
Внедрение в действия, 573 AddressSummary, 809
Внедрение в свойства. 573 Anchor'lagHelper. 781
Внедрение зависимостей (DI), 51; 543; AppldentityDbContext, 327
550:556 ApplicationDbContext, 362
для фильтров, 600 Appointment, 835
использование для конкретных типов. 563 Assert, 180
конфигурирование, 559 CacheThgHelper, 788
Выражение Cart, 263; 276
Razor. 128 CartController, 279
лямбда. 101 Controller, 443; 508; 520; 590
CustomeгController, 461
г CustomHtmlResult, 519
DbContext. 220
Генерация
DiagnosticsFilter, 604
URL. 478 DictionaryStorage. 561
списка категорий, 255
EFRepository, 362
EnvironmentтagHelper. 768
д ErrorMiddleware. 399
Данные ExceptionF!lterAttribute. 599
добавление данных модели представления, 232 FoгmThgHelper, 7 45
создание формы для ввода данных. 172 GuestResponse, 361
Действия,500 HeaderModel, 823
Диспетчер пакетов HomeController, 408; 430
Node (NPM), 351 HttpContext. 391
NuGet, 141; 197; 225 ImageThgHelper. 782
LegacyRoute, 482; 484; 489
з LinkТag, 779
Контроллер,33;70;500
асинхронные, 109
расширяющие, 97
Account. 332
фильтрующие, 100
АР!, 614 : 623
CRUD. 303 Механизм
для отображения сообщений об ошибках , 338 Razor, 654; 657; 658; 671
виэуалиэации,644
добавление контроллера, 213
создание. 117: 139;365: 617: 682;711;831 Миграция, 225
Конфигурацил маршрутизации, 212 Минификацил, 166
Конфигурирование Модель, 43
внедрения зависимостей, 559 создание модели, 545
поставщика служб. 558 представлений. 69
приложенил,327;369;713 привязка модел ей, 50; 270; 793; 799
числе: усовершенствованная
модель программирования
IDЕ-среды и архитектуры
MVVM для разработки
настольных приложений WPF;
многочисленные обновления
в ASP.NET Web API.
Помимо этого, предлагается
исчерпывающее рассмотрение