Язык программирования C#9 и платформа .NET5

Часть IX ASP.NET Core

Часть IX ASP.NET Core

Глава 29 Введение в ASP.NET Core

В финальной части книги рассматривается ASP.NET Core — последняя версия инфраструктуры для разработки веб-приложений, которая использует C# и .NET Core. В этой главе предлагается введение в инфраструктуру ASP.NET Core и раскрываются ее отличия от предыдущей версии, т.е. ASP.NET.

После ознакомления с основами паттерна "модель-представление-контроллер" (Model-View-Controller — MVC), реализованного в ASP.NET Core, вы приступите к построению двух приложений, которые будут работать вместе. Первое приложение, REST-служба ASP.NET Core, будет закончено в главе 30. Вторым приложением является веб-приложение ASP.NET Core, созданное с применением паттерна MVC, которое будет завершено в главе 31. Уровнем доступа к данным для обоих приложений послужат проекты

AutoLot.Dal
и
AutoLot.Models
, которые вы создали в главе 23.

Краткий экскурс в прошлое

Выпуск инфраструктуры ASP.NET MVC в 2007 году принес большой успех компании Microsoft. Инфраструктура базировалась на паттерне MVC и стала ответом разработчикам, разочарованным API-интерфейсом Web Forms, который по существу был ненадежной абстракцией поверх HTTP. Инфраструктура Web Forms проектировалась для того, чтобы помочь разработчикам клиент-серверных приложений перейти к созданию веб-приложений, и в этом отношении она была довольно успешной. Однако по мере того, как разработчики все больше и больше привыкали к процессу разработки веб-приложений, многим из них хотелось иметь более высокую степень контроля над визуализируемым выводом, избавиться от состояния представления и ближе придерживаться проверенных паттернов проектирования для веб- приложений. С учетом указанных целей и создавалась инфраструктура ASP.NET MVC.

Введение в паттерн MVC

Паттерн "модель-представление-контроллер" (Model-View-Controller — MVC) появился в 1970-х годах, будучи первоначально созданным для использования в Smalltalk. Относительно недавно его популярность возросла, в результате чего стали доступными реализации в различных языках, в том числе Java (Spring Framework), Ruby (Ruby on Rails) и .NET (ASP.NET MVC).

Модель

Модель — это данные в приложении. Данные обычно представляются с помощью простых старых объектов CLR (plain old CLR object — POCO). Модели представлений состоят из одной или большего числа моделей и приспособлены специально для потребителя данных. Воспринимайте модели и модели представлений как таблицы базы данных и представления базы данных.

С академической точки зрения модели должны быть в высшей степени чистыми и не содержать правила проверки достоверности или любые другие бизнес-правила. С практической точки зрения тот факт, содержит модель логику проверки достоверности или другие бизнес-правила, целиком зависит от применяемого языка и инфраструктур, а также специфических потребностей приложения. Например, в инфраструктуре EF Core присутствует много аннотаций данных, которые имеют двойное назначение: механизм для формирования таблиц базы данных и средство для проверки достоверности в веб-приложениях ASP.NET Core. Примеры, приводимые в книге, сконцентрированы на сокращении дублированного кода, что приводит к размещению аннотаций данных и проверок достоверности там, где в них есть наибольший смысл.

Представление

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

Контроллер

Контроллер является своего рода мозговым центром функционирования. Контроллеры принимают команды/запросы от пользователя (через представления) или клиента (через обращения к API-интерфейсу) посредством методов действий и надлежащим образом их обрабатывают. Результат операции затем возвращается пользователю или клиенту. Контроллеры должны быть легковесными и использовать другие компоненты или службы для обработки запросов, что содействует разделению обязанностей и улучшает возможности тестирования и сопровождения.

ASP.NET Core и паттерн MVC

С помощью ASP.NET Core можно создавать много типов веб-приложений и служб. Двумя вариантами являются веб-приложения, в которых применяются паттерн MVC и службы REST. Если вы имели дело с "классической" инфраструктурой ASP.NET, то знайте, что они аналогичны соответственно ASP.NET MVC и ASP.NET Web API. Типы веб-приложений МУС и приложений API разделяют часть "модель" и "контроллер" паттерна МУС, в то время как веб-приложения МУС также реализуют "представление", завершая паттерн МУС.

ASP.NET Core и .NET Core

Точно так же, как Entity Framework Core является полной переработкой Entity Framework 6, инфраструктура ASP.NET Core — это переработка популярной инфраструктуры ASP.NET Framework. Переписывание ASP.NET было нелегкой, но необходимой задачей, преследующей цель устранить зависимости от

System.Web
. Избавление от указанной зависимости позволило запускать приложения ASP.NET под управлением операционных систем, отличающихся от Windows, и веб-серверов помимо Internet Information Services (IIS), включая размещаемые самостоятельно. В итоге у приложений ASP.NET Core появилась возможность использовать межплатформенный, легковесный и быстрый веб-сервер с открытым кодом под названием Kestrel, который предлагает унифицированный подход к разработке для всех платформ.


На заметку! Изначально продукт Kestrel был основан на LibUV, но после выпуска ASP.NET Core 2.1 он базируется на управляемых сокетах.


Подобно EF Core инфраструктура ASP.NET Core разрабатывается в виде проекта с полностью открытым кодом на GitHub (

https://github.com/aspnet
). Она также спроектирована как модульная система пакетов NuGet. Разработчики устанавливают только те функциональные средства, которые нужны для конкретного приложения, сводя к минимуму пространство памяти приложения, сокращая накладные расходы и снижая риски в плане безопасности. В число дополнительных улучшений входят упрощенный запуск, встроенное внедрение зависимостей, более чистая система конфигурирования и подключаемое промежуточное программное обеспечение (ПО).

Одна инфраструктура, много сценариев использования

В оставшихся главах этой части вы увидите, что в ASP.NET Core внесено множество изменений и усовершенствований. Помимо межплатформенных возможностей еще одним значительным изменением следует считать унификацию инфраструктур для создания веб- приложений. В рамках ASP.NET Core инфраструктуры ASP.NET MVC, ASP.NET Web API и Razor Pages объединены в единую инфраструктуру для разработки. Разработка веб-приложений и служб с применением полной инфраструктуры .NET Framework предоставляла несколько вариантов, включая Web Forms, MVC, Web API, Windows Communication Foundation (WCF) и WebMatrix. Все они имели положительные и отрицательные стороны; одни были тесно связаны между собой, а другие сильно отличались друг от друга. Наличие стольких доступных вариантов означало, что разработчики обязаны были знать каждый из них для выбора того, который подходит для имеющейся задачи, или просто отдавать предпочтение какому-то одному и положиться на удачу.

С помощью ASP.NET Core вы можете строить приложения, которые используют Razor Pages, паттерн MVC, службы REST и одностраничные приложения, где применяются библиотеки JavaScript вроде Angular либо React. Хотя визуализация пользовательского интерфейса зависит от выбора между MVC, Razor Pages или библиотеками JavaScript, лежащая в основе среда разработки остается той же самой. Двумя прежними вариантами, которые не были перенесены в ASP.NET Core, оказались Web Forms и WCF.


На заметку! Поскольку все обособленные инфраструктуры объединены вместе под одной крышей, прежние названия ASP.NET MVC и ASP.NET Web API официально были изъяты из употребления. Для простоты в этой книге веб-приложения ASP . NET Core, использующие паттерн "модель-представление-контроллер", упоминаются как MVC, а REST-службы ASP.NET - как Web API.

Функциональные средства ASP.NET Core из MVC/Web API

Многие проектные цели и функции, которые побудили разработчиков применять ASP.NET MVC и ASP.NET Web API, по-прежнему поддерживаются в ASP.NET Core (и были улучшены).

Ниже перечислены некоторые из них (но далеко не все):

• соглашения по конфигурации (convention over configuration; или соглашение над конфигурацией, если делать акцент на преимуществе соглашения перед конфигурацией);

• контроллеры и действия;

• привязка моделей;

• проверка достоверности моделей;

• маршрутизация;

• фильтры;

• компоновки и представления Razor.


Они рассматриваются в последующих разделах за исключением компоновок и представлений Razor, которые будут раскрыты в главе 31.

Соглашения по конфигурации

ASP.NET MVC и ASP .NET Web API сократили объем необходимой конфигурации за счет введения ряда соглашений. В случае соблюдения таких соглашений уменьшается объем ручного (или шаблонного) конфигурирования, но при этом разработчики обязаны знать соглашения, чтобы использовать их в своих интересах. Двумя главными видами соглашений являются соглашения об именовании и структура каталогов.

Соглашения об именовании

В ASP.NET Core существует множество соглашений об именовании, предназначенных для приложений MVC и API. Например, контроллеры обычно содержат суффикс

Controller
в своих именах (скажем,
HomeController
) и вдобавок порождены от класса
Controller
(или
ControllerBase
). При доступе через маршрутизацию суффикс
Controller
отбрасывается. При поиске представлений контроллера отправной точкой поиска будет имя контроллера без суффикса. Такое соглашение об отбрасывании суффикса встречается повсюду в ASP.NET Core. В последующих главах вы встретите немало примеров.

Еще одно соглашение об именовании применяется относительно местоположения и выбора представлений. По умолчанию метод действия (в приложении MVC) будет визуализировать представление с таким же именем, как у метода. Шаблоны редактирования и отображения именуются в соответствии с классом, который они визуализируют в представлении. Описанное стандартное поведение может быть изменено, если того требует ваше приложение. Все это будет дополнительно исследоваться при построении приложения

AutoLot.Mvc
.

Структура каталогов

Существует несколько соглашений о папках, которые вы должны понимать, чтобы успешно создавать веб-приложения и службы ASP.NET Core.

Папка Controllers

По соглашению папка

Controllers
является тем местом, где реализации ASP.NET Core MVC и API (а также механизм маршрутизации) ожидают обнаружить контроллеры для вашего приложения.

Папка Views

В папке

Views
хранятся представления для приложения. Каждый контроллер получает внутри главной папки
Views
собственную папку с таким же именем, как у контроллера (исключая суффикс
Controller
). По умолчанию методы действий будут визуализировать представления из папки своего контроллера. Например, папка Views/Home содержит все представления для класса контроллера
HomeController
.

Папка Shared

Внутри папки

Views
имеется специальная папка по имени
Shared
, которая доступна всем контроллерам и их методам действий. Если представление не удалось найти в папке с именем контроллера, тогда поиск представления продолжается в папке
Shared
.

Папка wwwroot (нововведение в ASP.NET Core)

Улучшением по сравнению ASP.NET MVC стало создание особой папки по имени

wwwroot
для веб-приложений ASP.NET Core. В ASP.NET MVC файлы JavaScript, изображений, CSS и другое содержимое клиентской стороны смешивалось в остальных папках. В ASP.NET Core все файлы клиентской стороны содержатся в папке wwwroot. Такое отделение скомпилированных файлов от файлов клиентской стороны значительно проясняет структуру проекта при работе с ASP.NET Core.

Контроллеры и действия

Как и в ASP.NET MVC и ASP.NET Web API, контроллеры и методы действий являются основополагающими компонентами приложения ASP.NET Core MVC или API.

Класс Controller

Ранее уже упоминалось, что инфраструктура ASP.NET Core унифицировала ASP.NET MVC5 и ASP.NETWeb API. Такая унификация также привела к объединению базовых классов

Controller.ApiController
и
AsyncController
из MVC5 и Web API 2.2 в один новый класс
Controller
, который имеет собственный базовый класс по имени
ControllerBase
. Контроллеры веб-приложений ASP.NET Core наследуются от класса
Controller
, тогда как контроллеры служб ASP.NET — от класса
ControllerBase
(рассматривается следующим). Класс
Controller
предлагает множество вспомогательных методов для веб-приложений, наиболее часто используемые из которых перечислены в табл. 29.1.


Класс ControllerBase

Класс

ControllerBase
обеспечивает основную функциональность для веб-приложений и служб ASP.NET Core, и вдобавок предлагает вспомогательные методы для возвращения кодов состояния HTTP. В табл. 29.2 описана основная функциональность класса
ControllerBase
, а в табл. 29.3 перечислены некоторые вспомогательные методы для возвращения кодов состояния HTTP.



Действия

Действия — это методы в контроллере, которые возвращают экземпляр типа

IActionResult
(или
Task
для асинхронных операций) либо класса, реализующего
IActionResult
, такого как
ActionResult
или
ViewResult
. Действия будут подробно рассматриваться в последующих главах.

Привязка моделей

Привязка моделей представляет собой процесс, в рамках которого инфраструктура ASP.NET Core использует пары "имя-значение", отправленные НТТР-методом POST, для присваивания значений свойствам моделей. Для привязки к ссылочному типу пары "имя-значение" берутся из значений формы или тела запроса, ссылочные типы обязаны иметь открытый стандартный конструктор, а свойства, участвующие в привязке, должны быть открытыми и допускать запись. При присваивании значений везде, где это применимо, используются неявные преобразования типов (вроде установки значения свойства

string
в значение
int
). Если преобразование типа терпит неудачу, тогда такое свойство помечается как имеющее ошибку Прежде чем начать подробное обсуждение привязки, важно понять предназначение словаря
ModelState
и его роль в процессе привязки (а также проверки достоверности).

Словарь ModelState

Словарь

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


На заметку! Проверка достоверности модели, которая тоже устанавливает записи словаря

ModelState
, происходит после привязки модели. Как неявная, так и явная привязка модели автоматически вызывает проверку достоверности для модели. Проверка достоверности рассматривается в следующем разделе.

Добавление специальных ошибок в словарь ModelState

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

ModelState
можно добавлять специальные ошибки. Ошибки могут добавляться на уровне свойств или целой модели. Чтобы добавить специфическую ошибку для свойства (например, свойства
PetName
сущности
Car
), применяйте такой код:


ModelState.AddModelError("PetName","Name is required");


Чтобы добавить ошибку для целой модели, указывайте в качестве имени свойства

string.Empty
:


ModelState.AddModelError(string.Empty, $"Unable to create record: {ex.Message}");

Неявная привязка моделей

Неявная привязка моделей происходит, когда привязываемая модель является параметром для метода действия. Для сложных типов она использует рефлексию и рекурсию, чтобы сопоставить имена записываемых свойств модели с именами, которые содержатся в парах "имя-значение", отравленных методу действия. При наличии совпадения имен средство привязки применяет значение из пары "имя-значение", чтобы попробовать установить значение свойства. Если совпадение дают сразу несколько имен из пар "имя-значение", тогда используется значение первого совпавшего имени. Если имя не найдено, то свойство устанавливается в стандартное значение для его типа. Вот как выглядит порядок поиска пар "имя-значение":

• значения формы из HTTP-метода POST (включая отправки JavaScript AJAX);

• тело запроса (для контроллеров API);

• значения маршрута, предоставленные через маршрутизацию ASP.NET Core (для простых типов);

• значения строки запроса (для простых типов);

• загруженные файлы (для типов

IFormFile
).


Например, следующий метод будет пытаться установить все свойства в типе

Car
. Если процесс привязки завершается без ошибок, тогда свойство
ModelState.IsValid
возвращает
true
.


[HttpPost]

public ActionResult Create(Car entity)

{

  if (ModelState.IsValid)

  {

   // Сохранить данные.

  }

}

Явная привязка моделей

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

TryUpdateModelAsync()
с передачей ему экземпляра привязываемого типа и списка свойств, подлежащих привязке. Если привязка модели терпит неудачу, тогда метод возвращает
false
и устанавливает ошибки в
ModelState
аналогично неявной привязке. При использовании явной привязки моделей привязываемый тип не является параметром метода действия. Скажем, вы могли бы переписать предыдущий метод
Create()
с применением явной привязки:


[HttpPost]

public async Task Create()

{

  var vm = new Car();

  if (await TryUpdateModelAsync(vm,"",

   c=>c.Color,c=>c.PetName,c=>c.MakeId, c=>c.TimeStamp))

  {

   // Делать что-то важное.

  }

}

Атрибут Bind

Атрибут

Bind
в HTTP-методах POST позволяет ограничить свойства, которые участвуют в привязке модели, или установить префикс для имени в парах "имя-значение". Ограничение свойств, которые могут быть привязаны, снижает опасность атак избыточной отправкой (over-posting attack). Если атрибут
Bind
помещен на ссылочный параметр, то значения будут присваиваться через привязку модели только тем полям, которые перечислены в списке
Include
. Если атрибут
Bind
не используется, тогда привязку допускают все поля.

В следующем примере метода действия

Create()
все поля экземпляра
Car
доступны для привязки, поскольку атрибут
Bind
не применяется:


[HttpPost]

[ValidateAntiForgeryToken]

public IActionResult Create(Car car)

{

  if (ModelState.IsValid)

  {

   // Добавить запись.

  }

   // Позволить пользователю повторить попытку.

}


Пусть в ваших бизнес-требованиях указано, что методу

Create()
разрешено обновлять только поля
PetName
и
Color
. Добавление атрибута
Bind
(как показано в примере ниже) ограничивает свойства, участвующие в привязке, и инструктирует средство привязки моделей о том, что остальные свойства должны игнорироваться.


[HttpPost]

[ValidateAntiForgeryToken]

public ActionResult Create(

[Bind(nameof(Car.PetName),nameof(Car.Color))]Car car)

{

  if (ModelState.IsValid)

  {

   // Сохранить данные.

  }

   // Позволить пользователю повторить попытку.

}


Атрибут

Bind
можно также использовать для указания префикса имен свойств. Если имена в парах "имя-значение" имеют префикс, добавленный при их отправке методу действия, тогда атрибут
Bind
применяется для информирования средства привязки моделей о том, как сопоставлять эти имена со свойствами типа. Код в следующем примере устанавливает префикс для имен и позволяет привязывать все свойства:


[HttpPost]

[ValidateAntiForgeryToken]

public ActionResult Create(

[Bind(Prefix="MakeList")]Car car)

{

  if (ModelState.IsValid)

  {

   // Сохранить данные.

  }

}

Управление источниками привязки моделей в ASP.NET Core

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


Проверка достоверности моделей

Проверка достоверности происходит немедленно после привязки модели (явной и неявной). В то время как привязка модели добавляет ошибки в словарь

ModelState
из-за возникновения проблем преобразования, проверка достоверности добавляет ошибки в
ModelState
на основе бизнес-правил. Примерами бизнес-правил могут быть обязательные поля, строки с максимально разрешенной длиной или даты с заданным допустимым диапазоном.

Правила проверки достоверности устанавливаются через атрибуты проверки достоверности, встроенные или специальные. В табл. 29.5 кратко описаны наиболее часто используемые встроенные атрибуты проверки достоверности. Обратите внимание, что некоторые их них также служат аннотациями данных для формирования сущностей EF Core.



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

Маршрутизация

Маршрутизация — это способ, которым ASP.NET Core сопоставляет HTTP-запросы с контроллерами и действиями (исполняемые конечные точки) в вашем приложении взамен старого процесса отображения URL на структуру файлов проекта, принятого в Web Forms. Она также предлагает механизм для создания URL изнутри приложения на основе таких конечных точек. Конечная точка в приложении MVC или Web API состоит из контроллера, действия (только MVC), метода HTTP и необязательных значений (называемых значениями маршрута).


На заметку! Маршруты также применяются к страницам Razor, SignaIR, службам gRPC и т.д. В этой книге рассматриваются контроллеры стиля MVC и Web API.


Инфраструктура ASP.NET Core использует промежуточное ПО маршрутизации для сопоставления URL входящих запросов и для генерирования URL, отправляемых в ответах. Промежуточное ПО регистрируется в классе

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

Шаблоны URL и маркеры маршрутов

Конечные точки маршрутизации состоят из шаблонов URL, включающих в себя переменные-заполнители (называемые маркерами) и литералы, которые помещены в упорядоченную коллекцию, известную как таблица маршрутов. Каждая запись в ней определяет отличающийся шаблон URL, предназначенный для сопоставления. Заполнители могут быть специальными переменными или браться из заранее определенного списка. Зарезервированные маркеры маршрутов перечислены в табл. 29.6.



В дополнение к зарезервированным маркерам маршруты могут содержать специальные маркеры, которые отображаются (процессом привязки моделей) на параметры методов действий.

Маршрутизация и REST-службы ASP.NET Core

При определении маршрутов для служб ASP.NET метод действия не указывается. Вместо этого, как только контроллер обнаруживается, выполняемый метод действия базируется на методе HTTP запроса и назначениях методов HTTP методам действий. Детали будут приведены чуть позже.

Маршрутизация на основе соглашений

При маршрутизации на основе соглашений (или традиционной маршрутизации) таблица маршрутов строится в методе

UseEndpoints()
класса
Startup
. Метод
MapControllerRoute()
добавляет конечную точку в таблицу маршрутов, указывая имя, шаблон URL и любые стандартные значения для переменных в шаблоне URL. В приведенном ниже примере кода заранее определенные заполнители
{controller}
и
{action}
ссылаются на контроллер и метод действия, содержащийся в данном контроллере. Заполнитель
{id}
является специальным и транслируется в параметр (по имени
id
) для метода действия. Добавление к маркеру маршрута знака вопроса указывает на его необязательность.


app.UseEndpoints(endpoints =>

{

  endpoints.MapControllerRoute(

   name: "default",

   template: "{controller=Home}/{action=Index}/{id?}");

});


Запрашиваемый URL проверяется на соответствие с таблицей маршрутов. При наличии совпадения выполняется код, находящийся в этой конечной точке приложения. Примером URL, который мог бы обслуживаться таким маршрутом, является

Car/Delete/5
. В результате вызывается метод действия
Delete()
класса контроллера
CarController
с передачей значения
5
в параметре
id
.

В параметре

default
указано, каким образом заполнять пустые фрагменты в URL, которые содержат не все определенные компоненты. С учетом предыдущего кода, если в URL ничего не задано (например,
http://localhost:5001
), тогда механизм маршрутизации вызовет метод действия
Index()
класса
HomeController
без параметра
id
. Параметру
default
присуща поступательность, т.е. он допускает исключение справа налево. Однако пропускать части маршрута не разрешено. Ввод URL вида
http://localhost:5001/Delete/5
не пройдет сопоставление с шаблоном
{controller}/{action}/{id}
.

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

AmbiguousMatchException
.

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

https://www.skimedic.com
, то протокол (HTTPS) и имя хоста (
www.skimedic.com
) автоматически добавляются к маршруту при его создании (скажем,
https://www.skimedic.com/Car/Delete/5
). Для входящего запроса механизм маршрутизации использует порцию
Car/Delete/5
из URL.

Именованные маршруты

Имена маршрутов могут применяться в качестве сокращения для генерации URL изнутри приложения. Выше конечной точке было назначено имя

default
.

Маршрутизация с помощью атрибутов

При маршрутизации с помощью атрибутов маршруты определяются с использованием атрибутов C# в отношении контроллеров и их методов действий. Это может привести к более точной маршрутизации, но также увеличит объем конфигурации, поскольку для каждого контроллера и действия необходимо указать информацию маршрутизации.

Например, взгляните на приведенный ниже фрагмент кода. Четыре атрибута

Route
на методе действия
Index()
эквивалентны маршруту, который был определен ранее. Метод действия
Index()
является конечной точкой приложения для
mysite.com
,
mysite.com/Home
,
mysite.com/Home/Index
или
mysite.com/Home/Index/5
.


public class HomeController : Controller

{

  [Route("/")]

  [Route("/Home")]

  [Route("/Home/Index")]

  [Route("/Home/Index/{id?}")]

  public IActionResult Index(int? id)

  {

   ...

  }

}


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

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


public class CarController : Controller

{

  public IActionResult Delete(int id)

  {

   ...

  }

}


На заметку! Маршрутизацию на основе соглашений и маршрутизацию с помощью атрибутов можно использовать вместе. Если бы в методе

UseEndpoints()
был настроен стандартный маршрут контроллера (как в примере с маршрутизацией на основе соглашений), то предыдущий контроллер попал бы в таблицу маршрутов.


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

Delete()
и любые другие методы действий:


[Route("[controller]/[action]/{id?}")]

public class CarController : Controller

{

  public IActionResult Delete(int id)

  {

   ...

  }

}


На заметку! При маршрутизации с помощью атрибутов встроенные маркеры помечаются квадратными скобками (

[]
), а не фигурными (
{}
), как при маршрутизации на основе соглашений. Для специальных маркеров применяются все те же фигурные скобки.


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

/
). Скажем, если метод
Delete()
должен следовать шаблону URL вида
mysite.eom/Delete/Car/5
, то вот как понадобится сконфигурировать действие:


[Route("[controller]/[action]/{id?}")]

public class CarController : Controller

{

  [Route("/[action]/[controller]/{id}")]

  public IActionResult Delete(int id)

  {

   ...

  }

}


В маршрутах также можно жестко кодировать значения маршрутов вместо замены маркеров. Показанный ниже код даст тот же самый результат, как и предыдущий:


[Route("[controller]/[action]/{id?}")]

public class CarController : Controller

{

  [Route("/Delete/Car/{id}")]

  public IActionResult Delete(int id)

  {

   ...

  }

}

Именованные маршруты

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

GetOrderDetails
:


[HttpGet("{orderId}", Name = "GetOrderDetails")]

Маршрутизация и методы HTTP

Вы могли заметить, что ни в одном определении шаблона маршрута для методов не присутствует какой-нибудь метод HTTP. Причина в том, что механизм маршрутизации (в приложениях MVC и API) для выбора надлежащей конечной точки приложения использует шаблон маршрута и метод HTTP совместно.

Методы HTTP при маршрутизации в веб-приложениях (MVC)

Довольно часто при построении веб-приложений с применением паттерна MVC соответствовать определенному шаблону маршрута будут две конечные точки приложения. Средством различения в таких ситуациях является метод HTTP. Скажем, если

CarController
содержит два метода действий с именем
Delete()
и они оба соответствуют шаблону маршрута, то выбор метода для выполнения основывается на методе HTTP, который используется в запросе. Первый метод
Delete()
декорируется атрибутом
HttpGet
и будет выполняться, когда входящим запросом является
GET
. Второй метод
Delete()
декорируется атрибутом
HttpPost
и будет выполняться, когда входящим запросом является
POST
:


[Route("[controller]/[action]/{id?}")]

public class CarController : Controller

{

  [HttpGet]

  public IActionResult Delete(int id)

  {

   ...

  }

  [HttpPost]

  public IActionResult Delete(int id, Car recordToDelete)

  {

   ...

  }

}


Маршруты можно модифицировать также с применением атрибутов методов HTTP, а не атрибута

Route
. Например, ниже показан необязательный маркер маршрута
id
, добавленный в шаблон маршрута для обоих методов
Delete()
:


[Route("[controller]/[action] ")]

public class CarController : Controller

{

  [HttpGet("{id?}")]

  public IActionResult Delete(int? id)

  {

   ...

  }

  [HttpPost("{id}")]

  public IActionResult Delete(int id, Car recordToDelete)

  {

   ...

  }

}


Маршруты можно перезапускать с использованием методов HTTP; понадобится просто предварить шаблон маршрута символом прямой косой черты (

/
), как демонстрируется в следующем примере:


[HttpGet("/[controller]/[action]/{makeId}/{makeName}")]

public IActionResult ByMake(int makeId, string makeName)

{

  ViewBag.MakeName = makeName;

  return View(_repo.GetAllBy(makeId));

}


На заметку! Если метод действия не декорирован каким-либо атрибутом метода HTTP, то по умолчанию принимается метод

GET
. Тем не менее, в веб-приложениях MVC непомеченные методы действий могут также реагировать на запросы
POST
. По этой причине рекомендуется явно помечать все методы действий подходящим атрибутом метода HTTP.

Маршрутизация для служб API

Существенное различие между определениями маршрутов, которые применяются для приложений в стиле MVC, и определениями маршрутов, которые используются для служб REST, заключается в том, что в определениях маршрутов для служб не указываются методы действий. Методы действий выбираются на основе метода HTTP запроса (и необязательно типа содержимого), но не по имени. Ниже приведен код контроллера API с четырьмя методами, которые все соответствуют одному и тому же шаблону маршрута. Обратите внимание на атрибуты методов HTTP:


[Route("api/[controller]")]

[ApiController]

public class CarController : ControllerBase

{

  [HttpGet("{id}")]

  public IActionResult GetCarsById(int id)

  {

   ...

  }

  [HttpPost]

  public IActionResult CreateANewCar(Car entity)

  {

   ...

  }

  [HttpPut("{id}")]

  public IActionResult UpdateAnExistingCar(int id, Car entity)

  {

   ...

  }

  [HttpDelete("{id}")]

  public IActionResult DeleteACar(int id, Car entity)

  {

   ...

  }

}

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

GET
. В случае если запрос соответствует маршруту, но метод действия с корректным атрибутом метода HTTP отсутствует, тогда сервер возвратит ошибку 404 (не найдено).


На заметку! Инфраструктура ASP.NET Web API позволяет не указывать метод HTTP для метода действия, если его имя начинается с

Get
,
Put
,
Delete
или
Post
. Следование такому соглашению обычно считалось плохой идеей и в ASP.NET Core оно было удалено. Если для метода действия не указан метод HTTP, то он будет вызываться с применением НТТР-метода
GET
.


Последним селектором конечных точек для контроллеров API является необязательный атрибут

Consumes
, который задает тип содержимого, принимаемый конечной точкой. В запросе должен использоваться соответствующий заголовок
content-type
, иначе будет возвращена ошибка 415 Unsupported Media Туре (неподдерживаемый тип носителя). Следующие два примера конечных точек внутри одного и того же контроллера проводят различие между JSON и XML:


[HttpPost]

[Consumes("application/json")]

public IActionResult PostJson(IEnumerable values) =>

  Ok(new { Consumes = "application/json", Values = values });

[HttpPost]

[Consumes("application/x-www-form-urlencoded")]

public IActionResult PostForm([FromForm] IEnumerable values) =>

  Ok(new { Consumes = "application/x-www-form-urlencoded", Values = values });

Перенаправление с использованием маршрутизации

Еще одно преимущество маршрутизации связано с тем, что вам больше не придется жестко кодировать URL для других страниц в своем сайте. Записи в таблице маршрутов применяются для сопоставления с входящими запросами, а также для построения URL. При построении URL схема, хост и порт добавляются на основе значений текущего запроса.

Фильтры

Фильтры в ASP.NET Core запускают код до или после специфической фазы конвейера обработки запросов. Существуют встроенные фильтры для авторизации и кеширования, а также возможность назначения специальных фильтров. В табл. 29.7 описаны типы фильтров, которые могут быть добавлены в конвейер, перечисленные в порядке их выполнения.


Фильтры авторизации

Фильтры авторизации работают с системой ASP.NET Core Identity, чтобы предотвратить доступ к контроллерам или действиям, использовать которые пользователь не имеет права. Создавать специальные фильтры авторизации не рекомендуется, поскольку встроенные классы

AuthorizeAttribute
и
AllowAnonymousAttribute
обычно обеспечивают достаточный охват в случае применения ASP.NET Core Identity.

Фильтры ресурсов

Код "перед" выполняется после фильтров авторизации и до любых других фильтров, а код "после" выполняется после всех остальных фильтров. Таким образом, фильтры ресурсов способны замкнуть накоротко целый конвейер запросов. Обычно фильтры ресурсов используются для кеширования. Если ответ находится в кеше, тогда фильтр может пропустить остаток конвейера.

Фильтры действий

Код "перед" выполняется немедленно перед выполнением метода действия, а код "после" выполняется сразу после выполнения метода действия. Фильтры действий могут замкнуть накоротко метод действия и любые фильтры, помещенные внутрь фильтров действий.

Фильтры исключений

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

Фильтры результатов

Фильтры результатов завершают выполнение экземпляра реализации

IActionResult
для метода действия. Распространенный сценарий применения фильтра результатов предусматривает добавление с его помощью информации заголовка в сообщение ответа HTTP.

Нововведения в ASP.NET Core

Помимо поддержки базовой функциональности ASP.NET MVC и ASP.NET Web API разработчики ASP.NET Core сумели добавить множество новых средств и улучшений в сравнении с предшествующими инфраструктурами. В дополнение к унификации инфраструктур и контроллеров появились следующие усовершенствования и инновации:

• встроенное внедрение зависимостей:

• система конфигурации, основанная на среде и готовая к взаимодействию с облачными технологиями;

• легковесный, высокопроизводительный и модульный конвейер запросов HTTP.

• вся инфраструктура основана на мелкозернистых пакетах NuGet;

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

• введение вспомогательных функций дескрипторов;

• введение компонентов представлений;

• огромные улучшения в плане производительности.

Встроенное внедрение зависимостей

Внедрение зависимостей (dependency injection — DI) представляет собой механизм для поддержки слабой связанности между объектами. Вместо создания зависимых объектов напрямую или передачи специфических реализаций в классы и/или методы параметры определяются как интерфейсы. Таким образом, классам или методам и классам могут передаваться любые реализации интерфейсов, что разительно увеличивает гибкость приложения.

Поддержка DI — один из главных принципов, заложенных в переписанную версию ASP.NET Core. Все службы конфигурации и промежуточного ПО через внедрение зависимостей получает не только класс

Startup
(рассматриваемый позже в главе); ваши специальные классы могут (и должны) быть добавлены в контейнер DI с целью внедрения в другие части приложения. При конфигурировании элемента в контейнере ASP.NET Core DI доступны три варианта времени существования, кратко описанные в табл. 29.8.



Элементы в контейнере DI могут быть внедрены внутрь конструкторов и методов классов, а также в представления Razor.


На заметку! Если вы хотите использовать другой контейнер DI, то имейте в виду, что инфраструктура ASP.NET Core проектировалась с учетом такой гибкости. Чтобы узнать, как подключить другой контейнер DI, обратитесь в документацию по ссылке

https://docs.microsoft.com/ru-ru/aspnet/core/fundamentals/dependency-injection
.

Осведомленность о среде

Осведомленность приложений ASP. NET Core об их среде выполнения включает переменные среды хоста и местоположения файлов через экземпляр реализации

IWebHostEnvironment
. В табл. 29.9 описаны свойства, доступные в этом интерфейсе.



Помимо доступа к важным файловым путям интерфейс

IWebHostEnvironment
применяется для выяснения среды времени выполнения.

Выяснение среды времени выполнения

Инфраструктура ASP.NET Core автоматически читает значение переменной среды по имени

ASPNETCORE_ENVIRONMENT
, чтобы установить среду времени выполнения. Если переменная
ASPNETCORE_ENVIRONMENT
не установлена, тогда ASP.NET Core устанавливает ее значение в
Production
(производственная среда). Установленное значение доступно через свойство
EnvironmentName
интерфейса
IWebHostEnvironment
.

Во время разработки приложений ASP.NET Core переменная

ASPNETCORE_ENVIRONMENT
обычно устанавливается с использованием файла настроек или командной строки. Последовательно идущие среды (подготовительная, производственная и т.д.), как правило, задействуют стандартные переменные среды операционной системы.

Вы можете применять для среды любое имя или одно из трех имен, которые предоставляются статическим классом

Environments
.


public static class Environments

{

  public static readonly string Development = "Development"; // среда разработки

  public static readonly string Staging = "Staging";     // подготовительная среда

  public static readonly string Production = "Production";  // производственная среда

}


Класс

HostEnvironmentEnvExtensions
предлагает расширяющие методы на
IHostEnvironment
для работы со свойством имени среды, которые описаны в табл. 29.10.



Ниже перечислены некоторые примеры использования настройки среды:

• выяснение, какие конфигурационные файлы загружать:

• установка параметров отладки, ошибок и ведения журнала:

• загрузка файлов JavaScript и CSS, специфичных для среды.


Вы увидите все это в действии при построении приложений

AutoLot.Api
и
AutoLot.Mvc
в последующих двух главах.

Конфигурация приложений

В предшествующих версиях ASP.NET для конфигурирования служб и приложений применялся файл

web.config
, и разработчики получали доступ к конфигурационным настройкам через класс
System.Configuration
. Разумеется, помещение в файл
web.config
всех конфигурационных настроек для сайта, а не только специфичных для приложения, делало его (потенциально) запутанной смесью.

В ASP.NET Core была введена значительно более простая система конфигурации. По умолчанию она основывается на простых файлах JSON, которые хранят конфигурационные настройки в виде пар "имя-значение". Стандартный файл для конфигурации называется

appsettings.json
. Начальная версия файла
appsettings.json
(созданная шаблонами для веб-приложения ASP.NET Core и службы API) просто содержит конфигурационную информацию для регистрации в журнале, а также настройку для ограничения хостов:


{

  "Logging": {

   "LogLevel": {

    "Default": "Information",

    "Microsoft": "Warning",

    "Microsoft.Hosting.Lifetime": "Information"

   }

  },

  "AllowedHosts": "*"

}


Шаблон также создает файл

appsettings.Development.json
. Система конфигурации работает в сочетании с осведомленностью о среде времени выполнения, чтобы загружать дополнительные конфигурационные файлы на основе среды времени выполнения. Цель достигается инструктированием системы конфигурации о необходимости загрузки файла с именем
appsettings.{имя_среды}.json
после файла
appSettings.json
. В случае запуска приложения в среде разработки после файла начальных настроек загружается файл
appsettings.Development.json
. Если запуск происходит в подготовительной среде, тогда загружается файл
appsettings.Staging.json
. Важно отметить, что при загрузке более одного файла любые настройки, присутствующие в нескольких файлах, переопределяются настройками из последнего загруженного файла; они не являются аддитивными. Все конфигурационные настройки получаются через экземпляр реализации
IConfiguration
, доступный посредством системы внедрения зависимостей ASP.NET Core.

Извлечение настроек

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

GetXXX()
, таких как
GetSection()
,
GetValue()
и т.д.:


Configuration.GetSection("Logging")


Также доступно сокращение для получения строк подключения:


Configuration.GetConnectionString("AutoLot")


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

Развертывание приложений ASP.NET Core

Приложения ASP.NET предшествующих версий могли развертываться только на серверах Windows с использованием IIS. Инфраструктуру ASP.NET Core можно разворачивать под управлением многочисленных операционных систем многими способами, в том числе и вне веб-сервера. Ниже перечислены высокоуровневые варианты:

• на сервере Windows (включая Azure) с применением IIS;

• на сервере Windows (включая службы приложений Azure) вне IIS;

• на сервере Linux с использованием Apache или NGINX;

• под управлением Windows или Linux в контейнере Docker.


Подобная гибкость позволяет организациям выбирать платформу развертывания, которая имеет набольший смысл для организации, включая популярные модели развертывания на основе контейнеров (скажем, Docker), и не ограничиваться серверами Windows.

Легковесный и модульный конвейер запросов HTTP

Следуя принципам .NET Core, все в ASP.NET Core происходит по подписке. По умолчанию в приложение ничего не загружается. Такой подход позволяет приложениям быть насколько возможно легковесными, улучшая производительность и сводя к минимуму объем их кода и потенциальный риск.

Создание и конфигурирование решения

Теперь, когда у вас есть опыт работы с рядом основных концепций ASP.NET Core, самое время приступить к построению приложений ASP.NET Core. Проекты ASP.NET Core можно создавать с применением либо Visual Studio, либо командной строки. Оба варианта будут раскрыты в последующих двух разделах.

Использование Visual Studio

Преимущество Visual Studio связано с наличием графического пользовательского интерфейса, который поможет вам пройти через процесс создания решения и проектов, добавления пакетов NuGet и создания ссылок между проектами.

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

Начните с создания нового проекта в Visual Studio. Выберите в диалоговом окне Create a new project (Создание нового проекта) шаблон C# под названием ASP.NET Core Web Application (Веб-приложение ASP.NET Core). В диалоговом окне Configure your new project (Конфигурирование нового проекта) введите

AutoLot.Api
в качестве имени проекта и
AutoLot
для имени решения (рис. 29.1).



На следующем экране выберите шаблон ASP.NET Core Web API, а выше в раскрывающихся списках — .NET Core и ASP.NET Core 5.0. Оставьте флажки внутри области Advanced (Дополнительно) в их стандартном состоянии (рис. 29.2).



Добавьте в решение еще один проект ASP.NET Core Web Application, выбрав шаблон ASP.NET Core Web Арр (Model-View-Controller) (Веб-приложение ASP.NET Core (модель-представление-контроллер)). Удостоверьтесь в том, что в раскрывающихся списках вверху выбраны варианты .NET Core и ASP.NET Core 5.0; оставьте флажки внутри области Advanced в их стандартном состоянии.

Наконец, добавьте в решение проект C# Class Library (.NET Core) (Библиотека классов C# (.NET Core)) и назначьте ему имя

AutoLot.Services
. Отредактируйте файл проекта, чтобы установить
TargetFramework
в
net 5.0
:


  net5.0

Добавление проектов AutoLot.Models и AutoLot.Dal

Решение требует завершенного уровня доступа к данным из главы 23. Вы можете либо скопировать файлы в каталог текущего решения, либо оставить их на месте. В любом случае вам нужно щелкнуть правой кнопкой мыши на имени решения в окне Solution Explorer, выбрать в контекстном меню пункт Add►Existing Project (Добавить►Существующий проект), перейти к файлу

AutoLot.Models.csproj
и выбрать его. Повторите такие же действия для проекта
AutoLot.Dal
.


На заметку! Хотя порядок добавления проектов в решение формально не имеет значения, среда Visual Studio сохранит ссылки между

AutoLot.Models
и
AutoLot.Dal
, если проект
AutoLot.Models
добавляется первым.

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

Добавьте указанные ниже ссылки на проекты, щелкнув правой кнопкой на имени проекта в окне Solution Explorer и выбрав в контекстном меню пункт Add►Project Reference (Добавить►Ссылка на проект) для каждого проекта.

Проекты

AutoLot.Api
и
AutoLot.Mvc
ссылаются на:

•

AutoLot.Models

•

AutoLot.Dal

•

AutoLot.Services


Проект

AutoLot.Services
ссылается на:

•

AutoLot.Models

•

AutoLot.Dal

Добавление пакетов NuGet

Для приложения необходимы дополнительные пакеты

NuGet
.

Добавьте перечисленные ниже пакеты в проект

AutoLot.Api
:

•

AutoMapper

•

System.Text.Json

•

Swashbuckle.AspNetCore.Annotations

•

Swashbuckle.AspNetCore.Swagger

•

Swashbuckle.AspNetCore.SwaggerGen

•

Swashbuckle.AspNetCore.SwaggerUI

•

Microsoft.VisualStudio.Web.CodeGeneration.Design

•

Microsoft.EntityFrameworkCore.SqlServer


На заметку! Благодаря шаблонам ASP.NET Core 5.0 API ссылка на

Swashbuckle.AspNetCore
уже присутствует. Указанные здесь пакеты
Swashbuckle
добавляют возможности за рамками базовой реализации.


Добавьте следующие пакеты в проект

AutoLot.Mvc
:

•

AutoMapper

•

System.Text.Json

•

LigerShark.WebOptimizer.Core

•

Microsoft.Web.LibraryManager.Build

•

Microsoft.VisualStudio.Web.CodeGeneration.Design

•

Microsoft.EntityFrameworkCore.SqlServer


Добавьте указанные ниже пакеты в проект

AutoLot.Services
:

•

Microsoft.Extensions.Hosting.Abstractions

•

Microsoft.Extensions.Options

•

Serilog.AspNetCore

•

Serilog.Enrichers.Environment

•

Serilog.Settings.Configuration

•

Serlog.Sinks.Console

•

Serilog.Sinks.File

•

Serilog.Sinks.MSSqlServer

•

System.Text.Json

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

Как было показано ранее в книге, проекты и решения .NET Core можно создавать с применением командной строки. Откройте окно командной строки и перейдите в каталог, куда вы хотите поместить решение.


На заметку! В приводимых далее командах используется разделитель каталогов Windows. Если вы работаете не в среде Windows, тогда должным образом скорректируйте разделитель.


Создайте решение

AutoLot
и добавьте в него существующие проекты
AutoLot.Models
и
AutoLot.Dal
:


rem Создать решение

dotnet new sln -n AutoLot

rem Добавить в решение проекты

dotnet sln AutoLot.sln add ..\Chapter_23\AutoLot.Models

dotnet sln AutoLot.sln add ..\Chapter_23\AutoLot.Dal


Создайте проект

AutoLot.Services
, добавьте его в решение, добавьте пакеты NuGet и добавьте ссылки на проекты:


rem Создать библиотеку классов для служб приложения и добавить ее в решение

dotnet new classlib -lang c# -n AutoLot.Services -o .\AutoLot.Services -f net5.0

dotnet sln AutoLot.sln add AutoLot.Services


rem Добавить пакеты

dotnet add AutoLot.Services package Microsoft.Extensions.Hosting.Abstractions

dotnet add AutoLot.Services package Microsoft.Extensions.Options

dotnet add AutoLot.Services package Serilog.AspNetCore

dotnet add AutoLot.Services package Serilog.Enrichers.Environment

dotnet add AutoLot.Services package Serilog.Settings.Configuration

dotnet add AutoLot.Services package Serilog.Sinks.Console

dotnet add AutoLot.Services package Serilog.Sinks.File

dotnet add AutoLot.Services package Serilog.Sinks.MSSqlServer

dotnet add AutoLot.Services package System.Text.Json


rem Добавить ссылки на проекты

dotnet add AutoLot.Services reference ..\Chapter_23\AutoLot.Models

dotnet add AutoLot.Services reference ..\Chapter_23\AutoLot.Dal


Создайте проект

AutoLot.Api
, добавьте его в решение, добавьте пакеты NuGet и добавьте ссылки на проекты:


dotnet new webapi -lang c# -n AutoLot.Api -au none -o .\AutoLot.Api -f net5.0

dotnet sln AutoLot.sln add AutoLot.Api


rem Добавить пакеты

dotnet add AutoLot.Api package AutoMapper

dotnet add AutoLot.Api package Swashbuckle.AspNetCore

dotnet add AutoLot.Api package Swashbuckle.AspNetCore.Annotations

dotnet add AutoLot.Api package Swashbuckle.AspNetCore.Swagger

dotnet add AutoLot.Api package Swashbuckle.AspNetCore.SwaggerGen

dotnet add AutoLot.Api package Swashbuckle.AspNetCore.SwaggerUI

dotnet add AutoLot.Api package Microsoft.VisualStudio.Web.CodeGeneration.Design

dotnet add AutoLot.Api package Microsoft.EntityFrameworkCore.SqlServer

dotnet add AutoLot.Api package System.Text.Json


rem Добавить ссылки на проекты

dotnet add AutoLot.Api reference ..\Chapter_23\AutoLot.Dal

dotnet add AutoLot.Api reference ..\Chapter_23\AutoLot.Models

dotnet add AutoLot.Api reference AutoLot.Services


Создайте проект

AutoLot.Mvc
, добавьте его в решение, добавьте пакеты NuGet и добавьте ссылки на проекты:


dotnet new mvc -lang c# -n AutoLot.Mvc -au none -o .\AutoLot.Mvc -f net5.0

dotnet sln AutoLot.sln add AutoLot.Mvc


rem Добавить ссылки на проекты

dotnet add AutoLot.Mvc reference ..\Chapter_23\AutoLot.Models

dotnet add AutoLot.Mvc reference ..\Chapter_23\AutoLot.Dal

dotnet add AutoLot.Mvc reference AutoLot.Services


rem Добавить пакеты

dotnet add AutoLot.Mvc package AutoMapper

dotnet add AutoLot.Mvc package System.Text.Json

dotnet add AutoLot.Mvc package LigerShark.WebOptimizer.Core

dotnet add AutoLot.Mvc package Microsoft.Web.LibraryManager.Build

dotnet add AutoLot.Mvc package Microsoft.EntityFrameworkCore.SqlServer

dotnet add AutoLot.Mvc package Microsoft.VisualStudio.Web.CodeGeneration.Design


На этом настройка с применением командной строки завершена. Она намного эффективнее, если вы не нуждаетесь в графическом пользовательском интерфейсе Visual Studio.

Запуск приложений ASP.NET Core

Веб-приложения предшествующих версий ASP.NET всегда запускались с использованием IIS (или IIS Express). В ASP.NET Core приложения обычно запускаются с применением веб-сервера Kestrel, но существует вариант использования IIS, Apache, Nginx и т.д. через обратный прокси-сервер между Kestrel и другим веб-сервером. В результате происходит не только отход в сторону от строгого применения IIS, чтобы сменить модель развертывания, но и изменение возможностей разработки.

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

• из Visual Studio с использованием IIS Express;

• из Visual Studio с применением Kestrel;

• из командной строки .NET CLI с использованием Kestrel;

• из Visual Studio Code (VS Code) через меню Run (Запуск) с применением Kestrel;

• из VS Code через окно терминала с использованием .NET CLI и Kestrel.

Конфигурирование настроек запуска

Файл

launchsettings.json
(расположенный в узле Properties (Свойства) окна Solution Explorer) конфигурирует способ запуска приложения во время разработки под управлением Kestrel и IIS Express. Ниже приведено его содержимое в справочных целях (ваши порты IIS Express будут другими):


{

  "$schema": "http://json.schemastore.org/launchsettings.json",

  "iisSettings": {

   "windowsAuthentication": false,

   "anonymousAuthentication": true,

   "iisExpress": {

    "applicationUrl": "http://localhost:42788",

    "sslPort": 44375

   }

  },

  "profiles": {

   "IIS Express": {

    "commandName": "IISExpress",

    "launchBrowser": true,

    "launchUrl": "swagger",

    "environmentVariables": {

     "ASPNETCORE_ENVIRONMENT": "Development"

    }

   },

   "AutoLot.Api": {

    "commandName": "Project",

    "dotnetRunMessages": "true",

    "launchBrowser": true,

    "launchUrl": "swagger",

    "applicationUrl": "https://localhost:5001;http://localhost:5000",

    "environmentVariables": {

     "ASPNETCORE_ENVIRONMENT": "Development"

    }

   }

  }

}

Использование Visual Studio

В разделе

iisSettings
определены настройки запуска приложения с применением IIS Express в качестве веб-сервера. Наиболее важными настройками, на которые следует обратить внимание, являются настройка
applicationUrl
, определяющая порт, и блок
environmentVariables
, где определяется среда времени выполнения. При запуске приложения в режиме отладки эта настройка заменяет собой любую настройку среды машины. Второй профиль (
AutoLot.Mvc
или
AutoLot.Api
в зависимости от того, какой проект используется) определяет настройки для ситуации, когда приложение запускается с применением Kestrel в качестве веб-сервера. Профиль определяет
applicationUrl
и порты плюс среду.

Меню Run в Visual Studio позволяет выбрать либо IIS Express, либо Kestrel, как показано на рис. 29.3. После выбора профиля проект можно запустить, нажав <F5> (режим отладки), нажав <Ctrl+F5> (эквивалентно выбору пункта Start Without Debugging (Запустить без отладки) в меню Debug (Отладка)) или щелкнув на кнопке запуска с зеленой стрелкой (эквивалентно выбору пункта Start Debugging (Запустить с отладкой) в меню Debug).



На заметку! В случае запуска приложения из Visual Studio средства редактирования и продолжения больше не поддерживаются.

Использование командной строки или окна терминала Visual Studio Code

Чтобы запустить приложение из командной строки или окна терминала VS Code, перейдите в каталог, где находится файл

.csproj
для вашего приложения. Введите следующую команду для запуска приложения под управлением веб-сервера Kestrel:


dotnet run


Для завершения процесса нажмите <Ctrl+C>.

Изменение кода во время отладки

При запуске из командной строки код можно изменять, но изменения никак не будут отражаться в выполняющемся приложении. Чтобы изменения отражались в выполняющемся приложении, введите такую команду:


dotnet watch run


Обновленная команда вместе с вашим приложением запускает средство наблюдения за файлами. Когда в файлах любого проекта (или проекта, на который имеется ссылка) обнаруживаются изменения, приложение автоматически останавливается и затем снова запускается. Нововведением в версии ASP.NET Core 5 является перезагрузка любых подключенных окон браузера. Хотя в итоге средства редактирования и продолжения в точности не воспроизводятся, это немалое удобство при разработке.

Использование Visual Studio Code

Чтобы запустить проекты в VS Code, откройте каталог, где находится решение. После нажатия <F5> (или щелчка на кнопке запуска) среда VS Code предложит выбрать проект для запуска (

AutoLot.Api
или
AutoLot.Mvc
), создаст конфигурацию запуска и поместит ее в файл по имени
launch.json
. Кроме того, среда VS Code использует файл
launchsettings.json
для чтения конфигурации портов.

Изменение кода во время отладки

В случае запуска приложения из VS Code код можно изменять, но изменения никак не будут отражаться в выполняющемся приложении. Чтобы изменения отражались в выполняющемся приложении, введите в окне терминала команду

dotnet watch run
.

Отладка приложений ASP.NET Core

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

Присоединение с помощью Visual Studio

После запуска приложения (посредством команды

dotnet run
или
dotnet watch run
) выберите пункт меню Debug►Attach to Process (Отладкам►Присоединиться к процессу) в Visual Studio. В открывшемся диалоговом окне Attach to Process (Присоединение к процессу) отыщите процесс по имени вашего приложения (рис. 29.4).



После присоединения к выполняющемуся процессу вы можете устанавливать в Visual Studio точки останова, и отладка будет работать так, как ожидалось. Редактировать и продолжать выполнение не удастся; чтобы изменения отразились в функционирующем приложении, придется отсоединиться от процесса.

Присоединение с помощью Visual Studio Code

После запуска приложения (командой

dotnet run
или
dotnet watch run
) щелкните на кнопке запуска с зеленой стрелкой и выберите .NET Core Attach (Присоединение .NET Core) вместо .NET Core Launch (web) (Запуск .NET Core (веб)), как показано на рис. 29.5.



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

Преимущество использования среды VS Code заключается в том, что после ее присоединения (и применения команды

dotnet watch run
) вы можете обновлять свой код во время выполнения (без необходимости в отсоединении) и вносимые изменения будут отражаться в функционирующем приложении.

Обновление портов AutoLot.Api

Вы могли заметить, что приложения

AutoLot.Api
и
AutoLot.Mvc
имеют разные порты, указанные для их профилей IIS Express, но для обоих приложений порты Kestrel установлены в 5000 (HTTP) и 5001 (HTTPS), что вызовет проблемы, когда вы попытаетесь запустить приложения вместе. Измените порты для
AutoLot.Api
на 5020 (HTTP) и 5021 (HTTPS), например:


"AutoLot.Api": {

  "commandName": "Project",

 "launchBrowser": true,

 "launchUrl": "api/values",

 "applicationUrl": "https://localhost:5021;http://localhost:5020",

 "environmentVariables": {

 "ASPNETCORE_ENVIRONMENT": "Development"

 }

}

Создание и конфигурирование экземпляра WebHost

В отличие от классических приложений ASP.NET MVC или ASP.NET Web API приложения ASP.NET Core — это просто консольные приложения .NET Core, которые создают и конфигурируют экземпляр

WebHost
. Создание экземпляра
WebHost
и его последующее конфигурирование обеспечивают настройку приложения на прослушивание (и реагирование) на запросы HTTP. Экземпляр
WebHost
создается в методе
Main()
внутри файла
Program.cs
. Затем экземпляр
WebHost
конфигурируется для вашего приложения в файле
Startup.cs
.

Файл Program.cs

Откройте файл класса

Program.cs
в приложении
AutoLot.Api
и просмотрите его содержимое, которое для справки приведено ниже:


namespace AutoLot.Api

{

  public class Program

  {

   public static void Main(string[] args)

   {

   CreateHostBuilder(args).Build().Run();

   }

   public static IHostBuilder CreateHostBuilder(string[] args) =>

   Host.CreateDefaultBuilder(args)

    .ConfigureWebHostDefaults(webBuilder =>

     {

      webBuilder.UseStartup();

     });

  }

}


Метод

CreateDefaultBuilder()
ужимает наиболее типовую настройку приложения в один вызов. Он конфигурирует приложение (используя переменные среды и JSON-файлы
appsettings
), настраивает стандартный поставщик ведения журнала и устанавливает контейнер DI. Такая настройка обеспечивается шаблонами ASP.NET Core для приложений API и MVC.

Следующий метод,

ConfigureWebHostDefaults()
, тоже является мета-методом, который добавляет поддержку для Kestrel, IIS и дополнительные настройки. Финальный шаг представляет собой установку класса конфигурации, специфичной для приложения, который в данном примере (и по соглашению) называется
Startup
. Наконец, вызывается метод
Run()
для активизации экземпляра
WebHost
.

Помимо экземпляра

WebHost
в предыдущем коде создается экземпляр реализации
IConfiguration
, который добавляется внутрь контейнера DI.

Файл Startup.cs

Класс

Startup
конфигурирует то, как приложение будет обрабатывать запросы и ответы HTTP, настраивает необходимые службы и добавляет службы в контейнер DI. Имя класса может быть любым, если оно соответствует строке
UseStartup()
в конфигурации метода
CreateHostBuilder()
, но по соглашению класс именуется как
Startup
.

Доступные службы для класса Startup

Процессу запуска требуется доступ к инфраструктуре, а также к службам и настройкам среды, которые внедряются в класс инфраструктурой. Классу

Startup
доступно пять служб для конфигурирования приложения, которые кратко описаны в табл. 29.11.



Конструктор принимает экземпляр реализации

IConfiguration
и необязательный экземпляр реализации
IWebHostEnvironment/IHostEnvironment
. Метод
ConfigureServices()
запускается до того, как метод
Configure()
получает экземпляр реализации
IServiceCollection
. Метод
Configure()
должен принимать экземпляр реализации
IApplicationBuilder
, но может принимать экземпляры реализаций
IWebHostEnvironment/IHostEnvironment
,
ILoggerFactory
и любых интерфейсов, которые были добавлены внутрь контейнера DI в методе
ConfigureServices()
. Все перечисленные компоненты обсуждаются в последующих разделах.

Конструктор

Конструктор принимает экземпляр реализации интерфейса

IConfiguration
, который был создан методом
Host.CreateDefaultBuilder
в файле класса
Program.cs
, и присваивает его свойству
Configuration
для использования где-то в другом месте внутри класса. Конструктор также может принимать экземпляр реализации
IWebHostEnvironment
и/или
ILoggerFactory
, хотя он не добавляется в стандартном шаблоне.

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

IWebHostEnvironment
и присвойте его локальной переменной уровня класса. Это понадобится в методе
ConfigureServices()
. Проделайте такую же работу для приложений
AutoLot.Api
и
AutoLot.Mvc
.


private readonly IWebHostEnvironment _env;

public Startup(

  IConfiguration configuration, IWebHostEnvironment env)

{

  _env = env;

  Configuration = configuration;

}

Метод ConfigureServices()

Метод

ConfigureServices()
применяется для конфигурирования любых служб, необходимых приложению, и вставки их в контейнер DI. Сюда входят службы, требуемые для поддержки приложений MVC и служб API.

AutoLot.Api

Метод

ConfigureServices()
для API-интерфейса
AutoLot
по умолчанию конфигурируется с только одной службой, которая добавляет поддержку контроллеров. Благодаря этому мета-методу добавляется множество дополнительных служб, в том числе маршрутизация, авторизация, привязка моделей и все элементы, не относящиеся к пользовательскому интерфейсу, которые уже обсуждались в настоящей главе.


public void ConfigureServices(IServiceCollection services)

{

  services.AddControllers();

}


Метод

AddControllers()
может быть расширен, например, для настройки обработки JSON. По умолчанию для ASP.NET Core используется "верблюжий" стиль при обработке JSON (первая буква в нижнем регистре, а каждое последующее слово начинается с буквы верхнего регистра, скажем,
carRepo
). Это соответствует большинству инфраструктур производства не Microsoft, которые применяются для разработки веб-приложений. Однако в предшествующих версиях ASP.NET использовался стиль Pascal (например,
CarRepo
). Переход на "верблюжий" стиль был критическим изменением для многих приложений, которые ожидали стиля Pascal. Чтобы вернуть стиль Pascal при обработке JSON приложением (и улучшить форматирование разметки JSON), модифицируйте метод
AddControllers()
следующим образом:


public void ConfigureServices(IServiceCollection services)

{

  services.AddControllers()

   .AddJsonOptions(options =>

   {

    options.JsonSerializerOptions.PropertyNamingPolicy = null;

    options.JsonSerializerOptions.WriteIndented = true;

   });

}


Добавьте в файл

Startup.cs
перечисленные ниже операторы
using
:


using AutoLot.Dal.EfStructures;

using AutoLot.Dal.Initialization;

using AutoLot.Dal.Repos;

using AutoLot.Dal.Repos.Interfaces;

using Microsoft.EntityFrameworkCore;


Службам API необходим доступ к

ApplicationDbContext
и хранилищам внутри уровня доступа к данным. Существует встроенная поддержка для добавления EF Core в приложения ASP.NET Core. Добавьте следующий код в метод
ConfigureServices()
класса
Startup
:


var connectionString = Configuration.GetConnectionString("AutoLot");

services.AddDbContextPool(

  options => options.UseSqlServer(connectionString,

  sqlOptions => sqlOptions.EnableRetryOnFailure()));


Первая строка кода получает строку подключения из файла настроек (более подробно рассматривается позже). Следующая строка добавляет в контейнер DI пул экземпляров

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

Теперь необходимо добавить хранилища в контейнер DI. Вставьте в метод

ConfigureServices()
приведенный далее код после кода для конфигурирования
ApplicationDbContext
:


services.AddScoped();

services.AddScoped();

services.AddScoped();

services.AddScoped();

services.AddScoped();

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

Модифицируйте файл

appsettings.development.json
, как показано ниже, добавив строку подключения к базе данных. Обязательно включите запятую, отделяющую разделы, и приведите строку подключения в соответствие со своей средой.


{

  "Logging": {

   "LogLevel": {

    "Default": "Information",

    "Microsoft": "Warning",

    "Microsoft.Hosting.Lifetime": "Information"

   }

  },

  "ConnectionStrings": {

   "AutoLot": "Server=.,5433;Database=AutoLotFinal;

   User ID=sa;Password=P@ssw0rd;"

  }

}


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

appsettings.production.json
и обновите его следующим образом:


{

  "ConnectionStrings": {

   "AutoLot": "ITSASECRET"

  }

}


Это предохраняет реальную строку подключения от системы управления версиями и делает возможным замену маркера (

ITSASECRET
) в течение процесса разработки.

AutoLot.Mvc

Метод

ConfigureServices()
для веб-приложений MVC добавляет базовые службы для приложений API и поддержку визуализации представлений. Вместо вызова
AddControllers()
в приложениях MVC вызывается
AddControllersWithViews()
:


public void ConfigureServices(IServiceCollection services)

{

  services.AddControllersWithViews();

}

Добавьте в файл Startup.es показанные ниже операторы using:

using AutoLot.Dal.EfStructures;

using AutoLot.Dal.Initialization;

using AutoLot.Dal.Repos;

using AutoLot.Dal.Repos.Interfaces;

using Microsoft.EntityFrameworkCore;


Веб-приложение также должно использовать уровень доступа к данным. Добавьте в метод

ConfigureServices()
класса
Startup
следующий код:


var connectionString = Configuration.GetConnectionString("AutoLot");

services.AddDbContextPool(

  options => options.UseSqlServer(connectionString,

  sqlOptions => sqlOptions.EnableRetryOnFailure()));

services.AddScoped();

services.AddScoped();

services.AddScoped();

services.AddScoped();

services.AddScoped();


На заметку! Веб-приложение MVC будет работать как с уровнем доступа к данным, так и с API-интерфейсом для взаимодействия с данными, чтобы продемонстрировать оба механизма.

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

Модифицируйте файл

appsettings.development.json
, как показано ниже:


{

  "Logging": {

   "LogLevel": {

    "Default": "Information",

    "Microsoft": "Warning",

    "Microsoft.Hosting.Lifetime": "Information"

   }

  },

  "ConnectionStrings": {

   "AutoLot": "Server=.,5433;Database=AutoLotFinal;

   User ID=sa;Password=P@ssw0rd;"

  }

}

Метод Configure()

Метод

Configure()
применяется для настройки приложения на реагирование на запросы HTTP. Данный метод выполняется после метода
ConfigureServices()
, т.е. все, что добавлено в контейнер DI, также может быть внедрено в
Configure()
. Существуют различия в том, как приложения API и MVC конфигурируются для обработки запросов и ответов HTTP в конвейере.

AutoLot.Api

Внутри стандартного шаблона выполняется проверка среды, и если она установлена в

Development
(среда разработки), тогда в конвейер обработки добавляется промежуточное ПО
UseDeveloperExceptionPage()
, предоставляющее отладочную информацию, которую вы вряд ли захотите отображать в производственной среде. Далее производится вызов
UseHttpsRedirection()
для перенаправления всего трафика на HTTPS (вместо HTTP). Затем добавляются вызовы
арр.UseRouting()
,
арр.UseAuthorization()
и
арр.UseEndpoints()
. Вот полный код метода:


public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

{

  if (env.IsDevelopment())

  {

   // Если среда разработки, тогда отображать отладочную информацию.

   app.UseDeveloperExceptionPage();

   // Первоначальный код.

   app.UseSwagger();

   app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json",

    "AutoLot.Api v1"));

  }

  // Перенаправить трафик HTTP на HTTPS.

  app.UseHttpsRedirection();

  // Включить маршрутизацию.

  app.UseRouting();

  // Включить проверки авторизации.

  app.UseAuthorization();

  // Включить маршрутизацию с использованием конечных точек.

  // Использовать для контроллеров маршрутизацию с помощью атрибутов.

  app.UseEndpoints(endpoints =>

  {

   endpoints.MapControllers();

  });

}


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

Configure()
параметр типа
ApplicationDbContext
и вызовите метод
InitializeData()
из
AutoLot.Dal
.

Ниже показан модифицированный код:


public void Configure(

  IApplicationBuilder app,

  IWebHostEnvironment env,

  ApplicationDbContext context)

{

  if (env.IsDevelopment())

  {

   // Если среда разработки, тогда отображать отладочную информацию.

   app.UseDeveloperExceptionPage();

   // Инициализировать базу данных.

  if (Configuration.GetValue("RebuildDataBase"))

   {

    SampleDataInitializer.InitializeData(context);

   }

  }

  ...

}


Обновите файл

appsettings.development.json
с учетом свойства
RebuildDataBase
(пока что установив его в
false
):


{

  "Logging": {

   "LogLevel": {

    "Default": "Information",

    "Microsoft": "Warning",

    "Microsoft.Hosting.Lifetime": "Information"

   }

  },

  "RebuildDataBase": false,

  "ConnectionStrings": {

   "AutoLot": "Server=db;Database=AutoLotPresentation;

  User ID=sa;Password=P@ssw0rd;"

  }

}

AutoLot.Mvc

Метод

Configure()
для веб-приложений немного сложнее, чем его аналог для API. Ниже приведен полный код метода с последующим обсуждением:


public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

{

  if (env.IsDevelopment())

  {

   app.UseDeveloperExceptionPage();

  }

  else

  {

   app.UseExceptionHandler("/Home/Error");

   app.UseHsts();

  }

  app.UseHttpsRedirection();

  app.UseStaticFiles();

  app.UseRouting();

  app.UseAuthorization();

  app.UseEndpoints(endpoints =>

  {

   endpoints.MapControllerRoute(

    name: "default",

    pattern: "{controller=Home}/{action=Index}/{id?}");

  });

}


Метод

Configure()
также проверяет среду, и если она установлена в
Development
(среда разработки), тогда в конвейер обработки добавляется промежуточное ПО
UseDeveloperExceptionPage()
. Для любой другой среды в конвейер обработки добавляется универсальное промежуточное ПО
UseExceptionHandler()
и поддержка протокола строгой транспортной безопасности HTTP (HTTP Strict Transport Security — HSTS). Как и в аналоге для API, добавляется вызов
app.UseHttpsRedirection()
. Следующим шагом является добавление поддержки статических файлов с помощью вызова
app.UseStaticFiles()
. Поддержка статических файлов включается как мера по усилению безопасности. Если ваше приложение в ней не нуждается (подобно API-интерфейсам), тогда не добавляйте такую поддержку. Затем добавляется промежуточное ПО для маршрутизации, авторизации и конечных точек.

Добавьте в метод параметр типа

АрplicationDbContext
и вызовите
InitializeData()
из
AutoLot.Dal
. Вот модифицированный код:


public void Configure(

  IApplicationBuilder app,

  IWebHostEnvironment env,

  ApplicationDbContext context)

{

  if (env.IsDevelopment())

  {

   // Если среда разработки, тогда отображать отладочную информацию.

   app.UseDeveloperExceptionPage();

   // Инициализировать базу данных.

  if (Configuration.GetValue("RebuildDataBase"))

   {

    SampleDataInitializer.InitializeData(context);

   }

  }

  ...

}


Обновите файл

appsettings.development.json
с учетом свойства
RebuildDataBase
(пока что установив его в
false
):


{

  "Logging": {

   "LogLevel": {

    "Default": "Information",

    "Microsoft": "Warning",

    "Microsoft.Hosting.Lifetime": "Information"

   }

  },

  "RebuildDataBase": false,

  "ConnectionStrings": {

   "AutoLot": "Server=db;Database=AutoLotPresentation;

   User ID=sa;Password=P@ssw0rd;"

  }

}


Стандартный шаблон настраивает в методе

UseEndpoints()
маршрутизацию на основе соглашений. Ее понадобится отключить и повсюду в приложении применять маршрутизацию с помощью атрибутов. Закомментируйте (или удалите) вызов
MapControllerRoute()
и замените его вызовом
MapControllers()
:


app.UseEndpoints(endpoints =>

{

  endpoints.MapControllers();

});


Далее добавьте атрибуты маршрутов к

HomeController
в приложении
AutoLot.Mvc
. Первым делом добавьте шаблон контроллер/действие к самому контроллеру:


[Route("[controller]/[action]")]

public class HomeController : Controller

{

  ...

}


Затем добавьте три маршрута к методу

Index()
, так что он будет стандартным действием, когда не указано действие либо когда не указан контроллер или действие. Кроме того, снабдите метод атрибутом
HttpGet
, чтобы явно объявить его действием
GET
:


[Route("/")]

[Route("/[controller]")]

[Route("/[controller]/[action]")]

[HttpGet]

public IActionResult Index()

{

  return View();

}

Ведение журнала

Базовая инфраструктура ведения журнала добавляется в контейнер DI как часть процесса запуска и конфигурирования. Инфраструктура ведения журнала использует довольно простой интерфейс

ILogger
. Основополагающим компонентом ведения журнала является класс
LoggerExtensions
, определения методов которого показаны ниже:


public static class LoggerExtensions

{

  public static void LogDebug(this ILogger logger, EventId eventId,

   Exception exception, string message, params object[] args)

 public static void LogDebug(this ILogger logger, EventId eventId,

    string message, params 
object[] args)

 public static void LogDebug(this ILogger logger, Exception exception,

   string message, 
params object[] args)

 public static void LogDebug(this ILogger logger,

   string message, params object[] args)


  public static void LogTrace(this ILogger logger, EventId eventId,

   Exception exception, string message, params object[] args)

 public static void LogTrace(this ILogger logger, EventId eventId,

   string message, params 
object[] args)

 public static void LogTrace(this ILogger logger, Exception exception,

   string message, 
params object[] args)

  public static void LogTrace(this ILogger logger,

   string message, params object[] args)

   Exception exception, string message, params object[] args)

  public static void LogInformation(this ILogger logger, EventId eventId,

   string message, 
params object[] args)


  public static void LogInformation(this ILogger logger, Exception exception,

   string 
message, params object[] args)

  public static void LogInformation(this ILogger logger,

   string message, params object[] args)

  public static void LogWarning(this ILogger logger, EventId eventId,

   Exception exception, string message, params object[] args)

  public static void LogWarning(this ILogger logger, EventId eventId,

   string message, params 
object[] args)


  public static void LogWarning(this ILogger logger, Exception exception,

   string message, 
params object[] args)

  public static void LogWarning(this ILogger logger,

   string message, params object[] args)

  public static void LogError(this ILogger logger, EventId eventId,

   Exception exception, string message, params object[] args)

  public static void LogError(this ILogger logger, EventId eventId,

   string message, params 
object[] args)

  public static void LogError(this ILogger logger, Exception exception,

   string message, 
params object[] args)


  public static void LogError(this ILogger logger,

   string message, params object[] args)

  public static void LogCritical(this ILogger logger, EventId eventId,

   Exception exception, string message, params object[] args)

  public static void LogCritical(this ILogger logger, EventId eventId,

   string message, 
params object[] args)


  public static void LogCritical(this ILogger logger, Exception exception,

   string message, 
params object[] args)

  public static void LogCritical(this ILogger logger,

   string message, params object[] args)

  public static void Log(this ILogger logger, LogLevel logLevel,

   string message, params 
object[] args)

  public static void Log(this ILogger logger, LogLevel logLevel, EventId eventId,

   string 
message, params object[] args)

  public static void Log(this ILogger logger, LogLevel logLevel,

   Exception exception, string message, params object[] args)

  public static void Log(this ILogger logger, LogLevel logLevel, EventId eventId,

   Exception exception, string message, params object[] args)

}


Яркая характеристика ASP.NET Core связана с расширяемостью конвейера в целом и с ведением журнала в частности. Стандартное средство ведения журнала может быть заменено другой инфраструктурой ведения журнала при условии, что новая инфраструктура способна интегрироваться с установленным шаблоном ведения журнала. Serilog — одна из инфраструктур, которая хорошо интегрируется с ASP.NET Core. В последующих разделах демонстрируется создание инфраструктуры ведения журнала, основанной на Serilog, и конфигурирование приложений ASP.NET Core для использования нового кода регистрации в журнале.

Интерфейс IAppLogging

Начните с добавления в проект

AutoLot.Services
нового каталога по имени
Logging
. Добавьте в этот каталог новый файл под названием
IAppLogging.cs
для интерфейса
IAppLogging
. Приведите содержимое файла
IAppLogging.cs
к следующему виду:


using System;

using System.Runtime.CompilerServices;

namespace AutoLot.Services.Logging

{

  public interface IAppLogging

  {

   void LogAppError(Exception exception, string message,

    [CallerMemberName] string memberName = "",

    [CallerFilePath] string sourceFilePath = "",

    [CallerLineNumber] int sourceLineNumber = 0);


   void LogAppError(string message,

    [CallerMemberName] string memberName = "",

    [CallerFilePath] string sourceFilePath = "",

    [CallerLineNumber] int sourceLineNumber = 0);


   void LogAppCritical(Exception exception, string message,

    [CallerMemberName] string memberName = "",

    [CallerFilePath] string sourceFilePath = "",

    [CallerLineNumber] int sourceLineNumber = 0);


   void LogAppCritical(string message,

    [CallerMemberName] string memberName = "",

    [CallerFilePath] string sourceFilePath = "",

    [CallerLineNumber] int sourceLineNumber = 0);


   void LogAppDebug(string message,

    [CallerMemberName] string memberName = "",

    [CallerFilePath] string sourceFilePath = "",

    [CallerLineNumber] int sourceLineNumber = 0);


   void LogAppTrace(string message,

    [CallerMemberName] string memberName = "",

    [CallerFilePath] string sourceFilePath = "",

    [CallerLineNumber] int sourceLineNumber = 0);


   void LogAppInformation(string message,

    [CallerMemberName] string memberName = "",

    [CallerFilePath] string sourceFilePath = "",

    [CallerLineNumber] int sourceLineNumber = 0);


   void LogAppWarning(string message,

    [CallerMemberName] string memberName = "",

    [CallerFilePath] string sourceFilePath = "",

    [CallerLineNumber] int sourceLineNumber = 0);

  }

}


Атрибуты

CallerMemberName
,
CallerFilePath
и
CallerLineNumber
инспектируют стек вызовов для получения имени члена, пути к файлу и номера строки в вызывающем коде. Например, если строка, в которой вызывается
LogAppWarning()
, находится в функции
DoWork()
внутри файла по имени
MyClassFile.cs
, а номер этой строки 36, тогда вызов:


_appLogger.LogAppWarning("A warning");


преобразуется в следующий эквивалент:


_appLogger.LogAppWarning ("A warning","DoWork","c:/myfilepath/MyClassFile.cs",36);


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

Класс AppLogging

Класс

AppLogging
реализует интерфейс
IAppLogging
. Добавьте новый класс по имени
AppLogging
и модифицируйте операторы
using
, как показано ниже:


using System;

using System.Collections.Generic;

using System.Runtime.CompilerServices;

using Microsoft.Extensions.Configuration;

using Microsoft.Extensions.Logging;

using Serilog.Context;


Сделайте класс открытым и реализующим интерфейс

IAppLogging
. Добавьте конструктор, который принимает экземпляр реализации
ILogger
(интерфейс, поддерживаемый напрямую ASP.NET Core) и экземпляр реализации
IConfiguration
. Внутри конструктора получите доступ к конфигурации, чтобы извлечь имя приложения из файла настроек. Все три элемента (
ILogger
,
IConfiguration
и имя приложения) необходимо сохранить в переменных уровня класса.


namespace AutoLot.Services.Logging

{

  public class AppLogging : IAppLogging

  {

   private readonly ILogger _logger;

   private readonly IConfiguration _config;

   private readonly string _applicationName;

   public AppLogging(ILogger logger, IConfiguration config)

   {

    _logger = logger;

    _config = config;

    _applicationName = config.GetValue("ApplicationName");

   }

  }

}


Инфраструктура Serilog позволяет добавлять свойства в стандартный процесс ведения журнала, заталкивая их внутрь

LogContext
. Добавьте внутренний метод для заталкивания свойств
MemberName
,
FilePath
,
LineNumber
и
ApplicationName
:


internal List PushProperties(

  string memberName,

  string sourceFilePath,

  int sourceLineNumber)

{

  List list = new List

  {

   LogContext.PushProperty("MemberName", memberName),

   LogContext.PushProperty("FilePath", sourceFilePath),

   LogContext.PushProperty("LineNumber", sourceLineNumber),

   LogContext.PushProperty("ApplicationName", _applicationName)

  };

  return list;

}


Каждая реализация метода следует одному и тому же процессу. На первом шаге вызывается метод

PushProperties()
для добавления дополнительных свойств и затем соответствующий метод регистрации в журнале, предоставляемый
LoggerExtensions
в
ILogger
. Ниже приведены все реализованные методы интерфейса:


public void LogAppError(Exception exception, string message,

  [CallerMemberName] string memberName = "",

  [CallerFilePath] string sourceFilePath = "",

  [CallerLineNumber] int sourceLineNumber = 0)

{

  var list = PushProperties(memberName, sourceFilePath, sourceLineNumber);

  _logger.LogError(exception, message);

  foreach (var item in list)

  {

   item.Dispose();

  }

}


public void LogAppError(string message,

  [CallerMemberName] string memberName = "",

  [CallerFilePath] string sourceFilePath = "",

  [CallerLineNumber] int sourceLineNumber = 0)

{

  var list = PushProperties(memberName, sourceFilePath, sourceLineNumber);

  _logger.LogError(message);

  foreach (var item in list)

  {

   item.Dispose();

  }

}


public void LogAppCritical(Exception exception, string message,

  [CallerMemberName] string memberName = "",

  [CallerFilePath] string sourceFilePath = "",

  [CallerLineNumber] int sourceLineNumber = 0)

{

  var list = PushProperties(memberName, sourceFilePath, sourceLineNumber);

  _logger.LogCritical(exception, message);

  foreach (var item in list)

  {

   item.Dispose();

  }

}


public void LogAppCritical(string message,

  [CallerMemberName] string memberName = "",

  [CallerFilePath] string sourceFilePath = "",

  [CallerLineNumber] int sourceLineNumber = 0)

{

  var list = PushProperties(memberName, sourceFilePath, sourceLineNumber);

  _logger.LogCritical(message);

  foreach (var item in list)

  {

   item.Dispose();

  }

}


public void LogAppDebug(string message,

  [CallerMemberName] string memberName = "",

  [CallerFilePath] string sourceFilePath = "",

  [CallerLineNumber] int sourceLineNumber = 0)

{

  var list = PushProperties(memberName, sourceFilePath, sourceLineNumber);

  _logger.LogDebug(message);

  foreach (var item in list)

  {

   item.Dispose();

  }

}


public void LogAppTrace(string message,

  [CallerMemberName] string memberName = "",

  [CallerFilePath] string sourceFilePath = "",

  [CallerLineNumber] int sourceLineNumber = 0)

{

  var list = PushProperties(memberName, sourceFilePath, sourceLineNumber);

  _logger.LogTrace(message);

  foreach (var item in list)

  {

   item.Dispose();

  }

}


public void LogAppInformation(string message,

  [CallerMemberName] string memberName = "",

  [CallerFilePath] string sourceFilePath = "",

  [CallerLineNumber] int sourceLineNumber = 0)

{

  var list = PushProperties(memberName, sourceFilePath, sourceLineNumber);

  _logger.LogInformation(message);

  foreach (var item in list)

  {

   item.Dispose();

  }

}


public void LogAppWarning(string message,

  [CallerMemberName] string memberName = "",

  [CallerFilePath] string sourceFilePath = "",

  [CallerLineNumber] int sourceLineNumber = 0)

{

  var list = PushProperties(memberName, sourceFilePath, sourceLineNumber);

  _logger.LogWarning(message);

  foreach (var item in list)

  {

   item.Dispose();

  }

}

Конфигурация ведения журнала

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

LoggingConfiguration
в каталог
Logging
проекта
AutoLot.Services
. Модифицируйте операторы
using
и сделайте класс открытым и статическим:


using System;

using System.Collections.Generic;

using System.Data;

using Microsoft.Extensions.Configuration;

using Microsoft.Extensions.Hosting;

using Microsoft.Extensions.Logging;

using Serilog;

using Serilog.Events;

using Serilog.Sinks.MSSqlServer;


namespace AutoLot.Services.Logging

{

  public static class LoggingConfiguration

  {

  }

}


Для записи в различные целевые объекты для ведения журналов инфраструктура Serilog использует приемники (sink). Целевыми объектами, которые будут применяться для ведения журнала в приложениях ASP.NET Core, являются текстовый файл, база данных и консоль. Приемники типа текстового файла и базы данных требуют конфигурации — выходного шаблона для текстового файла и списка полей для базы данных. Чтобы настроить выходной шаблон, создайте следующее статическое строковое поле, допускающее только чтение:


private static readonly string OutputTemplate =

  @"[{TimeStamp:yy-MM-dd HH:mm:ss} {Level}]{ApplicationName}:

{SourceContext}{NewLine} 
Message:{Message}{NewLine}in method

{MemberName} at {FilePath}:{LineNumber}{NewLine} 
{Exception}{NewLine}";


Приемник SQL Server нуждается в списке столбцов, идентифицированных с использованием типа

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


private static readonly ColumnOptions ColumnOptions = new ColumnOptions

{

  AdditionalColumns = new List

  {

   new SqlColumn {DataType = SqlDbType.VarChar, ColumnName = "ApplicationName"},

   new SqlColumn {DataType = SqlDbType.VarChar, ColumnName = "MachineName"},

   new SqlColumn {DataType = SqlDbType.VarChar, ColumnName = "MemberName"},

   new SqlColumn {DataType = SqlDbType.VarChar, ColumnName = "FilePath"},

   new SqlColumn {DataType = SqlDbType.Int, ColumnName = "LineNumber"},

   new SqlColumn {DataType = SqlDbType.VarChar, ColumnName = "SourceContext"},

   new SqlColumn {DataType = SqlDbType.VarChar, ColumnName = "RequestPath"},

   new SqlColumn {DataType = SqlDbType.VarChar, ColumnName = "ActionName"},

  }

};


Замена стандартного средства ведения журнала вариантом Serilog представляет собой процесс из трех шагов. Первый шаг — очистка существующего поставщика, второй — добавление Serilog в

HostBuildern
третий — завершение конфигурирования Serilog. Добавьте новый метод по имени
ConfigureSerilog()
, который является расширяющим методом для
IHostBuilder
:


public static IHostBuilder ConfigureSerilog(this IHostBuilder builder)

{

  builder

   .ConfigureLogging((context, logging) => { logging.ClearProviders(); })

   .UseSerilog((hostingContext, loggerConfiguration) =>

  {

   var config = hostingContext.Configuration;

   var connectionString = config.GetConnectionString("AutoLot").ToString();

   var tableName = config["Logging:MSSqlServer:tableName"].ToString();

   var schema = config["Logging:MSSqlServer:schema"].ToString();

   string restrictedToMinimumLevel =

    config["Logging:MSSqlServer:restrictedToMinimumLevel"].ToString();

   if (!Enum.TryParse(restrictedToMinimumLevel, out var logLevel))

   {

    logLevel = LogEventLevel.Debug;

   }

   LogEventLevel level = (LogEventLevel)Enum.Parse(typeof(LogEventLevel), 

               restrictedToMinimumLevel);

   var sqlOptions = new MSSqlServerSinkOptions

  {

    AutoCreateSqlTable = false,

    SchemaName = schema,

    TableName = tableName,

   };

   if (hostingContext.HostingEnvironment.IsDevelopment())

   {

    sqlOptions.BatchPeriod = new TimeSpan(0, 0, 0, 1);

    sqlOptions.BatchPostingLimit = 1;

   }

   loggerConfiguration

    .Enrich.FromLogContext()

    .Enrich.WithMachineName()

    .WriteTo.File(

     path: "ErrorLog.txt",

     rollingInterval: RollingInterval.Day,

     restrictedToMinimumLevel: logLevel,

     outputTemplate: OutputTemplate)

    .WriteTo.Console(restrictedToMinimumLevel: logLevel)

    .WriteTo.MSSqlServer(

     connectionString: connectionString,

     sqlOptions,

     restrictedToMinimumLevel: level,

     columnOptions: ColumnOptions);

  });

  return builder;

}


Теперь, когда все готово, пора заменить стандартное средство ведения журнала на Serilog.

Обновление настроек приложения

Раздел

Logging
во всех файлах настроек приложения (
appsettings.json
,
appsettings.development.json
и
appsettings.production
) для проектов
AutoLot.Api
и
AutoLot.Dal
потребуется модифицировать с учетом новой информации о ведении журнала и добавить имя приложения.

Откройте файлы

appsettings.json
и обновите размертку JSON, как показано ниже; удостоверьтесь в том, что применяете корректное имя проекта в узле
ApplicationName
и указываете строку подключения, соответствующую вашей системе:


// appsettings.json

{

  "Logging": {

   "MSSqlServer": {

    "schema": "Logging",

    "tableName": "SeriLogs",

    "restrictedToMinimumLevel": "Warning"

   }

  },

  "ApplicationName": "AutoLot.Api",

  "AllowedHosts": "*"

}


// appsettings.development.json

{

  "Logging": {

   "MSSqlServer": {

    "schema": "Logging",

    "tableName": "SeriLogs",

    "restrictedToMinimumLevel": "Warning"

   }

  },

  "RebuildDataBase": false,

  "ApplicationName": "AutoLot.Api - Dev",

  "ConnectionStrings": {

   "AutoLot": "Server=.,5433;Database=AutoLot;User ID=sa;Password=P@ssw0rd;"

  }

}


// appsettings.production.json

{

  "Logging": {

   "MSSqlServer": {

    "schema": "Logging",

    "tableName": "SeriLogs",

    "restrictedToMinimumLevel": "Warning"

   }

  },

  "RebuildDataBase": false,

  "ApplicationName": "AutoLot.Api - Prod",

  "ConnectionStrings": {

   "AutoLot": "It's a secret"

  }

}

Обновление Program.cs

Добавьте в файлы Program.cs в проектах

AutoLot.Api
и
AutoLot.Mvc
следующий оператор
using
:


using AutoLot.Services.Logging;


Модифицируйте метод

CreateHostBuilder()
в обоих проектах, как показано ниже:


public static IHostBuilder CreateHostBuilder(string[] args) =>

  Host.CreateDefaultBuilder(args)

   .ConfigureWebHostDefaults(webBuilder =>

   {

    webBuilder.UseStartup();

    }).ConfigureSerilog();

Обновление Startup.cs

Добавьте в файлы

Startup.cs
в проектах
AutoLot.Api
и
AutoLot.Mvc
следующий оператор
using
:


using AutoLot.Services.Logging;


Затем необходимо поместить новые интерфейсы ведения журнала в контейнер DI. Добавьте в метод

ConfigureServices()
в обоих проектах такой код:


services.AddScoped(typeof(IAppLogging<>), typeof(AppLogging<>));

Обновление контроллера

Следующее обновление связано с заменой ссылок на

ILogger
ссылками на
IAppLogging
. Начните с класса
WeatherForecastController
в проекте
AutoLot.Api
. Добавьте в класс следующий оператор
using
:


using AutoLot.Services.Logging;


Далее измените

ILogger
на
IAppLogging
:


[ApiController]

[Route("[controller]")]

public class WeatherForecastController : ControllerBase

{

  ...

  private readonly IAppLogging _logger;

  public WeatherForecastController(IAppLogging logger)

  {

   _logger = logger;

  }

  ...

}


Теперь модифицируйте

HomeController
в проекте
AutoLot.Mvc
. Добавьте в класс следующий оператор
using
:


using AutoLot.Services.Logging;


Измените

ILogger
на
IAppLogging
:


[Route("[controller]/[action]")]

public class HomeController : Controller

{

  private readonly IAppLogging _logger;

  public HomeController(IAppLogging logger)

  {

   _logger = logger;

  }

  ...

}


После этого регистрация в журнале выполняется в каждом контроллере простым обращением к средству ведения журнала, например:


// WeatherForecastController.cs (AutoLot.Api)

[HttpGet]

public IEnumerable Get()

{

  _logger.LogAppWarning("This is a test");

  ...

}


// HomeController.cs (AutoLot.Mvc)

[Route("/")]

[Route("/[controller]")]

[Route("/[controller]/[action]")]

[HttpGet]

public IActionResult Index()

{

  _logger.LogAppWarning("This is a test");

  return View();

}

Испытание инфраструктуры ведения журнала

Имея установленную инфраструктуру Serilog, самое время протестировать ведение журналов для приложений. Если вы используете Visual Studio, тогда укажите

AutoLot.Mvc
в качестве стартового проекта (щелкните правой кнопкой мыши на имени проекта в окне Solution Explorer, выберите в контекстном меню пункт Set as Startup Project (Установить как стартовый проект) и щелкните на кнопке запуска с зеленой стрелкой или нажмите <F5>). В случае работы в VS Code откройте окно терминала (<Ctrl+'>), перейдите в каталог
AutoLot.Mvc
и введите команду
dotnet run
.

В Visual Studio автоматически запустится браузер с представлением

Home/Index
. Если вы применяете VS Code, то вам понадобится открыть браузер и перейти по ссылке
https://localhost:5001
. После загрузки вы можете закрыть браузер, поскольку обращение к средству ведения журнала произошло при загрузке домашней страницы. Закрытие браузера в случае использования Visual Studio останавливает отладку. Чтобы остановить отладку в VS Code, нажмите <Ctrl+C> в окне терминала.

В каталоге проекта вы увидите файл по имени

ErrorLogГГГMMДД.txt
, в котором обнаружите запись, похожую на показанную ниже:


[ГГ-ММ-ДД чч:мм:сс Warning]AutoLot.Mvc -

 Dev:AutoLot.Mvc.Controllers.HomeController

Message:This is a test

in method Index at

D:\Projects\Books\csharp9-wf\Code\New\Chapter_29\AutoLot.Mvc\Controllers\

HomeController.cs:30


Для тестирования кода регистрации в журнале в проекте

AutoLot.Api
установите этот проект в качестве стартового (Visual Studio) или перейдите в каталог
AutoLot.Api
в окне терминала (VS Code). Нажмите <F5> или введите
dotnet run
и перейдите по ссылке
https://localhost:44375/swagger/index.html
. В итоге загрузится страница Swagger для приложения API (рис. 29.6).



Щелкните на кнопке GET для записи

WeatherForecast
. В результате откроется экран с деталями для этого метода действия, включая возможность Try it out (Опробовать), как видно на рис. 29.7.



После щелчка на кнопке Try it out щелкните на кнопке Execute (Выполнить), которая обеспечивает обращение к конечной точке (рис. 29.8).



В каталоге проекта

AutoLot.Api
вы снова увидите файл по имени
ErrorLogГГГГММДД.txt
и найдете в нем примерно такую запись:


[ГГ-ММ-ДД чч:мм:сс Warning]AutoLot.Api -

 Dev:AutoLot.Api.Controllers.

WeatherForecastController

Message:This is a test

in method Get at

D:\Projects\Books\csharp9-wf\Code\New\Chapter_29\AutoLot.Api\Controllers\

WeatherForecastController.cs:30


На заметку! Нововведением в версии ASP.NET Core 5 является то, что Swagger по умолчанию включается в шаблон API. Инструменты Swagger будут подробно исследованы в следующей главе.

Резюме

В главе была представлена инфраструктура ASP.NET Core. Глава начиналась с краткого обзора истории появления ASP.NET, после чего были рассмотрены функциональные средства из классических инфраструктур ASP.NET MVC и ASP.NET Web API, которые присутствуют в ASP.NET Core.

Далее вы узнали о новых средствах ASP.NET Core и о том, как они работают. После изучения различных способов запуска и отладки приложений ASP.NET Core вы создали решение с двумя проектами ASP.NET Core — для общей библиотеки прикладных служб и для уровня доступа к данным AutoLot (из главы 23). Наконец, вы заменили в обоих проектах стандартное средство ведения журнала ASP.NET Core инфраструктурой Serilog.

В следующей главе приложение

AutoLot.Api
будет завершено.

Глава 30 Создание служб REST с помощью ASP.NET Core

В предыдущей главе была представлена инфраструктура ASP.NET Core, обсуждались ее новые возможности, были созданы проекты, а также обновлен код в AutoLot.Mvc и

AutoLot.Api
для включения
AutoLot.Dal
и ведения журнала Serilog.

Внимание в текущей главе будет сосредоточено на завершении работы над REST-службой

AutoLot.Api
.


На заметку! Исходный код, рассматриваемый в этой главе, находится в папке

Chapter_30
внутри хранилища GitHub для настоящей книги. Вы также можете продолжить работу с решением, начатым в главе 29.

Введение в REST-службы ASP.NET Core

Инфраструктура ASP.NET MVC начала набирать обороты почти сразу после своего выхода, а в составе версий ASP.NET MVC 4 и Visual Studio 2012 компания Microsoft выпустила ASP.NET Web API. Версия ASP.NET Web API 2 вышла вместе c Visual Studio 2013 и затем с выходом Visual Studio 2013 Update 1 была модернизирована до версии 2.2.

Продукт ASP.NETWeb API с самого начала разрабатывался как основанная на службах инфраструктура для построения служб REST (REpresentational State Transfer — передача состояния представления), которая базируется на инфраструктуре MVC минус "V" (представление) с рядом оптимизаций, направленных на создание автономных служб. Такие службы могут вызываться с применением любой технологии, а не только тех, которые производит Microsoft. Обращения к службе Web API основаны на базовых HTTP-методах (

GET
,
PUT
,
POST
,
DELETE
) осуществляются через универсальный идентификатор ресурса (uniform resource identifier — URI), например:


http://www.skimedic.com:5001/api/cars


Он похож на унифицированный указатель ресурса (uniform resource locator — URL), поскольку таковым и является! Указатель URL — это просто идентификатор URI, который указывает на физический ресурс в сети.

При вызове служб Web API используется схема HTTP (Hypertext Transfer Protocol — протокол передачи гипертекста) на конкретном хосте (в приведенном выше примере

www.skimedic.com
) и специфическом порте (5001), за которыми указывается путь (
api/cars
), а также необязательные запрос и фрагмент (в примере отсутствуют). Обращение к службе Web API может также содержать текст в теле сообщения, как вы увидите далее в этой главе. Из предыдущей главы вы узнали, что ASP.NET Core объединяет Web API и MVC в одну инфраструктуру.

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

Вспомните, что действия возвращают тип

IActionResult
(или
Task
для асинхронных операций). Кроме вспомогательных методов в
ControllerBase
, возвращающих специфические коды состояния HTTP методы действий способны возвращать содержимое как ответы в формате JSON (JavaScript Object Notation — запись объектов JavaScript).


На заметку! Строго говоря, методы действий могут возвращать широкий диапазон форматов. Формат JSON рассматривается в книге из-за своей популярности.

Результаты ответов в формате JSON

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


[

  "value1",

  "value2"

]


На заметку! Сериализация JSON с использованием

System.Text.Json
подробно обсуждалась в главе 20.


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

ControllerBase
, были перечислены в табл. 29.3. Успешные запросы возвращают коды состояния в диапазоне до 200, причем 200 (ОК) является самым распространенным кодом успеха. В действительности он настолько распространен, что вам не придется возвращать его явно. Если никаких исключений не возникало, а код состояния не был указан, тогда клиенту будет возвращен код 200 вместе с любыми данными.

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

AutoLot.Api
новый контроллер, добавив в каталог
Controllers
новый файл по имени
ValuesController.cs
с показанным ниже кодом:


using System.Collections.Generic;

using Microsoft.AspNetCore.Mvc;


[Route("api/[controller]")]

[ApiController]

public class ValuesController : ControllerBase

{

}


На заметку! В среде Visual Studio для контроллеров предусмотрены шаблоны. Чтобы получить к ним доступ, щелкните правой кнопкой мыши на имени каталога

Controllers
в проекте
AutoLot.Api
, выберите в контекстном меню пункт Add►Controller (Добавить►Контроллер) и укажите шаблон MVC Controller — Empty (Контроллер MVC — Пустой).


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

api
) и маркера (
[controller]
). Такой шаблон маршрута будет соответствовать URL наподобие
www.skimedic.com/api/values
. Атрибут
ApiController
выбирает несколько специфичных для API средств (раскрываются в следующем разделе). Наконец, класс контроллера наследуется от
ControllerBase
. Как обсуждалось в главе 29, в инфраструктуре ASP.NET Core все типы контроллеров, доступные в классической версии ASP.NET, были объединены в один класс по имени
Controller
с базовым классом
ControllerBase
. Класс
Controller
обеспечивает функциональность, специфичную для представлений ("V" в MVC), тогда как
ControllerBase
предлагает оставшуюся базовую функциональность для приложений в стиле MVC.

Существует несколько способов возвращения содержимого в формате JSON из метода действия. Все приведенные далее примеры возвращают те же самые данные JSON с кодом состояния 200. Различия практически полностью стилистические. Добавьте в свой класс

ValuesController
следующий код:


[HttpGet]

public IActionResult Get()

{

  return Ok(new string[] { "value1", "value2" });

}

[HttpGet("one")]

public IEnumerable Get1()

{

  return new string[] { "value1", "value2" };

}

[HttpGet("two")]

public ActionResult> Get2()

{

  return new string[] { "value1", "value2" };

}

[HttpGet("three")]

public string[] Get3()

{

  return new string[] { "value1", "value2" };

}

[HttpGet("four")]

public IActionResult Get4()

{

   return new JsonResult(new string[] { "value1", "value2" });

}


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

AutoLot.Api
; вы увидите список всех методов из
ValuesController
в пользовательском интерфейсе (рис. 30.1).



Вспомните, что при определении маршрутов суффикс

Controller
отбрасывается из имен маршрутов, поэтому конечные точки в
ValuesController
сопоставляются с
Values
, а не с
ValuesController
.

Для выполнения одного из методов щелкните на кнопке GET, на кнопке Try it out (Опробовать) и на кнопке Execute (Выполнить). После выполнения метода пользовательский интерфейс обновится, чтобы отобразить результаты; наиболее важная часть пользовательского интерфейса Swagger показана на рис. 30.2.



Вы увидите, что выполнение каждого метода приводит к получению тех же самых результатов JSON.

Атрибут ApiController

Атрибут

ApiController
, появившийся в версии ASP.NET Core 2.1, в сочетании с классом
ControllerBase
обеспечивает правила, соглашения и линии поведения, специфичные для REST. Соглашения и линии поведения рассматриваются в последующих разделах.

Обязательность маршрутизации с помощью атрибутов

При наличии атрибута

ApiController
контроллер обязан использовать маршрутизацию с помощью атрибутов. Это просто принудительное применение того, что многие расценивают как установившуюся практику.

Автоматические ответы с кодом состояния 400

Если есть проблема с привязкой модели, то действие будет автоматически возвращать код состояния HTTP 400 (Bad Request), что заменяет следующий код:


if (!ModelState.IsValid)

{

  return BadRequest(ModelState);

}


Для выполнения показанной выше проверки инфраструктура ASP.NET Core использует фильтр действий

ModelStatelnvalidFilter
. При наличии ошибок привязки или проверки достоверности ответ HTTP 400 в своем теле содержит детальные сведения об ошибках. Вот пример:


{

  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",

  "title": "One or more validation errors occurred.",

  "status": 400,

  "traceId": "|7fb5e16a-4c8f23bbfc974667.",

  "errors": {

   "": [

    "A non-empty request body is required."

   ]

  }

}


Такое поведение можно отключить через конфигурацию в методе

ConfigureServices()
класса
Startup
:


services.AddControllers()

   .ConfigureApiBehaviorOptions(options =>

   {

     options.SuppressModelStateInvalidFilter = true;

   });

Выведение источников для привязки параметров

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



Такое поведение можно отключить через конфигурацию в методе

Configure Services()
класса
Startup
:


services.AddControllers().ConfigureApiBehaviorOptions(options =>

{

  // Подавить все выведение источников для привязки.

  options.SuppressInferBindingSourcesForParameters= true;

  // Подавить выведение типа содержимого multipart/form-data.

  options. SuppressConsumesConstraintForFormFileParameters = true;

});

Детальные сведения о проблемах для кодов состояния ошибок

ASP.NET Core трансформирует результат ошибки (состояние 400 или выше) в результат с помощью типа

ProblemDetails
, который показан ниже:


public class ProblemDetails

{

  public string Type { get; set; }

  public string Title { get; set; }

  public int? Status { get; set; }

  public string Detail { get; set; }

  public string Instance { get; set; }

  public IDictionary Extensions { get; }

   = new Dictionary(StringComparer.Ordinal);

}


Чтобы протестировать это поведение, добавьте в

ValuesController
еще один метод:


[HttpGet("error")]

public IActionResult Error()

{

  return NotFound();

}


Запустите приложение и посредством пользовательского интерфейса Swagger выполните новую конечную точку

error
. Результатом по-прежнему будет код состояния 404 (Not Found), но в теле ответа возвратится дополнительная информация. Ниже приведен пример ответа (ваше значение
traceId
будет другим):


{

  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.4",

  "title": "Not Found",

  "status": 404,

  "traceId": "00-9a609e7e05f46d4d82d5f897b90da624-a6484fb34a7d3a44-00"

}


Такое поведение можно отключить через конфигурацию в методе

ConfigureServices()
класса
Startup
:


services.AddControllers()

  .ConfigureApiBehaviorOptions(options =>

 {

   options.SuppressMapClientErrors = true;

 });


Когда поведение отключено, вызов конечной точки error возвращает код состояния 404 без какой-либо дополнительной информации.

Обновление настроек Swagger/OpenAPI

Продукт Swagger (также известный как OpenAPI) является стандартом с открытым кодом для документирования служб REST, основанных на API. Два главных варианта для добавления Swagger к API-интерфейсам ASP.NET Core — Swashbuckle и NSwag. Версия ASP.NET Core 5 теперь включает Swashbuckle в виде части шаблона нового проекта. Документация

swagger.json
, сгенерированная для
AutoLot.Api
, содержит информацию по сайту, по каждой конечной точке и по любым объектам, задействованным в конечных точках.

Пользовательский интерфейс Swagger базируется на веб-интерфейсе и позволяет интерактивным образом исследовать конечные точки приложения, а также тестировать их (как делалось ранее в этой главе). Его можно расширить, добавляя документацию в сгенерированный файл

swagger.json
.

Обновление обращений к Swagger в классе Startup

Стандартный шаблон проекта API добавляет код для генерирования файла

swagger.json
в метод
ConfigureService()
класса
Startup
:


services.AddSwaggerGen(c =>

{

  c.SwaggerDoc("v1", new OpenApiInfo { Title = "AutoLot.Api", Version = "v1" });

});


Первое изменение стандартного кода предусматривает добавление метаданных к

OpenApiInfo
. Модифицируйте вызов
AddSwaggerGen()
следующим образом, чтобы обновить заголовок и добавить описание и сведения о лицензии:


services.AddSwaggerGen(c =>

{

  c.SwaggerDoc("v1",

   new OpenApiInfo

   {

    Title = "AutoLot Service",

    Version = "v1",

    Description = "Service to support the AutoLot dealer site",

    License = new OpenApiLicense

    {

     Name = "Skimedic Inc",

     Url = new Uri("http://www.skimedic.com")

    }

   });

});


Следующий шаг связан с переносом вызовов

UseSwagger()
и
UseSwaggerUI()
из блока, предназначенного только для среды разработки, в главный путь выполнения внутри метода
Configure()
. Кроме того, поменяйте заголовок
"AutoLot.Api vl"
на
"AutoLot Service vl"
.


public void Configure(IApplicationBuilder app, IWebHostEnvironment env,  

ApplicationDbContext context)

{

  if (env.IsDevelopment())

  {

   // Если среда разработки, тогда отображать отладочную информацию.

   app.UseDeveloperExceptionPage();

   // Первоначальный код:

   // app.UseSwagger();

   // app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json",

  //                     "AutoLot.Api v1"));

   // Инициализировать базу данных.

   if (Configuration.GetValue("RebuildDataBase"))

   {

    SampleDataInitializer.ClearAndReseedDatabase(context);

   }

  }

  // Включить промежуточное ПО для обслуживания сгенерированного

  // файла Swagger как конечной точки JSON.

  app.UseSwagger();

  // Включить промежуточное ПО для обслуживания пользовательского

  // интерфейса Swagger (HTML, JS, CSS и т.д.), указывая конечную

  // точку JSON для Swagger

  app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json",

                       "AutoLot Service 
v1"); });

  ...

}


В предыдущем коде используется

Swagger(app.UseSwagger())
и пользовательский интерфейс Swagger(
app.useSwaggerUI()
). В нем также конфигурируется конечная точка для файла
swagger.json
.

Добавление файла XML-документации

Инфраструктура .NET Core способна генерировать файл XML-документации для вашего проекта, исследуя методы на предмет наличия комментариев с тремя символами прямой косой черты (

///
). Чтобы включить такую функциональность в Visual Studio, щелкните правой кнопкой мыши на имени проекта
AutoLot.Api
и в контекстном меню выберите пункт Properties (Свойства). В открывшемся диалоговом окне Properties (Свойства) перейдите на вкладку Build (Сборка), отметьте флажок XML documentation file (Файл XML-документации) и укажите в качестве имени файла
AutoLot.Api.xml
. Кроме того, введите 1591 в текстовом поле Suppress warnings (Подавлять предупреждения), как показано на рис. 30.3.



Те же настройки можно вводить прямо в файле проекта. Ниже показан раздел

PropertyGroup
, который понадобится добавить:


  AutoLot.Api.xml

 1701;1702;1591;


Настройка

NoWarn
с указанием
1591
отключает выдачу предупреждений компилятором для методов, которые не имеют XML-комментариев.


На заметку! Предупреждения 1701 и 1702 являются пережитками ранних дней классической платформы .NET, которые обнажают компиляторы .NET Core. Чтобы взглянуть на процесс в действии, модифицируйте метод

Get()
класса
ValuesController
следующим образом:


/// 

/// This is an example Get method returning JSON

/// 

/// This is one of several examples for returning JSON:

/// 

/// [

///  "value1",

///  "value2"

/// ]

/// 

/// 

/// List of strings

[HttpGet]

public IActionResult Get()

{

  return Ok(new string[] { "value1", "value2" });

}


Когда вы скомпилируете проект, в корневом каталоге проекта появится новый файл по имени

AutoLot.Api.xml
. Открыв его, вы увидите только что добавленные комментарии:


  

   AutoLot.Api

  

  

   

    

     This is an example Get method returning JSON

    

   This is one of several examples for returning JSON:

     

     [

      "value1",

      "value2"

     ]

     

    

    List of strings   

  


На заметку! Если вы вводите три символа прямой косой черты перед определением класса или метода в Visual Studio, то среда создает начальную заглушку для XML-комментариев.


Далее необходимо объединить XML-комментарии со сгенерированным файлом

swagger.json
.

Добавление XML-комментариев в процесс генерации Swagger

Сгенерированные XML-комментарии должны быть добавлены в процесс генерации

swagger.json
. Начните с добавления следующих операторов
using
в файл класса
Startup.cs
:


using System.IO;

using System.Reflection;


Файл XML-документации добавляется в Swagger вызовом метода

IncludeXmlComments()
внутри метода
AddSwaggerGen()
. Перейдите к методу
ConfigureServices()
класса
Startup
и модифицируйте метод
AddSwaggerGen()
, добавив файл XML-документации:


services.AddSwaggerGen(c =>

{

  c.SwaggerDoc("v1",

   new OpenApiInfo

   {

    Title = "AutoLot Service",

    Version = "v1",

    Description = "Service to support the AutoLot dealer site",

    License = new OpenApiLicense

    {

     Name = "Skimedic Inc",

     Url = new Uri("http://www.skimedic.com")

    }

   });

   var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";

   var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);

   c.IncludeXmlComments(xmlPath);

});


Запустите приложение и загляните в пользовательский интерфейс Swagger. Обратите внимание на XML-комментарии, интегрированные в пользовательский интерфейс Swagger (рис. 30.4).



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

Дополнительные возможности документирования для конечных точек API

Существуют дополнительные атрибуты, которые дополняют документацию Swagger. Чтобы применить их, начните с добавления показанных далее операторов

using
в файл
ValuesController.cs
:


using Microsoft.AspNetCore.Http;

using Swashbuckle.AspNetCore.Annotations;


Атрибут

Produces
задает тип содержимого для конечной точки. Атрибут
ProducesResponseType
использует перечисление
StatusCodes
для указания возможного кода возврата для конечной точки. Модифицируйте метод
Get()
класса
ValuesController
, чтобы установить
application/json
в качестве возвращаемого типа и сообщить о том, что результатом действия будет либо 200 (ОК), либо 400 (Bad Request):


[HttpGet]

[Produces("application/json")]

[ProducesResponseType(StatusCodes.Status200OK)]

[ProducesResponseType(StatusCodes.Status400BadRequest)]

public ActionResult> Get()

{

  return new string[] {"value1", "value2"};

}


Хотя атрибут

ProducesResponseType
добавляет в документацию коды ответов, настроить эту информацию невозможно. К счастью, Swashbuckle добавляет атрибут
SwaggerResponse
, предназначенный как раз для такой цели. Приведите код метода
Get()
к следующему виду:


[HttpGet]

[Produces("application/json")]

[ProducesResponseType(StatusCodes.Status200OK)]

[ProducesResponseType(StatusCodes.Status400BadRequest)]

[SwaggerResponse(200, "The execution was successful")]

[SwaggerResponse(400, "The request was invalid")]

public ActionResult> Get()

{

  return new string[] {"value1", "value2"};

}


Прежде чем аннотации Swagger будут приняты и добавлены в сгенерированную документацию, их потребуется включить. Откройте файл

Startup.cs
и перейдите к методу
Configure()
. Обновите вызов
AddSwaggerGen()
, как показано ниже:


services.AddSwaggerGen(c =>

{

  c.EnableAnnotations();

  ...

});


Теперь, просматривая раздел ответов в пользовательском интерфейсе Swagger, вы будете видеть настроенный обмен сообщениями (рис. 30.5).



На заметку! В Swashbuckle поддерживается большой объем дополнительной настройки, за сведениями о которой обращайтесь в документацию по ссылке

https://github.com/domaindrivendev/Swashbuckle.AspNetCore
.

Построение методов действий API

Большинство функциональных средств приложения

AutoLot.Api
можно отнести к одному из перечисленных далее методов:

•

GetOne()

•

GetAll()

•

UpdateOne()

•

AddOnе()

•

DeleteOne()


Основные методы API будут реализованы в обобщенном базовом контроллере API. Начните с создания нового каталога под названием

Base
в каталоге
Controllers
проекта
AutoLot.Api
. Добавьте в этот каталог новый файл класса по имени
BaseCrudController.cs
. Модифицируйте операторы
using
и определение класса, как демонстрируется ниже:


using System;

using System.Collections.Generic;

using AutoLot.Dal.Exceptions;

using AutoLot.Models.Entities.Base;

using AutoLot.Dal.Repos.Base;

using AutoLot.Services.Logging;

using Microsoft.AspNetCore.Http;

using Microsoft.AspNetCore.Mvc;

using Swashbuckle.AspNetCore.Annotations;


namespace AutoLot.Api.Controllers.Base

{

  [ApiController]

  public abstract class BaseCrudController : ControllerBase

   where T : BaseEntity, new()

   where TController : BaseCrudController

  {

  }

}


Класс является открытым и абстрактным, а также унаследованным от

ControllerBase
. Он принимает два обобщенных параметра типа. Первый тип ограничивается так, чтобы быть производным от
BaseEntity
и иметь стандартный конструктор, а второй — быть производным от
BaseCrudController
(для представления производных контроллеров). Когда к базовому классу добавляется атрибут
ApiController
, производные контроллеры получают функциональность, обеспечиваемую атрибутом.


На заметку! Для этого класса не определен маршрут. Он будет установлен с использованием производных классов.

Конструктор

На следующем шаге добавляются две защищенные переменные уровня класса: одна для хранения реализации интерфейса

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


protected readonly IRepo MainRepo;

protected readonly IAppLogging Logger;

protected BaseCrudController(IRepo repo, IAppLogging logger)

{

  MainRepo = repo;

  Logger = logger;

}

Методы GetXXX()

Есть два HTTP-метода

GET
,
GetOne()
и
GetAll()
. Оба они используют хранилище, переданное контроллеру. Первым делом добавьте метод
Getll()
, который служит в качестве конечной точки для шаблона маршрута контроллера:


/// 

/// Gets all records

/// 

/// All records

/// Returns all items

[Produces("application/json")]

[ProducesResponseType(StatusCodes.Status200OK)]

[SwaggerResponse(200, "The execution was successful")]

[SwaggerResponse(400, "The request was invalid")]

[HttpGet]

public ActionResult> GetAll()

{

  return Ok(MainRepo.GetAllIgnoreQueryFilters());

}


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

id
, который передается как обязательный параметр маршрута и добавляется к маршруту производного контроллера:


/// 

/// Gets a single record

/// 

/// Primary key of the record

/// Single record

/// Found the record

/// No content

[Produces("application/json")]

[ProducesResponseType(StatusCodes.Status200OK)]

[ProducesResponseType(StatusCodes.Status204NoContent)]

[SwaggerResponse(200, "The execution was successful")]

[SwaggerResponse(204, "No content")]

[HttpGet("{id}")]

public ActionResult GetOne(int id)

{

  var entity = MainRepo.Find(id);

  if (entity == null)

  {

   return NotFound();

  }

  return Ok(entity);

}


Значение маршрута автоматически присваивается параметру

id
(неявно из
[FromRoute]
).

Метод UpdateOne()

Обновление записи делается с применением HTTP-метода

PUT
. Ниже приведен код метода
UpdateOne()
:


/// 

/// Updates a single record

/// 

/// 

/// Sample body:

/// 

/// {

///  "Id": 1,

///  "TimeStamp": "AAAAAAAAB+E="

///  "MakeId": 1,

///  "Color": "Black",

///  "PetName": "Zippy",

///  "MakeColor": "VW (Black)",

/// }

/// 

/// 

/// Primary key of the record to update

/// Single record

/// Found and updated the record

/// Bad request

[Produces("application/json")]

[ProducesResponseType(StatusCodes.Status200OK)]

[ProducesResponseType(StatusCodes.Status400BadRequest)]

[SwaggerResponse(200, "The execution was successful")]

[SwaggerResponse(400, "The request was invalid")]

[HttpPut("{id}")]

public IActionResult UpdateOne(int id,T entity)

{

  if (id != entity.Id)

  {

   return BadRequest();

  }

  try

  {

   MainRepo.Update(entity);

  }

  catch (CustomException ex)

  {

   // Пример специального исключения.

   // Должно обрабатываться более элегантно.

   return BadRequest(ex);

  }

  catch (Exception ex)

  {

   // Должно обрабатываться более элегантно.

   return BadRequest(ex);

  }

  return Ok(entity);

}


Метод начинается с установки маршрута как запроса

HttpPut
на основе маршрута производного контроллера с обязательным параметром маршрута
id
. Значение маршрута присваивается параметру
id
(неявно из
[FromRoute]
), а сущность (
entity
) извлекается из тела запроса (неявно из
[FromBody]
).Также обратите внимание, что проверка достоверности модели отсутствует, поскольку делается автоматически атрибутом
ApiController
. Если состояние модели укажет на наличие ошибок, тогда клиенту будет возвращен код 400 (Bad Request).

Метод проверяет, совпадает ли значение маршрута (

id
) со значением
id
в теле запроса. Если не совпадает, то возвращается код 400 (Bad Request). Если совпадает, тогда используется хранилище для обновления записи. Если обновление терпит неудачу с генерацией исключения, то клиенту возвращается код 400 (Bad Request). Если операция обновления проходит успешно, тогда клиенту возвращается код 200 (ОК) и обновленная запись в качестве тела запроса.


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

Метод AddOne()

Вставка записи делается с применением HTTP-метода

POST
. Ниже приведен код метода
AddOne()
:


/// 

/// Adds a single record

/// 

/// 

/// Sample body:

/// 

/// {

///  "Id": 1,

///  "TimeStamp": "AAAAAAAAB+E="

///  "MakeId": 1,

///  "Color": "Black",

///  "PetName": "Zippy",

///  "MakeColor": "VW (Black)",

/// }

/// 

/// 

/// Added record

/// Found and updated the record

/// Bad request

[Produces("application/json")]

[ProducesResponseType(StatusCodes.Status201Created)]

[ProducesResponseType(StatusCodes.Status400BadRequest)]

[SwaggerResponse(201, "The execution was successful")]

[SwaggerResponse(400, "The request was invalid")]

[HttpPost]

public ActionResult AddOne(T entity)

{

  try

  {

   MainRepo.Add(entity);

  }

  catch (Exception ex)

  {

   return BadRequest(ex);

  }

  return CreatedAtAction(nameof(GetOne), new {id = entity.Id}, entity);

}


Метод начинается с определения маршрута как запроса

HttpPost
. Параметр маршрута отсутствует, потому что создается новая запись. Если хранилище успешно добавит запись, то ответом будет результат вызова метода
CreatedAtAction()
, который возвращает клиенту код 201 вместе с URL для вновь созданной сущности в виде значения заголовка
Location
. Вновь созданная сущность в формате JSON помещается внутрь тела ответа.

Метод DeleteOne()

Удаление записи делается с применением HTTP-метода

DELETE
. Ниже приведен код метода
DeleteOne()
:


/// 

/// Deletes a single record

/// 

/// 

/// Sample body:

/// 

/// {

///  "Id": 1,

///  "TimeStamp": "AAAAAAAAB+E="

/// }

/// 

/// 

/// Nothing

/// Found and deleted the record

/// Bad request

[Produces("application/json")]

[ProducesResponseType(StatusCodes.Status200OK)]

[ProducesResponseType(StatusCodes.Status400BadRequest)]

[SwaggerResponse(200, "The execution was successful")]

[SwaggerResponse(400, "The request was invalid")]

[HttpDelete("{id}")]

public ActionResult DeleteOne(int id, T entity)

{

  if (id != entity.Id)

  {

   return BadRequest();

  }

  try

  {

   MainRepo.Delete(entity);

  }

  catch (Exception ex)

  {

   // Должно обрабатываться более элегантно.

   return new BadRequestObjectResult(ex.GetBaseException()?.Message);

  }

  return Ok();

}


Метод начинается с определения маршрута как запроса

HttpDelete
с обязательным параметром маршрута
id
. Значение
id
в маршруте сравнивается со значением
id
, отправленным с остальной частью сущности в теле запроса, и если они не совпадают, то возвращается код 400 (Bad Request). Если хранилище успешно удаляет запись, тогда клиенту возвращается код 200 (ОК), а если возникла какая-то ошибка, то клиент получает код 400 (Bad Request).

На этом создание базового контроллера завершено.

Класс CarsController

Приложению

AutoLot.Api
необходим дополнительный метод
HttpGet
для получения записей
Car
на основе значения
Make
. Он будет создан в новом классе по имени
CarsController
. Создайте в каталоге
Controllers
новый пустой контроллер API под названием
CarsController
. Модифицируйте операторы
using
следующим образом:


using System.Collections.Generic;

using AutoLot.Api.Controllers.Base;

using Microsoft.AspNetCore.Mvc;

using AutoLot.Models.Entities;

using AutoLot.Dal.Repos.Interfaces;

using AutoLot.Services.Logging;

using Microsoft.AspNetCore.Http;

using Swashbuckle.AspNetCore.Annotations;


Класс

CarsController
является производным от класса
BaseCrudController
и определяет маршрут на уровне контроллера. Конструктор принимает специфичное для сущности хранилище и средство ведения журнала. Вот первоначальный код контроллера:


namespace AutoLot.Api.Controllers

{

  [Route("api/[controller]")]

  public class CarsController : BaseCrudController

  {

   public CarsController(ICarRepo carRepo, IAppLogging logger) : 

base(carRepo, logger)

 {

   }

  }

}


Класс

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


/// 

/// Gets all cars by make

/// 

/// All cars for a make

/// Primary key of the make

/// Returns all cars by make

[Produces("application/json")]

[ProducesResponseType(StatusCodes.Status200OK)]

[ProducesResponseType(StatusCodes.Status204NoContent)]

[SwaggerResponse(200, "The execution was successful")]

[SwaggerResponse(204, "No content")]

[HttpGet("bymake/{id?}")]

public ActionResult> GetCarsByMake(int? id)

{

  if (id.HasValue && id.Value>0)

  {

   return Ok(((ICarRepo)MainRepo).GetAllBy(id.Value));

  }

  return Ok(MainRepo.GetAllIgnoreQueryFilters());

}


Атрибут

HttpGet
расширяет маршрут константой
bymake
и необязательным идентификатором производителя для фильтрации, например:


https://localhost:5021/api/cars/bymake/5 


Сначала в методе проверяется, было ли передано значение для

id
. Если нет, то получаются все автомобили. Если значение было передано, тогда с использованием метода
GetAllBy()
класса
CarRepo
получаются автомобили по производителю. Поскольку защищенное свойство
MainRepo
базового класса определено с типом
IRepo
, его потребуется привести к типу
ICarRepo
.

Оставшиеся контроллеры

Все оставшиеся контроллеры, специфичные для сущностей, будут производными от класса

BaseCrudController
, но без добавления дополнительной функциональности. Добавьте в каталог
Controllers
еще четыре пустых контроллера API с именами
CreditRisksController
,
CustomersController
,
MakesController
и
OrdersController
.

Вот код оставшихся контроллеров:


// CreditRisksController.cs

using AutoLot.Api.Controllers.Base;

using AutoLot.Models.Entities;

using AutoLot.Dal.Repos.Interfaces;

using AutoLot.Services.Logging;

using Microsoft.AspNetCore.Mvc;


namespace AutoLot.Api.Controllers

{

  [Route("api/[controller]")]

  public class CreditRisksController

   : BaseCrudController

  {

   public CreditRisksController(

    ICreditRiskRepo creditRiskRepo, IAppLogging logger)

    : base(creditRiskRepo, logger)

   {

   }

  }

}


// CustomersController.cs

using AutoLot.Api.Controllers.Base;

using AutoLot.Models.Entities;

using AutoLot.Dal.Repos.Interfaces;

using AutoLot.Services.Logging;

using Microsoft.AspNetCore.Mvc;


namespace AutoLot.Api.Controllers

{

  [Route("api/[controller]")]

  public class CustomersController : BaseCrudController

  {

   public CustomersController(

    ICustomerRepo customerRepo, IAppLogging logger)

    : base(customerRepo, logger)

   {

   }

  }

}


// MakesController.cs

using AutoLot.Api.Controllers.Base;

using AutoLot.Models.Entities;

using Microsoft.AspNetCore.Mvc;

using AutoLot.Dal.Repos.Interfaces;

using AutoLot.Services.Logging;


namespace AutoLot.Api.Controllers

{

  [Route("api/[controller]")]

  public class MakesController : BaseCrudController

  {

   public MakesController(IMakeRepo makeRepo, IAppLogging logger)

    : base(makeRepo, logger)

   {

   }

  }

}


// OrdersController.cs

using AutoLot.Api.Controllers.Base;

using AutoLot.Dal.Repos.Interfaces;

using AutoLot.Models.Entities;

using AutoLot.Services.Logging;

using Microsoft.AspNetCore.Mvc;


namespace AutoLot.Api.Controllers

{

  [Route("api/[controller]")]

  public class OrdersController : BaseCrudController

  {

   public OrdersController(IOrderRepo orderRepo,

    IAppLogging logger) : 
base(orderRepo, logger)

   {

   }

  }

}


Итак, все контроллеры готовы и вы можете с помощью пользовательского интерфейса Swagger протестировать полную функциональность. Если вы собираетесь добавлять/обновлять/удалять записи, тогда измените значение

RebuildDataBase
на
true
в файле
appsettings.development.json
:


{

  ...

  "RebuildDataBase": true,

  ...

}

Фильтры исключений

Когда в приложении Web API возникает исключение, никакая страница со сведениями об ошибке не отображается, т.к. пользователем обычно является другое приложение, а не человек. Информация об ошибке должна быть отправлена в формате JSON наряду с кодом состояния HTTP. Как обсуждалось в главе 29, инфраструктура ASP.NET Core позволяет создавать фильтры, которые запускаются при появлении необработанных исключений. Фильтры можно применять глобально, на уровне контроллера или на уровне действия. Для текущего приложения вы построите фильтр исключений для отправки данных JSON (вместе с кодом HTTP 500) и включения трассировки стека, если сайт функционирует в режиме отладки.


На заметку! Фильтры — крайне мощное средство ASP.NET Core. В этой главе вы ознакомитесь только с фильтрами исключений, но с их помощью можно создавать очень многое, что значительно экономит время при построении приложений ASP.NET Core. Полную информацию о фильтрах ищите в документации по ссылке

https://docs.microsoft.com/ru-ru/aspnet/core/mvc/controllers/filters
.

Создание специального фильтра исключений

Создайте новый каталог под названием

Filters
и добавьте в него новый файл класса по имени
CustomExceptionFilterAttribute.cs
. Приведите операторы
using
к следующему виду:


using Microsoft.AspNetCore.Hosting;

using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.Filters;

using Microsoft.EntityFrameworkCore;

using Microsoft.Extensions.Hosting;


Сделайте класс открытым и унаследованным от

ЕхсерtionFiIterAttribute
. Переопределите метод
OnException()
, как показано ниже:


namespace AutoLot.Api.Filters

{

  public class CustomExceptionFilterAttribute : ExceptionFilterAttribute

  {

   public override void OnException(ExceptionContext context)

   {

   }

  }

}


В отличие от большинства фильтров в ASP.NET Core, которые имеют обработчик событий "перед" и "после", фильтры исключений располагают только одним обработчиком:

OnException()
(или
OnExceptionAsync()
). Обработчик принимает один параметр,
ExceptionContext
, который предоставляет доступ к
ActionContext
, а также к сгенерированному исключению.

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

IWebHostEnvironment
, внедренный в фильтр, который будет использоваться для выяснения среды времени выполнения. Если средой является
Development
, тогда ответ должен также включать трассировку стека. Добавьте переменную уровня класса для хранения экземпляра реализации
IWebHostEnvironment
и конструктор:


private readonly IWebHostEnvironment _hostEnvironment;

public CustomExceptionFilterAttribute(IWebHostEnvironment hostEnvironment)

{

  _hostEnvironment = hostEnvironment;

}


Код в обработчике

OnException()
проверяет тип сгенерированного исключения и строит соответствующий ответ. В случае среды
Development
в сообщение ответа включается трассировка стека. Затем создается динамический объект, который содержит значения для отправки вызывающему запросу, и возвращается в
IActionResult
. Вот модифицированный код метода:


public override void OnException(ExceptionContext context)

{

  var ex = context.Exception;

  string stackTrace = _hostEnvironment.IsDevelopment()

   ? context.Exception.StackTrace : 
string.Empty;

  string message = ex.Message;

  string error;

   IActionResult actionResult;

  switch (ex)

  {

   case DbUpdateConcurrencyException ce:

    // Возвращается код HTTP 400.

    error = "Concurrency Issue.";

    actionResult = new BadRequestObjectResult(

     new {Error = error, Message = message, StackTrace = stackTrace});

    break;

   default:

    error = "General Error.";

    actionResult = new ObjectResult(

     new {Error = error, Message = message, StackTrace = stackTrace})

    {

     StatusCode = 500

    };

    break;

  }

  //context.ExceptionHandled = true; // Если убрать здесь комментарий,

                   // то исключение поглощается

  context.Result = actionResult;

}


Если вы хотите, чтобы фильтр исключений поглотил исключение и установил код состояния в 200 (скажем, для регистрации ошибки в журнале, не возвращая ее клиенту), тогда поместите следующую строку перед установкой

Result
(в предыдущем примере кода просто уберите комментарий):


context.ExceptionHandled = true;

Добавление фильтров в конвейер обработки

Фильтры можно применять к методам действий, контроллерам или глобально к приложению. Код "перед" фильтров выполняется снаружи вовнутрь (глобальный, контроллер, метод действия), в то время как код "после" фильтров выполняется изнутри наружу (метод действия, контроллер, глобальный).

На уровне приложения фильтры добавляются в методе

ConfigureServices()
класса
Startup
. Откройте файл класса
Startup.cs
и поместите в начало файла следующий оператор
using
:


using AutoLot.Api.Filters;


Модифицируйте метод

AddControllers()
, добавив специальный фильтр:


services

  .AddControllers(config => config.Filters.Add(

    new CustomExceptionFilterAttribute(_env)))

  .AddJsonOptions(options =>

  {

   options.JsonSerializerOptions.PropertyNamingPolicy = null;

   options.JsonSerializerOptions.WriteIndented = true;

  })

  .ConfigureApiBehaviorOptions(options =>

  {

  ...

  });

Тестирование фильтра исключений

Чтобы протестировать фильтр исключений, откройте файл

WeatherForecastController.cs
и обновите метод действия
Get()
показанным ниже кодом:


[HttpGet]

public IEnumerable Get()

{

  _logger.LogAppWarning("This is a test");

  throw new Exception("Test Exception");

  ...

}


Запустите приложение и испытайте метод с использованием Swagger. Результаты, отображенные в пользовательском интерфейсе Swagger должны соответствовать следующему выводу (трассировка стека приведена с сокращениями):


{

  "Error": "General Error.",

  "Message": "Test Exception",

  "StackTrace": "  at AutoLot.Api.Controllers.WeatherForecastController.Get() in 

D:\\Projects\\Books\\csharp9-wf\\Code\\New\\Chapter_30\\AutoLot.Api\\Controllers\\

WeatherForecastController.cs:line 31\r\n  "

}

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

Приложения API должны иметь политики, которые разрешают или запрещают взаимодействовать с ними клиентам, обращающимся из другого сервера. Такие типы запросов называются запросами между источниками (cross-origin requests — CORS). Хотя в этом нет необходимости при работе локально на своей машине для всего мира ASP.NET Core, поддержка CORS нужна фреймворкам JavaScript, которые желают взаимодействовать с вашим приложением API, даже когда они все вместе функционируют локально.


На заметку! Дополнительные сведения о поддержке CORS ищите в документации по ссылке

https://docs.microsoft.com/ru-ru/aspnet/core/security/cors
.

Создание политики CORS

Инфраструктура ASP.NET Core располагает развитой поддержкой конфигурирования CORS, включая методы для разрешения/запрещения заголовков, методов, источников, учетных данных и многого другого. В этом примере все будет оставлено максимально открытым.

Конфигурирование начинается с создания политики CORS и добавления ее в коллекцию служб. Политика имеет имя (оно будет использоваться в методе

Configure()
), за которым следуют правила. Далее будет сознана политика по имени
AllowAll
, разрешающая все. Добавьте в метод
ConfigureServices()
класса
Startup
следующий код:


services.AddCors(options =>

{

  options.AddPolicy("AllowAll", builder =>

  {

  builder

    .AllowAnyHeader()

    .AllowAnyMethod()

    .AllowAnyOrigin();

  });

});

Добавление политики CORS в конвейер обработки HTTP

Наконец, политику CORS необходимо добавить в конвейер обработки HTTP. Поместите между вызовами

арр. UseRouting()
и
арр.UseEndpoints()
в методе
Configure()
класса
Startup
показанную ниже строку (выделенную полужирным):


public void Configure(

  IApplicationBuilder app,

 IWebHostEnvironment env,

 ApplicationDbContext context)

{

  ...

 // Включить маршрутизацию.

 app.UseRouting();

 // Добавить политику CORS.

 app.UseCors("AllowAll");

 // Включить проверки авторизации.

 app.UseAuthorization();

  ...

}

Резюме

В главе вы продолжили изучение ASP.NET Core. Сначала вы узнали о возвращении данных JSON из методов действий, после чего взглянули на атрибут

ApiController
и его влияние на контроллеры API. Затем вы обновили общую реализацию Swashbuckle, чтобы включить XML-документацию приложения и информацию из атрибутов методов действий.

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

В следующей главе вы завершите построение веб-приложения ASP.NET Core, т.е.

AutoLot.Mvc
.

Глава 31 Создание приложений MVC с помощью ASP.NET Core

В главе 29 была заложена основа ASP.NET Core, а в главе 30 вы построили службу REST. В этой главе вы будете создавать веб-приложение с использованием паттерна МУС. Все начинается с помещения "V" обратно в "МУС".


На заметку! Исходный код, рассматриваемый в этой главе, находится в папке

Chapter_31
внутри хранилища GitHub для настоящей книги. Вы также можете продолжить работу с решением, начатым в главе 29 и обновленным в главе 30.

Введение в представления ASP.NET Core

При построении служб ASP.NET Core были задействованы только части "М " (модели) и "С" (контроллеры ) паттерна МУС. Пользовательский интерфейс создается с применением части "V", т.е. представлений паттерна МУС. Представления строятся с использованием кода HTML, JavaScript, CSS и Razor. Они необязательно имеют страницу базовой компоновки и визуализируются из метода действия контроллера или компонента представления. Если вы имели дело с классической инфраструктурой ASP.NET МУС, то все должно выглядеть знакомым.

Экземпляры класса ViewResult и методы действий

Как кратко упоминалось в главе 29, объекты результатов

ViewResult
и
PartialView
являются экземплярами класса
ActionResult
, которые возвращаются из методов действий с применением вспомогательных методов класса
Controller
. Класс
PartialViewResult
спроектирован для визуализации внутри другого представления и не использует страницу компоновки, тогда как класс
ViewResult
обычно визуализируется в сочетании со страницей компоновки.

По соглашению, принятому в ASP.NET Core (что было и в ASP.NET МУС), экземпляр View или PartialView визуализирует файл

*.cshtml
с таким же именем, как у метода. Представление должно находиться либо в каталоге с именем контроллера (без суффикса
Controller
), либо в каталоге
Shared
(оба расположены внутри родительского каталога
Views
).

Например, следующий код будет визуализировать представление

SampleAction.cshtml
, находящееся в каталоге
Views\Sample
или
Views\Shared
:


[Route("[controller]/[action]")]

public class SampleController: Controller

{

  public ActionResult SampleAction()

   {

   return View();

  }

}


На заметку! Первым производится поиск в каталоге с именем контроллера. Если представление там не обнаружено, то поиск выполняется в каталоге

Shared
. Если оно по-прежнему не найдено, тогда генерируется исключение.


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

cshtml
). Показанный ниже код будет визуализировать представление
CustomViewName.cshtml
:


public ActionResult SampleAction()

{

  return View("CustomViewName");

}


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


public ActionResult SampleAction()

{

  var sampleModel = new SampleActionViewModel();

  return View(sampleModel);

}


public ActionResult SampleAction()

{

  var sampleModel = new SampleActionViewModel();

  return View("CustomViewName",sampleModel);

}


В следующем разделе подробно рассматривается механизм визуализации Razor с использованием представления, которое визуализируется из метода действия по имени

RazorSyntax()
класса
HomeController
. Метод действия будет получать запись
Car
из экземпляра класса
CarRepo
, внедряемого в метод, и передавать экземпляр
Car
в качестве модели представлению.

Откройте

HomeController
в каталоге
Controllers
приложения
AutoLot.Mvc
и добавьте следующий оператор
using
:


using AutoLot.Dal.Repos.Interfaces;


Затем добавьте в контроллер метод

Razorsyntax()
:


[HttpGet]

public IActionResult RazorSyntax([FromServices] ICarRepo carRepo)

{

  var car = carRepo.Find(1);

  return View(car);

}


Метод действия декорируется атрибутом

HTTPGet
с целью установки этого метода в качестве конечной точки приложения для
/Home/RazorSyntax
при условии, что поступивший запрос является HTTP-запросом
GET
. Атрибут
FromServices
на параметре
ICarRepo
информирует ASP.NET Core о том, что параметр не должен привязываться к каким-либо входящим данным, а взамен метод получает экземпляр реализации
ICarRepo
из контейнера DI (dependency injection — внедрение зависимостей). Метод получает экземпляр
Car
и возвращает экземпляр
ViewResuit
с применением метода
View()
. Поскольку имя представления не было указано, ASP.NET Core будет искать представление с именем
RazorSyntax.cshtml
в каталоге
Views\Home
или
Views\Shared
. Если ни в одном местоположении представление не найдено, тогда клиенту (браузеру) возвратится исключение.

Запустите приложение и перейдите в браузере по ссылке

https://localhost:5001/Home/RazorSyntax
(в случае использования Visual Studio и IIS вам понадобится изменить номер порта). Так как в проекте отсутствует представление, которое может удовлетворить запрос, в браузер возвращается исключение. Вспомните из главы 29, что внутри метода
Configure()
класса
Startup
в конвейер HTTP добавляется вызов
UseDeveloperExceptionPage()
, если средой является
Development
. Результаты работы этого метода показаны на рис. 31.1.



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

Configure()
и замените ее "стандартным" обработчиком ошибок:


if (env.IsDevelopment())

{

  // app.UseDeveloperExceptionPage();

  app.UseExceptionHandler("/Home/Error");

  ...

}


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

http://localhost:5001/Home/RazorSyntax
, вы завидите стандартную страницу ошибок, которая показана на рис. 31.2.



На заметку! Во всех примерах URL в этой главе применяется веб-сервер Kestrel и порт 5001. Если вы имеете дело с Visual Studio и веб-сервером IIS Express, тогда используйте URL из профиля для IIS в файле

launchsettings.json
.


Стандартный обработчик ошибок выполняет перенаправление ошибок методу действия

Error
класса
HomeController
. Не забудьте восстановить применение страницы исключений для разработчиков в методе
Configure()
:


if (env.IsDevelopment())

{

  app.UseDeveloperExceptionPage();

  ...

}


Дополнительные сведения о настройке обработки ошибок и доступных вариантах ищите в документации по ссылке

https://docs.microsoft.com/ru-ru/aspnet/core/fundamentals/error-handling
.

Механизм визуализации и синтаксис Razor

Механизм визуализации Razor задумывался как усовершенствование механизма визуализации Web Forms и использует Razor в качестве основного языка. Razor — это код серверной стороны, который встраивается в представление, базируется на C# и избавляет от многих неудобств, присущих механизму визуализации Web Forms. Встраивание Razor в HTML и CSS приводит к тому, что код становится намного чище и лучше для восприятия, чем в случае, когда применяется синтаксис механизма визуализации Web Forms.

Первым делом добавьте новое представление, щелкнув правой кнопкой мыши на имени каталога

Views\Home
в проекте
AutoLot.Mvc
и выбрав в контекстном меню пункт Add►New Item (Добавить►Новый элемент). В открывшемся диалоговом окне Add New Item —
AutoLot.Mvc
(Добавить новый элемент —
AutoLot.Mvc
) выберите шаблон Razor View — Empty (Представление Razor — Пустое) и назначьте представлению имя
RazorSyntax.cshtml
.


На заметку! Контекстное меню, открывшееся в результате щелчка правой кнопкой мыши на

Views\Home
, содержит также пункт Add►View (Добавить►Представление). Тем не менее, его выбор приводит к переходу в то же самое диалоговое окно Add New Item.


Представления Razor, как правило, строго типизированы с использованием директивы

@model
(обратите внимание на букву
m
в нижнем регистре). Измените тип нового представления на сущность
Car
, добавив в начало файла представления такой код:


@model AutoLot.Models.Entities.Car


Поместите в верхнюю часть страницы дескриптор <

hl
>. Он не имеет ничего общего с Razor, а просто добавляет заголовок к странице:


Razor Syntax


Блоки операторов Razor открываются с помощью символа

@
и являются либо самостоятельными операторами (вроде
foreach
), либо заключаются в фигурные скобки, как демонстрируется в следующих примерах:


@for (var i = 0; i < 15; i++)

{

   // Делать что-то.

}

@{

   // Блок кода.

   var foo = "Foo";

   var bar = "Bar";

   var htmlString = "
  • one
  • two
";

}


Чтобы вывести значение переменной в представление, просто укажите символ

@
с именем переменной, что эквивалентно вызову
Response.Write()
. Как видите, при выводе напрямую в браузер после оператора нет точки с запятой:


@foo


@htmlString


@foo.@bar



В предыдущем примере две переменные комбинируются посредством точки между ними (

@foo.@bar
). Это не обычная "точечная" запись в языке С#, предназначенная для навигации по цепочке свойств. Здесь просто значения двух переменных выводятся в поток ответа с физической точкой между ними. Если вас интересует "точечная" запись в отношении переменной, тогда примените
@
к переменной и записывайте свой код стандартным образом:


@foo.ToUpper()


Если вы хотите вывести низкоуровневую HTML-разметку, тогда используйте так называемые вспомогательные функции HTML (HTML helper), которые встроены в механизм визуализации Razor. Следующая строка выводит низкоуровневую HTML-разметку:


@Html.Raw(htmlString)



В блоках кода можно смешивать разметку и код. Строки, начинающиеся с разметки, интерпретируются как HTML, а остальные строки — как код. Если строка начинается с текста, который не является кодом, вы должны применять указатель содержимого (

@:
) или указатель блока содержимого
(
). Обратите внимание, что строки могут меняться с одного вида на другой и наоборот. Ниже приведен пример:


@{

  @:Straight Text

  
Value:@Model.Id

  

   Lines without HTML tag

  

  

}


При желании отменить символ

@
используйте удвоенный
@
. Кроме того, механизм Razor достаточно интеллектуален, чтобы обрабатывать адреса электронной почты, поэтому отменять символ
@
в них не нужно. Если необходимо заставить Razor трактовать символ
@
подобно маркеру Razor, тогда добавьте круглые скобки:


foo@foo.com


@@foo


test@foo


test@(foo)



Предыдущий код выводит

foo@foo.com
,
@foo
,
test@foo
и
testFoo
.

Комментарии Razor открываются с помощью

@*
и закрываются посредством
*@
:


@*

  Multiline Comments

  Hi.

*@


В Razor также поддерживаются внутристрочные функции. Например, следующая функция сортирует список строк:


@functions {

 public static IList SortList(IList strings)  {

  var list = from s in strings orderby s select s;

  return list.ToList();

 }

}


Приведенный далее код создает список строк, сортирует их с применением функции

SortList()
и выводит отсортированный список в браузер:


@{

  var myList = new List {"C", "A", "Z", "F"};

  var sortedList = SortList(myList);

}

@foreach (string s in sortedList)

{

  @s@: 

}



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


@{

   Func b = @@item;

}

This will be bold: @b("Foo")


Кроме того, Razor содержит вспомогательные методы HTML, которые предоставляются инфраструктурой ASP.NET Core, например,

DisplayForModel()
и
EditorForModel()
. Первый применяет рефлексию к модели представления для отображения на веб-странице. Второй тоже использует рефлексию, чтобы создать HTML-разметку для формы редактирования (имейте в виду, что он не поставляет дескрипторы Form, а только разметку для модели). Вспомогательные методы HTML подробно рассматриваются позже в главе.

Наконец, в версии ASP.NET Core появились вспомогательные функции дескрипторов (tag helper), которые объединяют разметку и код; они будут обсуждаться далее в главе.

Представления

Представления — это специальные файлы кода с расширением

cshtml
, содержащие сочетание разметки HTML, стилей CSS, кода JavaScript и кода Razor.

Каталог Views

Внутри каталога Views хранятся представления в проектах ASP.NET Core, использующих паттерн MVC. В самом каталоге Views находятся два файла:

_iewStart.cshtml
и
_ViewImports.cshtml
.

Код в файле

_ViewStart.cshtml
выполняется перед визуализацией любого другого представления (за исключением частичных представлений и компоновок). Файл
_ViewStart.cshtml
обычно применяется с целью установки стандартной компоновки для представлений, в которых она не указана. Компоновки подробно рассматриваются в разделе "Компоновки" позже в главе. Вот как выглядит содержимое файла
_ViewStart.cshtml
:


@{

   Layout = "_Layout";

}


Файл

_ViewImports.cshtml
служит для импортирования совместно используемых директив, таких как операторы
using
. Содержимое применяется ко всем представлениям в том же каталоге или подкаталоге, где находится файл
_ViewImports
. Добавьте оператор
using
для
AutoLot.Models.Entities
:


@using AutoLot.Mvc

@using AutoLot.Mvc.Models

@using AutoLot.Models.Entities 

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers


Строка

@addTegHelper
будет раскрыта вместе со вспомогательными функциями дескрипторов.


На заметку! А для чего служит ведущий символ подчеркивания в

_ViewStart.html
,
_ViewImports.cshtml
и
_Layout.cshtml
? Механизм визуализации Razor изначально создавался для платформы WebMatrix, где не разрешалось напрямую визуализировать файлы, имена которых начинались с символа подчеркивания. Все ключевые файлы (вроде компоновки и конфигурации) имеют имена, начинающиеся с символа подчеркивания. Это не соглашение MVC, поскольку здесь отсутствует проблема, которая была в WebMatrix, но наследие символа подчеркивания продолжает существовать.


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

Controller
). Скажем, в каталоге
Views\Cars
содержатся все представления для
CarsController
. Представления обычно именуются согласно методам действий, которые их визуализируют, хотя их имена можно изменять, как уже было показано.

Каталог Shared

Внутри каталога

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

Каталог DisplayTemplates

В каталоге

DisplayTemplates
хранятся специальные шаблоны, которые управляют визуализацией типов, а также содействуют многократному использованию кода и согласованности отображения. Когда вызываются методы
DisplayFor()/DisplayForModel()
, механизм визуализации Razor ищет шаблон, имя которого совпадает с именем визуализируемого типа, например,
Car.cshtml
для класса
Car
. Если специальный шаблон найти не удалось, тогда разметка визуализируется с применением рефлексии. Поиск начинается с каталога
Views\{CurrentControllerName}\DisplayTemplates
и в случае неудачи продолжается в каталоге
Views\Shared\DisplayTemplates
. Методы
DisplayFor()/DisplayForModel()
принимают необязательный параметр, указывающий имя шаблона.

Шаблон отображения DateTime

Создайте внутри каталога

Views\Shared
новый каталог под названием
DisplayTemplates
и добавьте в него новое представление по имени
DateTime.cshtml
. Удалите сгенерированный код вместе с комментариями и замените его следующим кодом:


@model DateTime?

@if (Model == null)

{

  @:Unknown

}

else

{

  if (ViewData.ModelMetadata.IsNullableValueType)

  {

   @:@(Model.Value.ToString("d"))

  }

  else

  {

   @:@(((DateTime)Model).ToString("d"))

  }

}


Обратите внимание, что в директиве

@model
, строго типизирующей представление, используется буква m нижнего регистра. При ссылке на присвоенное значение модели в Razor применяется буква
М
верхнего регистра. В этом примере определение модели допускает значения
null
. Если переданное представлению значение для модели равно
null
, то шаблон отображает слово
Unknown
(неизвестно). В противном случае шаблон отображает дату в сокращенном формате, используя свойство
Value
допускающего
null
типа или саму модель.

Шаблон отображения Car

Создайте внутри каталога

Views
новый каталог по имени
Cars
, а внутри него — каталог под названием
DisplayTemplates
. Добавьте в каталог
DisplayTemplates
новое представление по имени
Car.cshtml
. Удалите сгенерированный код вместе с комментариями и замените его показанным ниже кодом, который отображает сущность
Car
:


@model AutoLot.Models.Entities.Car

  

   @Html.DisplayNameFor(model => model.MakeId)

  

  

   @Html.DisplayFor(model => model.MakeNavigation.Name)

  

  

   @Html.DisplayNameFor(model => model.Color)

  

  

   @Html.DisplayFor(model => model.Color)

  

  

   @Html.DisplayNameFor(model => model.PetName)

  

  

   @Html.DisplayFor(model => model.PetName)

  


Вспомогательная функция HTML под названием

DisplayNameFor()
отображает имя свойства, если только свойство не декорировано или атрибутом
Display(Name="")
, или атрибутом
DisplayName("")
, и тогда применяется отображаемое значение. Метод
DisplayFor()
отображает значение для свойства модели, указанное в выражении. Обратите внимание, что для получения названия производителя используется навигационное свойство
MakeNavigation
.

Запустив приложение и перейдя на страницу

RazorSyntax
, вы можете быть удивлены тем, что шаблон отображения
Car
не применяется. Причина в том, что шаблон находится в каталоге представления
Cars
, а метод действия
RazorSyntax
и представление вызываются из
HomeController
. Методы действий в
HomeController
будут осуществлять поиск представлений в каталогах
Home
и
Shared
и потому не найдут шаблон отображения
Car
.

Если вы переместите файл

Car.cshtml
в каталог
Shared\DisplayTemplates
, тогда представление
RazorSyntax
будет использовать шаблон отображения
Car
.

Шаблон отображения CarWithColor

Шаблон

CarWithColor
похож на шаблон
Car
. Разница в том, что этот шаблон изменяет цвет текста Color (Цвет) на основе значения свойства
Color
модели. Добавьте в каталог
Cars\DisplayTemplates
новый шаблон по имени
CarWithColors.cshtml
и приведите разметку к следующему виду:


@model Car


  

   

    @Html.DisplayNameFor(model => model.PetName)

   

   

    @Html.DisplayFor(model => model.PetName)

   

   

    @Html.DisplayNameFor(model => model.MakeNavigation)

   

   

    @Html.DisplayFor(model => model.MakeNavigation.Name)

   

   

    @Html.DisplayNameFor(model => model.Color)

   

   

    @Html.DisplayFor(model => model.Color)

   

  


Чтобы применить шаблон

CarWithColors.cshtml
вместо
Car.cshtml
, вызовите
DisplayForModel()
с именем шаблона (обратите внимание, что правила местоположения по-прежнему актуальны):


@Html.DisplayForModel("CarWithColors")

Каталог EditorTemplates

Каталог

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

Шаблон редактирования Car

Создайте внутри каталога

Views\Cars
новый каталог под названием
EditorTemplates
и добавьте в него новое представление по имени
Car.cshtml
. Удалите сгенерированный код вместе с комментариями и замените его показанным ниже кодом, который является разметкой для редактирования сущности
Car
:


@model Car

   

  

   

   

   

   

   

   


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

asp-for
,
asp-items
,
asp-validation-for
и
asp-validation-summary
), которые рассматриваются позже в главе.

Шаблон редактирования

Car
вызывается с помощью вспомогательных функций HTML, которые называются
EditorFor()
и
EditorForModel()
. Подобно шаблонам отображения упомянутые функции будут искать представление с именем
Car.cshtml
или с таким же именем, как у метода.

Компоновки

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

Views\Shared
и откройте файл
_Layout.cshtml
. Это полноценный HTML-файл с дескрипторами
и
.

Файл

_Layout.cshtml
является основой, в которую визуализируются другие представления. Кроме того, поскольку большая часть страницы (такая как разметка для навигации и верхнего и/или нижнего колонтитула) поддерживается страницей компоновки, страницы представлений сохраняются небольшими и простыми. Найдите в файле
_Layout.cshtml
следующую строку кода Razor:


@RenderBody()


Эта строка указывает странице компоновки, где визуализировать представление. Теперь перейдите к строке, расположенной прямо перед закрывающим дескриптором

, которая создает новый раздел для компоновки и объявляет его необязательным:


@await RenderSectionAsync("scripts", required: false)


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

required
) значения
true
. Вдобавок они могут визуализироваться синхронным образом:


@RenderSection("Header",true)


Любой код и/или разметка в блоке @ section файла представления будет визуализироваться не там, где вызывается

@RenderBody()
, а в месте определения раздела, присутствующего в компоновке. Например, пусть у вас есть представление со следующей реализацией раздела:


@section Scripts {

  

}


Код из представления визуализируется в компоновке на месте определения раздела. Если компоновка содержит показанное ниже определение:


@await RenderSectionAsync("Scripts", required: false)


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



В ASP.NET Core появились два новых метода:

IgnoreBody()
и
IgnoreSection()
. В случае помещения внутрь компоновки эти методы отменяют визуализацию тела представления или указанного раздела соответственно. Они позволяют включать или отключать функции представления в компоновке на основе условной логики, такой как уровни безопасности.

Указание стандартной компоновки для представлений

Как упоминалось ранее, стандартная страница компоновки определяется в файле

_ViewStart.cshtml
. Любое представление, где не указана компоновка, будет использовать компоновку, определенную в первом файле
_ViewStart.cshtml
, который обнаруживается в каталоге представления или выше него в структуре каталогов.

Частичные представления

Частичные представления концептуально похожи на пользовательские элементы управления в Web Forms. Частичные представления удобны для инкапсуляции пользовательского интерфейса, что помогает сократить объем повторяющегося кода и/или разметки. Частичное представление не задействует компоновку и внедряется внутрь другого представления или визуализируется с помощью компонента представления (рассматривается позже в главе).

Обновление компоновки с использованием частичных представлений

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

Создание частичных представлений

Создайте внутри каталога Shared новый каталог подназванием

Partials
и добавьте в него три пустых представления с именами
_Head.cshtml
,
_JavaScriptFiles.cshtml
и
_Menu.cshtml
.

Частичное представление Head

Вырежьте содержимое между дескрипторами

в компоновке и вставьте его в файл
_Head.cshtml
:


@ViewData["Title"] - AutoLot.Mvc


Замените разметку, удаленную из файла

_Layout.cshtml
, вызовом для визуализации нового частичного представления:


  


Дескриптор

— это еще один пример вспомогательной функции дескриптора. В атрибуте name указывается имя частичного представления с путем, начинающимся с текущего каталога представления, которым в данном случае является
Views\Shared
.

Частичное представление Menu

Для частичного представления

Menu
вырежьте всю разметку между дескрипторами
(не
) и вставьте ее в файл
Menu.cshtml
. Модифицируйте файл
Layout.cshtml
, чтобы визуализировать частичное представление
Menu
:


  

Частичное представление JavaScriptFiles

Наконец, вырежьте дескрипторы


Ниже приведена текущая разметка в файле

_Layout.cshtml
:


  

  

   

  

  

   

    @RenderBody()

   

  

  

   

    © 2021 - AutoLot.Mvc - 

asp-action="Privacy">Privacy

   

  

  

  @await RenderSectionAsync("Scripts", required: false)

Отправка данных представлениям

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

).

Строго типизированные представления и модели представлений

При передаче методу

View()
модели или модели представления значение присваивается свойству
@model
строго типизированного представления (обратите внимание на букву
m
в нижнем регистре):


@model IEnumerable


Свойство

@model
устанавливает тип для представления, к которому затем можно получать доступ с использованием Razor-команды
@Model
(обратите внимание на букву
М
в верхнем регистре):


@foreach (var item in Model)

{

  // Делать что-то.

}


В методе действия

RazorViewSyntax()
демонстрируется представление, получающее данные из этого метода действия:


[HttpGet]

public IActionResult RazorSyntax([FromServices] ICarRepo carRepo)

{

  var car = carRepo.Find(1);

  return View(car);

}


Значение модели может быть передано и в

, как показано ниже:


Объекты ViewBag, ViewData и TempData

Объекты

ViewBag
,
ViewData
и
TempData
являются механизмами для отправки представлению данных небольшого объема. В табл. 31.1 описаны три механизма передачи данных из контроллера в представление (помимо свойства
Model
) либо из контроллера в контроллер.



И

ViewBag
, и
ViewData
указывают на тот же самый объект; они просто предлагают разные способы доступа к данным. Еще раз взгляните на созданный ранее файл
_HeadPartial.cshtml
(важная строка выделена полужирным):


@ViewData["Title"] - AutoLot.Mvc


Вы заметите, что в атрибуте

для установки значения применяется объект
ViewData
. Поскольку
ViewData
— конструкция Razor, она предваряется символом
@
. Чтобы увидеть результаты, модифицируйте представление
RazorSyntax.cshtml
следующим образом:


@model AutoLot.Models.Entities.Car

@{

   ViewData["Title"] = "RazorSyntax";

}

Razor Syntax

...


Теперь после запуска приложенияи перехода поссылке

https://localhost:5001/Home/RazorSyntax
вы увидите на вкладке браузера заголовок Razor Syntax —
AutoLot.Mvc
(Синтаксис Razor —
AutoLot.Mvc
).

Вспомогательные функции дескрипторов

Вспомогательные функции дескрипторов являются новым средством, введенным в версии ASP.NET Core. Вспомогательная функция дескриптора (tag helper) — это разметка (специальный дескриптор или атрибут в стандартном дескрипторе), представляющий код серверной стороны, который затем помогает сформировать выпускаемую HTML-разметку Они значительно совершенствуют процесс разработки и улучшают читабельность представлений MVC.

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

Например, показанная ниже вспомогательная функция HTML создает метку для свойства

FullName
заказчика:


@Html.Label("FullName","Full Name:",new {@class="customer"})


В итоге генерируется следующая HTML-разметка:



По всей видимости, синтаксис вспомогательных функций HTML хорошо понятен разработчикам на языке С#, применяющим ASP.NET МУС и Razor. Но его нельзя считать интуитивно понятным, особенно для тех, кто имеет дело с HTML/CSS/JavaScript, но не с языком С#.

Версия в виде вспомогательной функции дескриптора выглядит так:



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

Существует множество встроенных вспомогательных функций дескрипторов, которые предназначены для применения вместо соответствующих им вспомогательных функций HTML. Однако не все вспомогательные функции HTML имеют ассоциированные вспомогательные функции дескрипторов. В табл. 31.2 перечислены самые распространенные вспомогательные функции дескрипторов, соответствующие им вспомогательные функции HTML и доступные атрибуты. Они будут раскрыты более подробно в оставшейся части главы.






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

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

_ViewImports.html
из стандартного шаблона уже содержит следующую строку:


@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers


Строка делает все вспомогательные функции дескрипторов из сборки

Microsoft.AspNetCore.Mvc.TagHelpers
(содержащей все встроенные вспомогательные функции дескрипторов) доступными всем представлениям на уровне каталога с файлом
_ViewImports.cshtml
и ниже него в иерархии каталогов.

Вспомогательная функция дескриптора для формы

Вспомогательная функция дескриптора для формы (

) заменяет вспомогательные функции HTML с именами
Html.BeginForm()
и
Html.BeginRouteForm()
. Скажем, чтобы создать форму, которая отправляет версию действия
Edit
для НТТР-метода POST контроллера
CarsController
с одним параметром (
Id
), потребуется следующий код и разметка:


  asp-route-id="@Model.Id" >


С точки зрения строгой HTML-разметки дескриптор

будет работать без атрибутов вспомогательной функции дескриптора для формы. Если атрибуты отсутствуют, тогда это просто обычная HTML-форма, к которой понадобится вручную добавить маркер защиты от подделки. Тем не менее, после добавления одного из атрибутов
asp-*
к форме добавляется и маркер защиты от подделки, который можно отключить, добавив к дескриптору
атрибут
asp-antiforgery="false"
. Маркер защиты от подделки рассматривается позже в главе.

Форма создания для сущности Car

Форма создания для сущности

Car
отправляется методу действия
Create()
класса
CarsController
. Добавьте в каталог
Views\Cars
новое пустое представление Razor по имени
Create.cshtml
со следующим содержимым:


@model Car

@{

  ViewData["Title"] = "Create";

}

Create a New Car


  

   

   

  


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

Car
. Блок кода Razor устанавливает специфичный к представлению заголовок для страницы HTML-дескриптор
имеет атрибуты
asp-controller
и
asp-action
, которые выполняются на серверной стороне для формирования дескриптора, а также добавления маркера защиты от подделки. Чтобы визуализировать это представление, добавьте в каталог
Controllers
новый контроллер по имени
CarsController
. Модифицируйте код, как показано ниже (позже в главе он будет обновлен):


using Microsoft.AspNetCore.Mvc;


namespace AutoLot.Mvc.Controllers

{

  [Route("[controller]/[action]")]

  public class CarsController : Controller

  {

   public IActionResult Create()

   {

    return View();

   }

  }

}


Теперь запустите приложение и перейдите по ссылке

http://localhost:5001/Cars/Create
. Инспектирование источника покажет, что форма имеет атрибут действия (
action
), основанный на
asp-controller
и
asp-action
, метод (
method
), установленный в
post
, и добавленный скрытый элемент
с именем
__RequestVerificationToken
:


  

 value="CfDJ8Hqg5HsrvCtOkkLRHY4ukxwv
ix0vkQ3vOvezvtJWdl0P5lwbI5-

FFWXh8KCFZo7eKxveCuK8NRJywj8Jz23pP2nV37fIGqqcITRyISGgq7tRYZDuPv8N

MIYz2nCWRiDbxOvlkg61DTDW9BrJxr8H63Y">


Далее в главе представление

Create
будет неоднократно обновляться.

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

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

POST
конечной точке
Create
:


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

Вспомогательная функция дескриптора для якоря (

<а>
) заменяет вспомогательную функцию HTML с именем
Html.ActionLink()
. Скажем, чтобы создать ссылку на представление
RazorSyntax
, применяйте такой код:


   asp-action="RazorSyntax">

  Razor Syntax


Для добавления страницы синтаксиса Razor в меню модифицируйте

_Menu.cshtml
, как показано ниже, добавив новый элемент меню между элементами Home (Домой) и Privacy (Секретность) (дескрипторы
  • , окружающие дескрипторы якорей, предназначены для меню Bootstrap):


    ...

  •   

        asp-action="Index">Home

  •   

    asp-action="RazorSyntax">Razor Syntax

  •   

    asp-action="Privacy">Privacy

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

    Вспомогательная функция дескриптора для элемента ввода (

    ) является одной из наиболее универсальных. В дополнение к автоматической генерации атрибутов
    id
    и
    name
    стандарта HTML, а также любых атрибутов
    data-val
    стандарта HTML5, вспомогательная функция дескриптора строит надлежащую HTML-разметку, основываясь на типе данных целевого свойства. В табл. 31.3 перечислены типы HTML, которые создаются на базе типов .NET Core свойств.



    Кроме того, вспомогательная функция дескриптора для элемента ввода добавит атрибуты

    type
    из HTML5, основываясь на аннотациях данных. В табл. 31.4 перечислены некоторые распространенные аннотации и генерируемые атрибуты
    type
    из HTML5.



    Шаблон редактирования

    Car.cshtml
    содержит дескрипторы
    для свойств
    PetName
    и
    Color
    . В качестве напоминания ниже приведены только эти дескрипторы:



    Вспомогательная функция дескриптора для элемента ввода добавляет к визуализируемому дескриптору атрибуты

    name
    и
    id
    , существующее значение для свойства (если оно есть) и атрибуты проверки достоверности HTML5. Оба поля являются обязательными и имеют ограничение на длину строки в 50 символов. Вот визуализированная разметка для указанных двух свойств:


       data-val-length="The field Pet 
    Name must be a string with a

    maximum length of 50." data-val-length-max="50"

       data-val-
    required="The Pet Name field is required."

       id="PetName" maxlength="50" name="PetName" 
    value="Zippy">

       data-val-length="The field 
    Color must be a string with a

    maximum length of 50." 
    data-val-length-max="50"

       data-val-
    required="The Color field is required."

       id="Color" maxlength="50" name="Color" value="Black" 

      aria-describedby="Color-error" aria-invalid="false">

    Вспомогательная функция дескриптора для текстовой области

    Вспомогательная функция дескриптора для текстовой области (

    Вспомогательная функция дескриптора для элемента выбора

    Вспомогательная функция дескриптора для элемента выбора (

    с надлежащими дескрипторами
    :



    Если значением свойства

    Country
    является
    CA
    , тогда в представление будет выведена показанная ниже разметка:


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

    Вспомогательные функции дескрипторов для сообщения проверки достоверности и для сводки по проверке достоверности в точности отражают вспомогательные функции HTML с именами

    Html.ValidationMessageFor()
    и
    Html.ValidationSummaryFor()
    . Первая применяется к HTML-дескриптору
    для отдельного свойства модели, а вторая — к HTML-дескриптору
    для целой модели. Сводка по проверке достоверности поддерживает варианты
    Аll
    (все ошибки),
    ModelOnly
    (ошибки только модели, но не свойств модели) и
    None
    (никаких ошибок).

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

    EditorTemplate
    в файле
    Car.cshtml
    (выделены полужирным):


     

     

     

     

     

     

     

     


    Эти вспомогательные функции дескрипторов будут отображать ошибки модели, возникшие во время привязки и проверки достоверности, как показано на рис. 31.3.


    Вспомогательная функция дескриптора для среды

    Вспомогательная функция дескриптора для среды (

    ) обычно используется для условной загрузки файлов JavaScript и CSS (или подходящей разметки) на основе среды, в которой запущен сайт. Откройте частичное представление
    _Head.cshtml
    и модифицируйте разметку следующим образом:


    @ViewData["Title"] - AutoLot.Mvc

      

      


    В первой вспомогательной функции дескриптора для среды применяется атрибут

    include="Development"
    , чтобы включить содержащиеся файлы, когда среда установлена в
    Development
    . В таком случае загружается неминифицированная версия Bootstrap. Во второй вспомогательной функции дескриптора для среды используется атрибут
    exclude="Development"
    , чтобы задействовать содержащиеся файлы, когда среда отличается от
    Development
    . В таком случае загружается минифицированная версия Bootstrap. Файл
    site.css
    остается тем же самым в среде
    Development
    и других средах, поэтому он загружается за пределами вспомогательной функции дескриптора для среды.

    Теперь модифицируйте частичное представление

    _JavaScriptFiles.cshtml
    , как показано ниже (обратите внимание, что файлы в разделе
    Development
    больше не имеют расширения
    .min
    ):


     

     

     

     

    Вспомогательная функция дескриптора для ссылки

    Вспомогательная функция дескриптора для ссылки (

    ) имеет атрибуты, применяемые с локальными и удаленными файлами. Атрибут
    asp-append-version
    , используемый с локальными файлами, добавляет хеш-значение для файла как параметр строки запроса в URL, который отправляется браузеру. При изменении файла изменяется и хеш-значение, обновляя посылаемый браузеру URL. Поскольку ссылка изменилась, браузер очищает кеш от этого файла и перезагружает его. Модифицируйте дескрипторы ссылок для
    bootstrap.css
    и
    site.css
    в файле
    _Head.cshtml
    следующим образом:


      

        asp-append-
    version="true"/>

      

    asp-append-version="true"/>


    Ссылка, отправляемая браузеру для файла

    site.css
    , теперь выглядит так (ваше хеш-значение будет другим):


       rel="stylesheet">


    При загрузке файлов CSS из сети доставки содержимого вспомогательные функции дескрипторов предоставляют механизм тестирования, позволяющий удостовериться в том, что файл был загружен надлежащим образом. Тест ищет конкретное значение для свойства в определенном классе CSS, и если свойство не дает совпадения, то вспомогательная функция дескриптора загрузит запасной файл. Модифицируйте раздел

    в файле
    _Head.cshtml
    , как показано ниже:


      

       href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/

    bootstrap.min.css"

       asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.css"

       asp-fallback-test-class="sr-only"

      asp-fallback-test-property="position"

       asp-fallback-
    test-value="absolute"

    crossorigin="anonymous"

      integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/

    iJTQUOhcWr7x9JvoRxT2MZw1T"/>

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

    Вспомогательная функция дескриптора для сценария (

     

      

      


    Частичное представление

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


      

      

      

      

      

    Вспомогательная функция дескриптора для изображения

    Вспомогательная функция дескриптора для изображения (

    ) предоставляет атрибут
    asp-append-version
    , который работает точно так же, как во вспомогательных функциях дескрипторов для ссылки и сценария.

    Специальные вспомогательные функции дескрипторов

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

    AutoLot.Mvc
    специальные вспомогательные функции дескрипторов заменят HTML-разметку, используемую для навигации между экранами CRUD для
    Car
    .

    Подготовительные шаги

    Специальные вспомогательные функции дескрипторов задействуют

    UrlHelperFactory
    и
    IActionContextAccessor
    для ссылок на основе маршрутизации. Кроме того, будет добавлен расширяющий метод для типа string, чтобы удалять суффикс
    Controller
    из имен контроллеров.

    Обновление Startup.cs

    Для создания экземпляра

    UrlFactory
    класса, производного не от класса
    Controller
    , в коллекцию служб потребуется добавить
    IActionContextAccessor
    . Начните с добавления в файл
    Startup.cs
    следующих пространств имен:


    using Microsoft.AspNetCore.Mvc.Infrastructure;

    using Microsoft.Extensions.DependencyInjection.Extensions;


    Затем добавьте в метод

    ConfigureServices()
    такую строку:


    services.TryAddSingleton();

    Создание расширяющего метода для типа string

    При обращении к именам контроллеров в коде инфраструктуре ASP.NET Core довольно часто требуется низкоуровневое строковое значение, не содержащее суффикс

    Controller
    , что препятствует использованию метода
    nameof()
    без последующего вызова
    string.Replace()
    . Со временем задача становится утомительной, поэтому для ее решения будет создан расширяющий метод для типа
    string
    .

    Создайте в проекте

    AutoLot.Services
    новый каталог по имени
    Utilities
    и добавьте в него файл
    StringExtensions.cs
    со статическим классом
    StringExtensions
    . Модифицируйте код, добавив расширяющий метод
    RemoveController()
    :


    using System;


    namespace AutoLot.Mvc.Extensions

    {

      public static class StringExtensions

      {

       public static string RemoveController(this string original)

        => original.Replace("Controller", "", StringComparison.OrdinalIgnoreCase);

      }

    }

    Создание базового класса

    Создайте в проекте

    AutoLot.Mvc
    новый каталог по имени
    TagHelpers
    и внутри него каталог
    Base
    . Добавьте в каталог
    Base
    файл класса
    ItemLinkTagHelperBase.cs
    , сделайте класс
    ItemLinkTagHelperBase
    открытым и абстрактным, а также унаследованным от класса
    TagHelper
    . Приведите код класса к следующему виду:


    using AutoLot.Mvc.Controllers;

    using AutoLot.Services.Utilities;

    using Microsoft.AspNetCore.Mvc;

    using Microsoft.AspNetCore.Mvc.Infrastructure;

    using Microsoft.AspNetCore.Mvc.Routing;

    using Microsoft.AspNetCore.Razor.TagHelpers;


    namespace AutoLot.Mvc.TagHelpers.Base

    {

      public abstract class ItemLinkTagHelperBase : TagHelper

      {

      }

    }


    Добавьте конструктор, который принимает экземпляры реализаций

    IActionContextAccessor
    и
    IUrlHelperFactory
    . Используйте
    UrlHelperFactory
    с
    ActionContextAccessor
    , чтобы создать экземпляр реализации
    IUrlHelper
    , и сохраните его в переменной уровня класса. Вот необходимый код:


    protected readonly IUrlHelper UrlHelper;

    protected ItemLinkTagHelperBase(IActionContextAccessor contextAccessor,

                     IUrlHelperFactory 
    urlHelperFactory)

    {

      UrlHelper = urlHelperFactory.GetUrlHelper(contextAccessor.ActionContext);

    }


    Добавьте открытое свойство для хранения

    Id
    элемента:


    public int? ItemId { get; set; }


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

    Process()
    , принимающий два параметра,
    TagHelperContext
    и
    TagHelperOutput
    . Параметр
    TagHelperContext
    применяется для получения остальных атрибутов дескриптора и словаря объектов, которые используются с целью взаимодействия с другими вспомогательными функциями дескрипторов, нацеленными на дочерние элементы. Параметр
    TagHelperOutput
    применяется для создания визуализированного вывода. Поскольку это базовый класс, создайте метод по имени
    BuildContent()
    , который производные классы смогут вызывать из метода
    Process()
    . Добавьте следующий код:


    protected void BuildContent(TagHelperOutput output,

      string actionName, string className, string displayText, string fontAwesomeName)

    {

      output.TagName = "a";  // Заменить  дескриптором .

      var target = (ItemId.HasValue)

      ? UrlHelper.Action(actionName,

        nameof(CarsController).RemoveController(),
     new {id = ItemId})

       : UrlHelper.Action(actionName, nameof(CarsController).RemoveController());

      output.Attributes.SetAttribute("href", target);

      output.Attributes.Add("class",className);

      output.Content.AppendHtml($@"{displayText}

       ");

    }

    В предыдущем код присутствует ссылка на набор инструментов для значков и шрифтов Font Awesome, который будет добавлен в проект позже в главе.

    Вспомогательная функция дескриптора для вывода сведений об элементе

    Создайте в каталоге

    TagHelpers
    новый файл класса по имени
    ItemDetailsTagHelper.cs
    . Сделайте класс
    ItemDetailsTagHelper
    открытым и унаследованным от класса
    ItemLinkTagHelperBase
    . Добавьте в новый файл показанный ниже код:


    using AutoLot.Mvc.Controllers;

    using AutoLot.Mvc.TagHelpers.Base;

    using Microsoft.AspNetCore.Mvc.Infrastructure;

    using Microsoft.AspNetCore.Mvc.Routing;

    using Microsoft.AspNetCore.Razor.TagHelpers;


    namespace AutoLot.Mvc.TagHelpers

    {

      public class ItemDetailsTagHelper : ItemLinkTagHelperBase

      {

      }

    }


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


    public ItemDetailsTagHelper(

       IActionContextAccessor contextAccessor,

       IUrlHelperFactory urlHelperFactory)

        : base(contextAccessor, urlHelperFactory) {}


    Переопределите метод

    Process()
    , чтобы вызывать метод
    BuildContent()
    базового класса:


    public override void Process(TagHelperContext context, TagHelperOutput output)

    {

      BuildContent(output,nameof(CarsController.Details),

           "text-info","Details","info-circle");

    }


    Код создает ссылку Details (Детали) с изображением значка информации из Font Awesome. Чтобы не возникали ошибки при компиляции, добавьте в

    CarsController
    базовый метод
    Details()
    :


    public IActionResult Details()

    {

      return View();

    }

    Вспомогательная функция дескриптора для удаления элемента

    Создайте в каталоге

    TagHelpers
    новый файл класса по имени
    ItemDeleteTagHelper.cs
    . Сделайте класс
    ItemDeleteTagHelper
    открытым и унаследованным от класса
    ItemLinkTagHelperBase
    . Добавьте в новый файл следующий код:


    using AutoLot.Mvc.Controllers;

    using AutoLot.Mvc.TagHelpers.Base;

    using Microsoft.AspNetCore.Mvc.Infrastructure;

    using Microsoft.AspNetCore.Mvc.Routing;

    using Microsoft.AspNetCore.Razor.TagHelpers;


    namespace AutoLot.Mvc.TagHelpers

    {

      public class ItemDeleteTagHelper : ItemLinkTagHelperBase

      {

      }

    }


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


    public ItemDeleteTagHelper(

       IActionContextAccessor contextAccessor,

       IUrlHelperFactory urlHelperFactory)

        : base(contextAccessor, urlHelperFactory) {}


    Переопределите метод

    Process()
    , чтобы вызывать метод
    BuildContent()
    базового класса:


    public override void Process(TagHelperContext context, TagHelperOutput output)

    {

      BuildContent(output,nameof(CarsController.Delete),"text-danger","Delete","trash");

    }


    Код создает ссылку Delete (Удалить) с изображением значка мусорного ящика из Font Awesome. Чтобы не возникали ошибки при компиляции, добавьте в

    CarsController
    базовый метод
    Delete()
    :


    public IActionResult Delete()

    {

      return View();

    }

    Вспомогательная функция дескриптора для редактирования сведений об элементе

    Создайте в каталоге

    TagHelpers
    новый файл класса по имени
    ItemEditTagHelper.cs
    . Сделайте класс
    ItemEditTagHelper
    открытым и унаследованным от класса
    ItemLinkTagHelperBase
    . Добавьте в новый файл показанный ниже код:


    using AutoLot.Mvc.Controllers;

    using AutoLot.Mvc.TagHelpers.Base;

    using Microsoft.AspNetCore.Mvc.Infrastructure;

    using Microsoft.AspNetCore.Mvc.Routing;

    using Microsoft.AspNetCore.Razor.TagHelpers;


    namespace AutoLot.Mvc.TagHelpers

    {

      public class ItemEditTagHelper : ItemLinkTagHelperBase

      {

      }

    }


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


    public ItemEditTagHelper(

       IActionContextAccessor contextAccessor,

       IUrlHelperFactory urlHelperFactory)

        : base(contextAccessor, urlHelperFactory) {}


    Переопределите метод

    Process()
    , чтобы вызывать метод
    BuildContent()
    базового класса:


    public override void Process(TagHelperContext context, TagHelperOutput output)

    {

      BuildContent(output,nameof(CarsController.Edit),"text-warning","Edit","edit");

    }


    Код создает ссылку Edit (Редактировать) с изображением значка карандаша из Font Awesome. Чтобы не возникали ошибки при компиляции, добавьте в

    CarsController
    базовый метод
    Edit()
    :


    public IActionResult Edit()

    {

      return View();

    }

    Вспомогательная функция дескриптора для создания элемента

    Создайте в каталоге

    TagHelpers
    новый файл класса по имени
    itemCreateTagHelper.cs
    . Сделайте класс
    ItemCreateTagHelper
    открытым и унаследованным от класса
    ItemLinkTagHelperBase
    . Добавьте в новый файл следующий код:


    using AutoLot.Mvc.Controllers;

    using AutoLot.Mvc.TagHelpers.Base;

    using Microsoft.AspNetCore.Mvc.Infrastructure;

    using Microsoft.AspNetCore.Mvc.Routing;

    using Microsoft.AspNetCore.Razor.TagHelpers;


    namespace AutoLot.Mvc.TagHelpers

    {

      public class ItemCreateTagHelper : ItemLinkTagHelperBase

      {

      }

    }


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


    public ItemCreateTagHelper(

       IActionContextAccessor contextAccessor,

       IUrlHelperFactory urlHelperFactory)

        : base(contextAccessor, urlHelperFactory) {}


    Переопределите метод

    Process()
    , чтобы вызывать метод
    BuildContent()
    базового класса:


    public override void Process(TagHelperContext context, TagHelperOutput output)

    {

      BuildContent(output,nameof(CarsController.Create),"text-success","Create new","plus");

    }


    Код создает ссылку Create new (Создать) с изображением значка плюса из Font Awesome.

    Вспомогательная функция дескриптора для вывода списка элементов

    Создайте в каталоге

    TagHelpers
    новый файл класса по имени
    ItemListTagHelper.cs
    . Сделайте класс
    ItemListTagHelper
    открытым и унаследованным от класса
    ItemLinkTagHelperBase
    . Добавьте в новый файл показанный ниже код:


    using AutoLot.Mvc.Controllers;

    using AutoLot.Mvc.TagHelpers.Base;

    using Microsoft.AspNetCore.Mvc.Infrastructure;

    using Microsoft.AspNetCore.Mvc.Routing;

    using Microsoft.AspNetCore.Razor.TagHelpers;


    namespace AutoLot.Mvc.TagHelpers

    {

      public class ItemListTagHelper : ItemLinkTagHelperBase

      {

      }

    }


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


    public ItemListTagHelper(

       IActionContextAccessor contextAccessor,

       IUrlHelperFactory urlHelperFactory)

        : base(contextAccessor, urlHelperFactory) {}


    Переопределите метод

    Process()
    , чтобы вызывать метод
    BuildContent()
    базового класса:


    public override void Process(TagHelperContext context, TagHelperOutput output)

    {

      BuildContent(output,nameof(CarsController.Index),

           "text-default","Back to List","list");

    }


    Код создает ссылку Back to List (Список) с изображением значка списка из Font Awesome. Чтобы не возникали ошибки при компиляции, добавьте в

    CarsController
    базовый метод
    Index()
    :


    public IActionResult Index()

    {

      return View();

    }

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

    Чтобы сделать специальные вспомогательные функции дескрипторов видимыми, потребуется выполнить команду

    @addTagHelper
    для представлений, которые используют эти вспомогательные функции дескрипторов, или поместить ее в файл
    _ViewImports.cshtml
    . Откройте файл
    _ViewImports.cshtml
    в каталоге Views и добавьте в него следующую строку:


    @addTagHelper *, AutoLot.Mvc

    Вспомогательные функции HTML

    Вспомогательные функции HTML из ASP.NET MVC по-прежнему поддерживаются, а некоторые из них применяются довольно широко и перечислены в табл. 31.5.


    Вспомогательная функция DisplayFor()

    Вспомогательная функция

    DisplayFor()
    отображает объект, определяемый выражением. Если для отображаемого типа существует шаблон отображения, тогда он будет применяться при создании HTML-разметки, представляющей элемент. Например, если моделью представления является сущность
    Car
    , то информацию о производителе автомобиля можно отобразить следующим образом:


    @Html.DisplayFor(x=>x.MakeNavigation);


    Если в каталоге

    DisplayTemplates
    присутствует представление по имени
    Make.cshtml
    , тогда оно будет использоваться для визуализации значений (вспомните, что поиск имени шаблона базируется на типе объекта, а не на имени его свойства). Если представление по имени
    ShowMake.cshtml
    (например) существует, то оно будет применяться для визуализации объекта с помощью приведенного ниже вызова:


    @Html.DisplayFor(x=>x.MakeNavigation, "ShowMake");


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

    Вспомогательная функция DisplayForModel()

    Вспомогательная функция

    DisplayForModel()
    отображает модель для представления. Если для отображаемого типа существует шаблон отображения, то он будет применяться при создании HTML-разметки, представляющей элемент. Продолжая предыдущий пример представления с сущностью
    Car
    в качестве модели, полную информацию
    Car
    можно отобразить следующим образом:


    @Html.DisplayForModel();


    Как и в случае со вспомогательной функцией

    DisplayFor()
    , если существует шаблон отображения, имеющий имя типа, тогда он будет использоваться. Можно также применять именованные шаблоны. Скажем, для отображения сущности
    Car
    с помощью шаблона отображения
    CarWithColors.html
    необходимо использовать такой вызов:


    @Html.DisplayForModel("CarWithColors");


    Если шаблон не указан и отсутствует представление с именем класса, то для создания HTML-разметки, подлежащей отображению, используется рефлексия.

    Вспомогательные функции EditorFor() и EditorForModel()

    Вспомогательные функции

    EditorFor()
    и
    EditorForModel()
    работают аналогично соответствующим вспомогательным функциям для отображения, но с тем отличием, что шаблоны ищутся в каталоге
    EditorTemplates
    и вместо представления объекта, предназначенного только для чтения, отображаются HTML-формы редакторов.

    Управление библиотеками клиентской стороны

    До завершения представлений нужно обновить библиотеки клиентской стороны (CSS и JavaScript). Проект диспетчера библиотек LibraryManager (первоначально разрабатываемый Мэдсом Кристенсеном) теперь является частью Visual Studio (VS2019) и также доступен в виде глобального инструмента .NET Core. Для извлечения инструментов CSS и JavaScript из

    CDNJS.com
    ,
    UNPKG.com
    ,
    jsDelivr.com
    или файловой системы в
    LibraryManager
    используется простой файл JSON.

    Установка диспетчера библиотек как глобального инструмента .NET Core

    Диспетчер библиотек встроен в Visual Studio. Чтобы установить его как глобальный инструмент .NET Core, введите следующую команду:


    dotnet tool install --global Microsoft.Web.LibraryManager.Cli --version 2.1.113


    Текущая версия диспетчера библиотек доступна по ссылке

    https://www.nuget.org/packages/Microsoft.Web.LibraryManager.Cli/
    .

    Добавление в проект AutoLot.Mvc библиотек клиентской стороны

    При создании проекта

    AutoLot.Mvc
    (с помощью Visual Studio или командной строки .NET Core CLI) в каталог
    wwwroot\lib
    было установлено несколько файлов JavaScript и CSS. Удалите каталог
    lib
    вместе со всеми содержащимися в нем файлами, т.к. все они будут заменены диспетчером библиотек.

    Добавление файла libman.json

    Файл

    libman.json
    управляет тем, что именно устанавливается, из каких источников и куда попадают установленные файлы.

    Visual Studio

    Если вы работаете в Visual Studio, тогда щелкните правой кнопкой мыши на имени проекта

    AutoLot.Mvc
    и выберите в контекстном меню пункт Manage Client-Side Libraries (Управлять библиотеками клиентской стороны), в результате чего в корневой каталог проекта добавится файл
    libman.json
    . В Visual Studio также есть возможность связать диспетчер библиотек с процессом MSBuild. Щелкните правой кнопкой мыши на имени файла
    libman.json
    и выберите в контекстном меню пункт Enable restore on build (Включить восстановление при сборке). Вам будет предложено разрешить другому пакету NuGet (
    Microsoft.Web.LibraryManager.Build
    ) восстановиться в проекте. Разрешите установку пакета.

    Командная строка

    Создайте новый файл

    libman.json
    посредством следующей команды (она устанавливает
    CDNJS.com
    в качестве стандартного поставщика):


    libman init --default-provider cdnjs

    Обновление файла libman.json

    Для поиска библиотек, подлежащих установке, сеть доставки содержимого

    CDNJS.com
    предлагает удобный для человека API-интерфейс. Список всех доступных библиотек можно просмотреть по следующему URL:


    https://api.cdnjs.com/libraries?output=human


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


    https://api.cdnjs.com/libraries/jquery?output=human


    После выбора версии и файлов для установки добавьте имя библиотеки (плюс версию), место назначения (обычно

    wwwroot/lib/<ИмяБиблиотеки>
    ) и файлы, которые требуется загрузить. Скажем, чтобы загрузить jQuery, введите в массив JSON библиотеки следующий код:


    {

      "library": "jquery@3.5.1",

      "destination": "wwwroot/lib/jquery",

      "files": [ "jquery.js"]

    }


    Ниже приведено полное содержимое файла

    libman.json
    , где указаны все файлы, необходимые для разрабатываемого приложения:


    {

      "version": "1.0",

      "defaultProvider": "cdnjs",

      "defaultDestination": "wwwroot/lib",

      "libraries": [

       {

        "library": "jquery@3.5.1",

       "destination": "wwwroot/lib/jquery",

        "files": [ "jquery.js", "jquery.min.js" ]

       },

       {

        "library": "jquery-validate@1.19.2",

        "destination": "wwwroot/lib/jquery-validation",

        "files": [ "jquery.validate.js", "jquery.validate.min.js",

              "additional-methods.js", 
    "additional-methods.min.js" ]

       },

       {

        "library": "jquery-validation-unobtrusive@3.2.11",

        "destination": "wwwroot/lib/jquery-validation-unobtrusive",

        "files": [ "jquery.validate.unobtrusive.js",

             "jquery.validate.unobtrusive.min.js" ]

       },

       {

        "library": "twitter-bootstrap@4.5.3",

        "destination": "wwwroot/lib/bootstrap",

        "files": [

         "css/bootstrap.css",

         "js/bootstrap.bundle.js",

         "js/bootstrap.js"

        ]

       },

       {

        "library": "font-awesome@5.15.1",

        "destination": "wwwroot/lib/font-awesome/",

        "files": [

         "js/all.js",

         "css/all.css",

         "sprites/brands.svg",

         "sprites/regular.svg",

         "sprites/solid.svg",

         "webfonts/fa-brands-400.eot",

         "webfonts/fa-brands-400.svg",

         "webfonts/fa-brands-400.ttf",

         "webfonts/fa-brands-400.woff",

         "webfonts/fa-brands-400.woff2",

         "webfonts/fa-regular-400.eot",

         "webfonts/fa-regular-400.svg",

         "webfonts/fa-regular-400.ttf",

         "webfonts/fa-regular-400.woff",

         "webfonts/fa-regular-400.woff2",

         "webfonts/fa-solid-900.eot",

         "webfonts/fa-solid-900.svg",

         "webfonts/fa-solid-900.ttf",

         "webfonts/fa-solid-900.woff",

         "webfonts/fa-solid-900.woff2"

        ]

       }

      ]

    }


    На заметку! Вскоре будет объяснена причина отсутствия в списке минифицированных файлов.


    После сохранения

    libman.json
    (в Visual Studio) файлы будут загружены в каталог
    wwwroot\lib
    проекта. Если же вы работаете в командной строке, тогда введите следующую команду, чтобы перезагрузить все файлы:


    libman restore


    Доступны дополнительные параметры командной строки, которые можно просмотреть с помощью команды

    libman -h
    .

    Обновление ссылок на файлы JavaScript и CSS

    С переходом на диспетчер библиотек местоположение многих файлов JavaScript и CSS изменилось. Файлы Bootstrap и jQuery были загружены в каталог

    \dist
    . Кроме того, в приложение был добавлен набор инструментов для значков и шрифтов Font Awesome.

    Местоположение файлов Bootstrap необходимо изменить на

    ~/lib/boostrap/css
    вместо
    ~/lib/boostrap/dist/css
    . Добавьте Font Awesome в конец, прямо перед
    site.css
    . Модифицируйте файл
    _Head.cshtml
    , как показано ниже:


    @ViewData["Title"] - AutoLot.Mvc

     

      asp-append-
    version="true"/>

     

      asp-fallback-href="~/lib/bootstrap/css/bootstrap.css"

      asp-fallback-test-class="sr-only"

       asp-fallback-test-property="position"

       asp-fallback-
    test-value="absolute"

      crossorigin="anonymous"

      integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/

    iJTQUOhcWr7x9JvoRxT2MZw1T"/>

     asp-append-version="true"/>

     asp-append-version="true"/>


    Далее модифицируйте файл

    JavaScriptFiles.cshtml
    , удалив
    \dist
    из местоположений jQuery и Bootstrap:


      

      

      

     


    Финальное изменение связано с обновлением местоположений

    jquery.validate
    в частичном представлении
    _ValidationScriptsPartial.cshtml
    :


      

      

     

      

    Завершение работы над представлениями CarsController и Cars

    В этом разделе будет завершена работа над представлениями

    CarsController
    и
    Cars
    . Если вы установите в
    true
    флаг
    RebuildDatabase
    внутри файла
    appsettings.development.json
    , тогда любые изменения,внесенные вами во время тестирования этих представлений, будут сбрасываться при следующем запуске приложения.

    Класс CarsController

    Класс

    CarsController
    является центральной точкой приложения
    AutoLot.Mvc
    , обладая возможностями создания, чтения, обновления и удаления. В этой версии
    CarsController
    напрямую используется уровень доступа к данным. Позже в главе вы создадите еще одну версию
    CarsController
    , в которой для доступа к данным будет применяться служба
    AutoLot.Api
    .

    Приведите операторы

    using
    в классе
    CarsController
    к следующему виду:


    using AutoLot.Dal.Repos.Interfaces;

    using AutoLot.Models.Entities;

    using AutoLot.Services.Logging;

    using Microsoft.AspNetCore.Mvc;

    using Microsoft.AspNetCore.Mvc.Rendering;


    Ранее вы добавили класс контроллера с маршрутом. Теперь наступило время добавить экземпляры реализаций

    ICarRepo
    и
    IAppLogging
    через внедрение зависимостей. Добавьте две переменные уровня класса для хранения этих экземпляров, а также конструктор, который будет внедрять оба экземпляра:


    private readonly ICarRepo _repo;

    private readonly IAppLogging _logging;

    public CarsController(ICarRepo repo, IAppLogging logging)

    {

      _repo = repo;

      _logging = logging;

    }

    Частичное представление списка автомобилей

    Списковые представления (одно для целого реестра автомобилей и одно для списка автомобилей по производителям) совместно используют частичное представление. Создайте в каталоге

    Views\Cars
    новый каталог по имени
    Partials
    и добавьте в него файл представления
    _CarListPartial.cshtml
    , очистив его содержимое. Установите
    IEnumerable
    в качестве типа (его ненужно указывать полностью, поскольку в файл
    _ViewImports.cshtml
    добавлено пространство имен
    AutoLot.Models.Entities
    ):


    @model IEnumerable< Car>


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

    CarListPartial.cshtml
    применяется полным реестром автомобилей, производители будут показаны, а когда отображаются автомобили только одного производителя, то поле
    Make
    должно быть скрыто:


    @{

       var showMake = true;

       if (bool.TryParse(ViewBag.ByMake?.ToString(), out bool byMake))

       {

         showMake = !byMake;

       }

    }


    В следующей разметке

    ItemCreateTagHelper
    используется для создания ссылки на метод
    Create()
    типа
    HttpGet
    . В случае применения специальных вспомогательных функций дескрипторов имя указывается с использованием "шашлычного" стиля в нижнем регистре, т.е. суффикс
    TagHelper
    отбрасывается, а каждое слово в стиле Pascal приводится к нижнему регистру и отделяется символом переноса (что похоже на шашлык):


      


    Для настройки таблицы и ее заголовков применяется вспомогательная функция HTML, посредством которой получаются значения

    DisplayName
    , связанные с каждым свойством. Для
    DisplayName
    будет выбираться значение атрибута
    Display
    или
    DisplayName
    , и если он не установлен, то будет использоваться имя свойства. В следующем разделе применяется блок кода Razor для отображения информации о производителе на основе ранее установленной переменной уровня представления:


      

      

      @if (showMake)

       {

        

       }

       

       

       

      

      


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

    DisplayFor()
    . Эта вспомогательная функция HTML ищет шаблон отображения с именем, соответствующим типу свойства, и если шаблон не обнаруживается, то разметка создается стандартным образом. Для каждого свойства объекта также выполняется поиск шаблона отображения, который применяется при его наличии. Например, если
    Car
    имеет свойство
    DateTime
    , то для него будет использоваться показанный ранее в главе шаблон
    DisplayTemplate
    .

    В следующем блоке также задействованы специальные вспомогательные функции дескрипторов

    item-edit
    ,
    item-details
    и
    item-delete
    , которые были добавлены ранее. Обратите внимание, что при передаче значений открытому свойству специальной вспомогательной функции имя свойства указывается с применением "шашлычного" стиля в нижнем регистре и добавляется к дескриптору в виде атрибута:


      

       @foreach (var item in Model)

       {

        

         @if (showMake)

         {

          

         }

         

         

         

        

       }

       

         @Html.DisplayNameFor(model => model.MakeId)

        

        @Html.DisplayNameFor(model => model.Color)

       

        @Html.DisplayNameFor(model => model.PetName)

       

           @Html.DisplayFor(modelItem => item.MakeNavigation.Name)

          

          @Html.DisplayFor(modelItem => item.Color)

         

          @Html.DisplayFor(modelItem => item.PetName)

         

           |

           |

          

         

    Представление Index

    При наличии частичного представления

    _CarListPartial
    представление
    Index
    будет небольшим. Создайте в каталоге
    Views\Cars
    новый файл представления по имени
    Index.cshtml
    . Удалите весь сгенерированный код и добавьте следующую разметку:


    @model IEnumerable

    @{

      ViewData["Title"] = "Index";

    }

    Vehicle Inventory


    Частичное представление

    _CarListPartial
    вызывается со значением модели содержащего представления (
    IEnumerable
    ), которое передается с помощью атрибута
    model
    . В итоге модель частичного представления устанавливается в объект, переданный вспомогательной функции дескриптора
    .

    Чтобы взглянуть на представление

    Index
    , модифицируйте метод
    Index()
    класса
    CarsController
    , как показано ниже:


    [Route("/[controller]")]

    [Route("/[controller]/[action]")]

    public IActionResult Index()

      => View(_repo.GetAllIgnoreQueryFilters());


    Запустив приложение и перейдя по ссылке

    https://localhost:5001/Cars/Index
    , вы увидите список автомобилей (рис. 31.4).



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

    Представление ВуMake

    Представление

    ВуMake
    похоже на
    Index
    , но настраивает частичное представление так, что информация о производителе отображается только в заголовке страницы. Создайте в каталоге
    Views\Cars
    новый файл представления по имени
    ВуMake.cshtml
    . Удалите весь сгенерированный код и добавьте следующую разметку:


    @model IEnumerable

    @{

       ViewData["Title"] = "Index";

    }

    Vehicle Inventory for @ViewBag.MakeName

    @{

       var mode = new ViewDataDictionary(ViewData) {{"ByMake", true}};

    }


    Отличия заметить легко. Здесь создается экземпляр

    ViewDataDictionary
    , содержащий свойство
    ByMake
    из
    ViewBag
    , который затем вместе с моделью передается частичному представлению, что позволяет скрыть информацию о производителе. Метод действия для этого представления должен получить все автомобили с указанным значением
    MakeId
    и установить
    ViewBag
    в
    MakeName
    с целью отображения в пользовательском интерфейсе. Оба значения берутся из маршрута. Добавьте в класс
    CarsController
    новый метод действия по имени
    ByMake()
    :


    [HttpGet("/[controller]/[action]/{makeId}/{makeName}")]

    public IActionResult ByMake(int makeId, string makeName)

    {

      ViewBag.MakeName = makeName;

      return View(_repo.GetAllBy(makeId));

    }


    Запустив приложение и перейдя по ссылке

    https://localhost:5001/Cars/l/VW
    , вы увидите список, показанный на рис. 31.5.


    Представление Details

    Создайте в каталоге

    Views\Cars
    новый файл представления по имени
    Details.cshtml
    . Удалите весь сгенерированный код и добавьте следующую разметку:


    @model Car

    @{

      ViewData["Title"] = "Details";

    }

    Details for @Model.PetName

    @Html.DisplayForModel()

      

      

      


    Вспомогательная функция

    @Html.DisplayForModel()
    использует созданный ранее шаблон отображения (
    Car.cshtml
    ) для вывода детальной информации об автомобиле.

    Прежде чем обновлять метод действия

    Details()
    , добавьте вспомогательный метод по имени
    GetOne()
    , который будет извлекать одиночную запись
    Car
    :


    internal Car GetOneCar(int? id) => !id.HasValue ? null : _repo.Find(id.Value);


    Модифицируйте метод действия

    Details()
    следующим образом:


    [HttpGet("{id?}")]

    public IActionResult Details(int? id)

    {

      if (!id.HasValue)

      {

       return BadRequest();

      }

      var car = GetOneCar(id);

      if (car == null)

      {

       return NotFound();

      }

      return View(car);

    }


    Маршрут для метода действия

    Details()
    содержит необязательный параметр маршрута
    id
    с идентификатором автомобиля, значение которого присваивается параметру
    id
    метода. Обратите внимание, что у параметра маршрута есть вопросительный знак с маркером. Это указывает на необязательность параметра, почти как вопросительный знак в типе
    int?
    делает переменную
    int
    допускающей значение
    null
    . Если параметр не был предоставлен или оболочка службы не может отыскать автомобиль с идентификатором, заданным в параметре маршрута, тогда метод возвращает ошибку
    NotFound
    . В противном случае метод отправляет найденную запись
    Car
    представлению
    Details
    . Запустив приложение и перейдя по ссылке
    https://localhost:5001/Cars/Details/1
    , вы увидите экран, показанный на рис. 31.6.


    Представление Create

    Представление

    Create
    было начато ранее. Вот его полная разметка:


    @model Car

    @{

       ViewData["Title"] = "Create";

    }

    Create a New Car


     

      

       

       @Html.EditorForModel()

       

        

          class="btn btn-success">Create 

           |  

         

       

      

     

    @section Scripts {

       

    }


    Вспомогательная функция

    @Html.EditorForModel()
    использует созданный ранее шаблон отображения (
    Car.cshtml
    ) для отображения редактора сведений об автомобиле.

    В разделе

    Scripts
    представления указано частичное представление
    _ValidationScriptsPartial
    . Вспомните, что в компоновке этот раздел встречается после загрузки jQuery. Шаблон разделов помогает гарантировать загрузку надлежащих зависимостей до загрузки самого содержимого.

    Методы действий Create()

    В рамках процесса создания применяются два метода действий: первый (

    HttpGet
    ) возвращает пустое представление для ввода новой записи, а второй (
    HttpPut
    ) отправляет значения новой записи.

    Вспомогательный метод GetMakes()

    Вспомогательный метод

    GetMakes()
    возвращает список записей
    Make
    в виде экземпляра
    SelectList
    и принимает в качестве параметра экземпляр реализации
    IMakeRepo
    :


    internal SelectList GetMakes(IMakeRepo makeRepo)

      => new SelectList(makeRepo.GetAll(), nameof(Make.Id), nameof(Make.Name));

    Метод действия Create() для GET

    Метод действия

    Create()
    для
    GET
    помещает в словарь
    ViewData
    список
    SelectList
    с записями
    Make
    и отправляет его представлению
    Create
    :


    [HttpGet]

    public IActionResult Create([FromServices] IMakeRepo makeRepo)

    {

      ViewData["MakeId"] = GetMakes(makeRepo);

      return View();

    }


    Форму создания можно просмотреть по ссылке

    /Cars/Create
    (рис. 31.7).


    Метод действия Create() для POST

    Метод действия

    Create()
    для
    POST
    применяет неявную привязку модели для создания сущности
    Car
    из значений формы. Вот его код:


    [HttpPost]

    [ValidateAntiForgeryToken]

    public IActionResult Create([FromServices] IMakeRepo makeRepo, Car car)

    {

      if (ModelState.IsValid)

      {

       _repo.Add(car);

       return RedirectToAction(nameof(Details),new {id = car.Id});

      }

      ViewData["MakeId"] = GetMakes(makeRepo);

      return View(car);

    }


    Атрибут

    HttpPost
    помечает метод как конечную точку приложения для маршрута
    Cars/Create
    , когда запросом является
    POST
    . Атрибут
    ValidateAntiForgeryToken
    , использует значение скрытого элемента ввода для
    __RequestVerificationToken
    чтобы сократить количество атак на сайт.

    Экземпляр реализации

    IMakeRepo
    внедряется в метод из контейнера DI. Поскольку внедрение осуществляется в метод, применяется атрибут
    FromServices
    . Как вы наверняка помните, атрибут
    FromServices
    сообщает механизму привязки о том, чтобы он не пытался привязывать этот тип, и позволяет контейнеру DI узнать о необходимости создания экземпляра класса.

    Сущность

    Car
    неявно привязывается к данным входящего запроса. Если состояние модели (
    ModelState
    ) допустимо, тогда сущность
    Car
    добавляется в базу данных и пользователь перенаправляется на метод действия
    Details()
    с использованием вновь созданного идентификатора
    Car
    в качестве параметра маршрута. Такой шаблон называется "отправка-перенаправление-получение" (
    Post-Redirect-Get
    ). Пользователь выполняет отправку с помощью метода
    HttpPost(Create()
    ) и затем перенаправляется на метод
    HttpGet(Details()
    ), что предотвращает повторную отправку браузером запроса
    POST
    , если пользователь решит обновить страницу.

    Если состояние модели не является допустимым, то список

    SelectList
    с записями
    Make
    добавляется в объект
    ViewData
    и сущность, которая была отправлена, посылается обратно представлению
    Create
    . Состояние модели тоже неявно отправляется представлению, так что могут быть отображены любые ошибки.

    Представление Edit

    Создайте в каталоге

    Views\Cars
    новый файл представления по имени
    Edit.cshtml
    . Удалите весь сгенерированный код и добавьте следующую разметку:


    @model Car

    @{

       ViewData["Title"] = "Edit";

    }

    Edit @Model.PetName


      

       

        asp-route-id="@Model.Id">

        @Html.EditorForModel()

        

        

        

      

           Save 

           |  

         

        

       

      

    @section Scripts {

       

    }

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

    @Html.EditorForModel()
    и частичное представление
    _ValidationScriptsPartial
    . Однако оно еще содержит два скрытых элемента ввода для
    Id
    и
    TimeStamp
    . Они будут отправляться вместе с остальными данными формы, но не должны редактироваться пользователями. Без значений
    Id
    и
    TimeStamp
    не удалось бы сохранять изменения.

    Методы действий Edit()

    В рамках процесса редактирования используются два метода действий: первый (

    HttpGet
    ) возвращает сущность, подлежащую редактированию, а второй (
    HttpPut
    ) отправляет значения обновленной записи.

    Метод действия Edit() для GET

    Метод действия

    Edit()
    для
    GET
    получает одиночную запись
    Car
    с идентификатором
    Id
    через оболочку службы и отправляет ее представлению
    Edit
    :


    [HttpGet("{id?}")]

    public IActionResult Edit([FromServices] IMakeRepo makeRepo, int? id)

    {

      var car = GetOneCar(id);

      if (car == null)

      {

       return NoContent();

      }

      ViewData["MakeId"] = GetMakes(makeRepo);

      return View(car);

    }


    Маршрут имеет необязательный параметр

    id
    , значение которого передается методу с применением параметра
    id
    . Экземпляр реализации
    IMakeRepo
    внедряется в метод и используется для создания списка
    SelectList
    записей
    Make
    . Посредством вспомогательного метода
    GetOneCar()
    получается запись
    Car
    . Если запись
    Car
    найти не удалось, тогда метод возвращает ошибку
    NoContent
    . В противном случае он добавляет список
    SelectList
    записей
    Make
    в словарь
    ViewData
    и визуализирует представление
    Edit
    .

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

    /Cars/Edit/1
    (рис. 31.8).


    Метод действия Edit() для POST

    Метод действия

    Edit()
    для
    POST
    аналогичен методу действия
    Create()
    для
    POST
    с отличиями, описанными после кода метода:


    [HttpPost("{id}")]

    [ValidateAntiForgeryToken]

    public IActionResult Edit([FromServices] IMakeRepo makeRepo, int id, Car car)

    {

      if (id != car.Id)

      {

       return BadRequest();

      }

      if (ModelState.IsValid)

      {

       _repo.Update(car);

       return RedirectToAction(nameof(Details),new {id = car.Id});

      }

      ViewData["MakeId"] = GetMakes(makeRepo);

      return View(car);

    }


    Метод действия

    Edit()
    для
    POST
    принимает один обязательный параметр маршрута
    id
    . Если его значение не совпадает со значением
    Id
    реконструированной сущности
    Car
    , тогда клиенту отправляется ошибка
    BadRequest
    . Если состояние модели допустимо, то сущность обновляется, после чего пользователь перенаправляется на метод действия
    Details()
    с применением свойства
    Id
    сущности
    Car
    в качестве параметра маршрута. Здесь также используется шаблон "отправка-перенаправление-получение".

    Если состояние модели не является допустимым, то список

    SelectList
    с записями
    Make
    добавляется в объект
    ViewData
    и сущность, которая была отправлена, посылается обратно представлению
    Edit
    . Состояние модели тоже неявно отправляется представлению, так что могут быть отображены любые ошибки.

    Представление Delete

    Создайте в каталоге

    Views\Cars
    новый файл представления по имени
    Delete.cshtml
    . Удалите весь сгенерированный код и добавьте следующую разметку:


    @model Car

    @{

      ViewData["Title"] = "Delete";

    }

    Delete @Model.PetName

    Are you sure you want to delete this car?

      @Html.DisplayForModel()

      

       

       

       

        Delete 

         |  

       

      


    В представлении

    Delete
    тоже применяется вспомогательная функция
    @Html.DisplayForModel()
    и два скрытых элемента ввода для
    Id
    и
    TimeStamp
    . Это единственные поля, которые отправляются в виде данных формы.

    Методы действий Delete()

    В рамках процесса удаления используются два метода действий: первый (

    HttpGet
    ) возвращает сущность, подлежащую удалению, а второй (
    HttpPut
    ) отправляет значения удаляемой записи.

    Метод действия Delete() для GET

    Метод действия

    Delete()
    для
    GET
    функционирует точно так же, как метод действия
    Details()
    :


    [HttpGet("{id?}")]

    public IActionResult Delete(int? id)

    {

      var car = GetOneCar(id);

      if (car == null)

      {

       return NotFound();

      }

      return View(car);

    }


    Форму удаления можно просмотреть по ссылке

    /Cars/Delete/1
    (рис. 31.9).


    Метод действия Delete() для POST

    Метод действия

    Delete()
    для
    POST
    просто отправляет значения
    Id
    и
    TimeStamp
    оболочке службы:


    [HttpPost("{id}")]

    [ValidateAntiForgeryToken]

    public IActionResult Delete(int id, Car car)

    {

      if (id != car.Id)

      {

       return BadRequest();

      }

      _repo.Delete(car);

      return RedirectToAction(nameof(Index));

    }


    Метод действия

    Delete()
    для
    POST
    оптимизирован для отправки только значений, которые необходимы инфраструктуре EF Core для удаления записи.

    На этом создание представлений и контроллера для сущности

    Car
    завершено.

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

    Компоненты представлений — еще одно новое функциональное средство, появившееся в ASP.NET Core. Они сочетают в себе преимущества частичных представлений и дочерних действий для визуализации частей пользовательского интерфейса. Как и частичные представления, компоненты представлений вызываются из другого представления,но в отличие от частичных представлений самих по себе компоненты представлений также имеют компонент серверной стороны. Благодаря такой комбинации они хорошо подходят для решения задач, подобных созданию динамических меню (как вскоре будет показано), панелей входа, содержимого боковой панели и всего того, что требует кода серверной стороны, но не может квалифицироваться как автономное представление.


    На заметку! Дочерние действия в классической инфраструктуре ASP.NET MVC были методами действий контроллера, которые не могли служить конечными точками, видимыми клиенту. В ASP.NET Core они не существуют.


    Для

    AutoLot
    компонент представления будет динамически создавать меню на основе производителей, которые присутствуют в базе данных. Меню отображается на каждой странице, поэтому вполне логичным местом для него является файл
    _Layout.cshtml
    . Но
    _Layout.cshtml
    не имеет компонента серверной стороны (в отличие от представлений), так что любое действие в приложении должно предоставлять данные компоновке
    _Layout.cshtml
    . Это можно делать в обработчике события
    OnActionExecuting()
    и в записях, помещаемых в объект
    ViewBag
    , но сопровождать подобное не будет простой задачей. Смешивание возможностей серверной стороны и инкапсуляции пользовательского интерфейса превращает такой сценарий в идеальный вариант для использования компонентов представлений.

    Код серверной стороны

    Создайте в корневом каталоге проекта

    AutoLot.Mvc
    новый каталог по имени
    ViewComponents
    и добавьте в него файл класса
    MenuViewComponent.cs
    . Подобно контроллерам классы компонентов представлений по соглашению именуются с суффиксом
    ViewComponent
    . И как у контроллеров, при обращении к компонентам представлений суффикс
    ViewComponent
    отбрасывается.

    Добавьте в начало файла следующие операторы

    using
    :


    using System.Linq;

    using AutoLot.Dal.Repos.Interfaces;

    using Microsoft.AspNetCore.Mvc;

    using Microsoft.AspNetCore.Mvc.ViewComponents;


    Сделайте класс общедоступным и унаследованным от

    ViewComponent
    . Компоненты представлений не обязательно наследовать от базового класса
    ViewComponent
    , но аналогично ситуации с базовым классом
    Controller
    наследование от
    ViewComponent
    упрощает большую часть работы. Создайте конструктор, который принимает экземпляр реализации интерфейса
    IMakeRepo
    и присваивает его переменной уровня класса. Пока что код выглядит так:


    namespace AutoLot.Mvc.ViewComponents

    {

      public class MenuViewComponent : ViewComponent

      {

       private readonly IMakeRepo _makeRepo;

       public MenuViewComponent(IMakeRepo makeRepo)

       {

        _makeRepo = makeRepo;

      }

    }


    Компонентам представлений доступны два метода,

    Invoke()
    и
    InvokeAsync()
    . Один из них должен быть реализован и поскольку
    MakeRepo
    делает только синхронные вызовы, добавьте метод
    Invoke()
    :


    public async IViewComponentResult Invoke()

    {

    }


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

    Invoke()/InvokeAsync()
    . Этот метод возвращает экземпляр реализации интерфейса
    IViewComponentResult
    , который концептуально подобен
    PartialViewResult
    , но сильно упрощен. В методе
    Invoke()
    получается список производителей из хранилища и в случае успеха возвращается экземпляр
    ViewViewComponentResult
    (в его имени нет опечатки), где в качестве модели представления применяется список производителей. Если вызов для получения записей
    Make
    завершается неудачей, тогда производится возврат экземпляра
    ContentViewComponentResult
    с сообщением об ошибке. Модифицируйте код метода, как показано ниже:


    public IViewComponentResult Invoke()

    {

      var makes = _makeRepo.GetAll().ToList();

      if (!makes.Any())

      {

       return new ContentViewComponentResult("Unable to get the makes");

      }

      return View("MenuView", makes);

    }


    Вспомогательный метод

    View()
    из базового класса
    ViewComponent
    похож на вспомогательный метод с тем же именем из класса
    Controller
    , но с парой ключевых отличий. Первое отличие заключается в том, что стандартным именем файла представления является
    Default.cshtml
    , а не имя метода. Однако подобно вспомогательному методу
    View()
    из класса
    Controller
    имя представления может быть любым, когда оно передается вызову метода (без расширения
    .cshtml
    ). Второе отличие связано с тем, что представление обязано находиться в одном из следующих трех каталогов:


    Views/< controller>/Components/<имя_компонента_представления>/

    Views/Shared/Components/<имя_компонента_представления>/

    Pages/Shared/Components/<имя_компонента_представления>/


    На заметку! В версии ASP.NET Core 2.x появился еще один механизм для создания веб-приложений, который называется Razor Pages, но в этой книге он не рассматривается.


    Класс C# может находиться где угодно (даже в другой сборке), но файл

    <имя_представления>.cshtml
    должен храниться в одном из ранее перечисленных каталогов.

    Построение частичного представления

    Частичное представление, визуализируемое классом

    MenuViewComponent
    , будет проходить по записям
    Make
    , добавляя каждую в виде элемента списка, который предназначен для отображения в меню Bootstrap. Элемент меню All (Все) добавляется первым как жестко закодированное значение.

    Создайте внутри каталога

    Views\Shared
    новый каталог по имени
    Components
    , а в нем — еще один каталог под названием
    Menu
    . Имя каталога должно совпадать с именем созданного ранее класса компонента представления минус суффикс
    ViewComponent
    . Добавьте в каталог
    Menu
    файл частичного представления по имени
    MenuView.cshtml
    .

    Удалите существующий код и поместите в файл показанную ниже разметку:


    @model IEnumerable

    action="Index">All

    @foreach (var item in Model)

    {

     

       asp-action="ByMake"

       asp-route-
    makeId="@item.Id"

       asp-route-makeName="@item.Name">@item.Name

    }

    Вызов компонентов представлений

    Компоненты представлений обычно визуализируются из представления (хотя их можно визуализировать также из метода действия контроллера). Синтаксис довольно прямолинеен:

    Component.Invoke(<имя_компонента_представления>)
    или
    @await Component.InvokeAsync(<имя_компонента_представления>)
    . Как и в случае с контроллерами, при вызове компонента представления суффикс
    ViewComponent
    не должен указываться:


    @await Component.InvokeAsync("Menu") // асинхронная версия

    @Component.Invoke("Menu")       // синхронная версия

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

    Появившиеся в ASP.NET 1.1 компоненты представлений можно вызывать с использованием синтаксиса вспомогательных функций дескрипторов. Вместо применения

    Component.InvokeAsync()/Component.Invoke()
    просто вызывайте компонент представления следующим образом:



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

    @addTagHelper
    с именем сборки, которая содержит нужный компонент представления. В файл
    _ViewImports.cshtml
    необходимо добавить показанную ниже строку, которая уже была добавлена для специальных вспомогательных функций дескрипторов:


    @addTagHelper *, AutoLot.Mvc

    Обновление меню

    Откройте частичное представление

    _Menu.cshtml
    и перейдите в место сразу после блока
  • /
  • , который соответствует методу действия
    Home/Index
    . Поместите в частичное представление следующую разметку:


  •  

       data-toggle="dropdown">Inventory 
    class="fa fa-car">

       


    Строка, выделенная полужирным, визуализирует

    MenuViewComponent
    внутри меню. Окружающая ее разметка реализует форматирование Bootstrap.

    Запустив приложение, вы увидите меню Inventory (Реестр), содержащее производителей в качестве элементов подменю (рис. 31.10).


    Пакетирование и минификация

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

    Пакетирование

    У веб-браузеров есть установленный предел на количество файлов, которые разрешено загружать параллельно из одной конечной точки. В случае использования с вашими файлами JavaScript и CSS приемов разработки SOLID, которые предусматривают разбиение связанного кода и стилей на более мелкие и управляемые файлы, могут возникать проблемы. Такой подход совершенствует процесс разработки, но становится причиной снижения производительности приложения из-за того, что файлы ожидают своей очереди на загрузку. Пакетирование — это просто объединение файлов с целью предотвращения их блокировки при достижении веб-браузером своего предела загрузки.

    Минификация

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

    Решение WebOptimizer

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

    WebOptimizer представляет собой пакет с открытым кодом, который обеспечивает пакетирование, минификацию и кеширование в качестве части конвейера ASP.NET Core. Он гарантирует, что пакетированные и минифицированные файлы соответствуют первоначальным файлам. Такие файлы не только точны, они еще и кешируются, значительно уменьшая количество операций дискового чтения для запросов страниц. Вы уже добавили пакет

    Libershark.WebOptimizer.Core
    при создании проектов в главе 29. Теперь пора им воспользоваться.

    Обновление Startup.cs

    Первый шаг предусматривает добавление WebOptimizer в конвейер. Откройте файл

    Startup.cs
    из проекта
    AutoLot.Mvc
    , отыщите в нем метод
    Configure()
    и добавьте в него следующую строку (сразу после вызова
    арр.UseStaticFiles()
    ):


    app.UseWebOptimizer();


    Следующим шагом будет конфигурирование того, что должно минифицироваться и пакетироваться. Обычно при разработке своего приложения вы хотите видеть непакетированные/неминифицированные версии файлов, но в подготовительной и производственной средах желательно применять пакетирование и минификацию. Добавьте показанный ниже блок кода в метод

    ConfigureServices()
    :


    if (_env.IsDevelopment() || _env.IsEnvironment("Local"))

    {

      services.AddWebOptimizer(false,false);

    }

    else

    {

      services.AddWebOptimizer(options =>

      {

       options.MinifyCssFiles();  // Минифицировать все файлы CSS

       //options.MinifyJsFiles(); // Минифицировать все файлы JavaScript

       options.MinifyJsFiles("js/site.js");

       options.MinifyJsFiles("lib/**/*.js");

      });

    }


    В случае среды

    Development
    пакетирование и минификация отключаются. Для остальных сред минифицируются все файлы CSS, файл
    site.js
    и все файлы JavaScript (с расширением
    .js
    ) в каталоге
    lib
    и его подкаталогах. Обратите внимание, что все пути в проекте начинаются с каталога
    wwwroot
    .

    WebOptimizer также поддерживает пакетирование. В первом примере создается пакет с использованием универсализации файловых имен, а во втором — пакет, для которого приводится список конкретных имен:


    options.AddJavaScriptBundle("js/validations/validationCode.js",

     "js/validations/**/*.js");

    options.AddJavaScriptBundle("js/validations/validationCode.js",

     "js/validations/validators.
    js", "js/validations/errorFormatting.js");


    Важно отметить, что минифицированные и пакетированные файлы на самом деле не находятся на диске, а помещаются в кеш. Также важно отметить, что минифицированные файлы сохраняют то же самое имя (site.js и не имеют обычное расширение

    .min
    (
    site.min.js
    ).


    На заметку! При обновлении своих представлений с целью добавления ссылок на пакетированные файлы среда Visual Studio сообщит о том, что они не существуют. Не переживайте, все будет визуализироваться из кеша.

    Обновление _Viewlmports.cshtml

    На финальном шаге в систему добавляются вспомогательные функции дескрипторов WebOptimizer. Они работают точно так же, как вспомогательные функции дескрипторов

    asp-append-version
    , описанные ранее в главе, но делают это автоматически для всех пакетированных и минифицированных файлов. Поместите в конец файла
    _ViewImports.cshtml
    следующую строку:


    @addTagHelper *, WebOptimizer.Core

    Шаблон параметров в ASP.NET Core

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

    IOptions
    . В табл. 31.6 кратко описан ряд версий интерфейса
    IOptions
    .


    Добавление информации об автодилере

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

    appsettings.json
    :


    {

      "Logging": {

       "MSSqlServer": {

        "schema": "Logging",

        "tableName": "SeriLogs",

        "restrictedToMinimumLevel": "Warning"

       }

      },

      "ApplicationName": "AutoLot.MVC",

      "AllowedHosts": "*",

      "DealerInfo": {

       "DealerName": "Skimedic's Used Cars",

       "City": "West Chester",

       "State": "Ohio"

      }

    }


    Далее понадобится создать модель представления для хранения информации об автодилере. Добавьте в каталог

    Models
    проекта
    AutoLot.Mvc
    новый файл класса по имени
    DealerInfo.cs
    со следующим содержимым:


    namespace AutoLot.Mvc.Models

    {

      public class DealerInfo

      {

       public string DealerName { get; set; }

       public string City { get; set; }

      public string State { get; set; }

      }

    }


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


    Метод

    Configure()
    интерфейса
    IServiceCollection
    сопоставляет раздел конфигурационных файлов с конкретным типом. Затем этот тип может быть внедрен в классы и представления с применением шаблона параметров. Откройте файл
    Startup.cs
    и добавьте в него показанный ниже оператор
    using
    :


    using AutoLot.Mvc.Models;


    Перейдите к методу

    ConfigureServices()
    и поместите в него следующую строку кода:


    services.Configure(Configuration.GetSection(nameof(DealerInfo)));


    Откройте файл

    HomeController.cs
    и добавьте в него такой оператор
    using
    :


    using Microsoft.Extensions.Options;


    Затем модифицируйте метод

    Index()
    , как продемонстрировано далее:


    [Route("/")]

    [Route("/[controller]")]

    [Route("/[controller]/[action]")]

    [HttpGet]

    public IActionResult Index([FromServices] IOptionsMonitor dealerMonitor)

    {

      var vm = dealerMonitor.CurrentValue;

      return View(vm);

    }


    Когда класс сконфигурирован в коллекции служб и добавлен в контейнер DI, его можно извлечь с использованием шаблона параметров. В рассматриваемом примере

    OptionsMonitor
    будет читать конфигурационный файл, чтобы создать экземпляр класса
    DealerInfo
    . Свойство
    CurrentValue
    получает экземпляр
    DealerInfo
    , созданный из текущего файла настроек (даже если файл изменялся после запуска приложения). Затем экземпляр
    DealerInfo
    передается представлению
    Index.cshtml
    .

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

    Index.cshtml
    , расположенное в каталоге
    Views\Home
    , чтобы оно было строго типизированным для класса
    DealerInfo
    и отображало свойства модели:


    @model AutoLot.Mvc.Models.DealerInfo

    @{

       ViewData["Title"] = "Home Page";

    }

       

    Welcome to @Model.DealerName

       

    Located in @Model.City, @Model.State


    На заметку! За дополнительными сведениями о шаблоне параметров в ASP.NET Core обращайтесь в документацию по ссылке

    https://docs.microsoft.com/ru-ru/aspnet/core/fundamentals/configuration/options
    .

    Создание оболочки службы

    Вплоть до этого момента в приложении

    AutoLot.Mvc
    применялся уровень доступа к данным напрямую. Еще один подход предусматривает использование службы
    AutoLot.Api
    , позволяя ей обрабатывать весь доступ к данным.

    Обновление конфигурации приложения

    Конечные точки приложения

    AutoLot.Api
    будут варьироваться на основе среды. Скажем, при разработке на вашей рабочей станции базовый URI выглядит как
    https://localhost:5021
    . В промежуточной среде им может быть
    https://mytestserver.com
    . Осведомленность о среде в сочетании с обновленной конфигурационной системой (представленной в главе 29) будут применяться для добавления разных значений.

    Файл

    appsettings.Development.json
    добавит информацию о службе для локальной машины По мере того как код перемещается по разным средам, настройки будут обновляться в специфическом файле среды, чтобы соответствовать базовому URI и конечным точкам для этой среды. В рассматриваемом примере вы обновляете только настройки для среды
    Development
    . Откройте файл
    appsettings.Development.json
    и модифицируйте его следующим образом (изменения выделены полужирным):


    {

      "Logging": {

       "MSSqlServer": {

        "schema": "Logging",

        "tableName": "SeriLogs",

        "restrictedToMinimumLevel": "Warning"

       }

      },

      "RebuildDataBase": false,

      "ApplicationName": "AutoLot.Mvc - Dev",

      "ConnectionStrings": {

       "AutoLot": "Server=.,5433;Database=AutoLot;User ID=sa;Password=P@ssw0rd;"

      },

      "ApiServiceSettings": {

       "Uri": "https://localhost:5021/",

       "CarBaseUri": "api/Cars",

       "MakeBaseUri": "api/Makes"

      }

    }


    На заметку! Удостоверьтесь, что номер порта соответствует вашей конфигурации для

    AutoLot.Api
    .


    За счет использования конфигурационной системы ASP.NET Core и обновления файлов, специфичных для среды (например,

    appsettings.staging.json
    и
    appsettings.production.json
    ), ваше приложение будет располагать надлежащими значениями без необходимости в изменении кода.

    Создание класса ApiServiceSettings

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

    AutoLot.Services
    новый каталог по имени
    ApiWrapper
    и добавьте в него файл класса
    ApiServiceSettings.cs
    . Имена свойств класса должны совпадать с именами свойств в разделе
    ApiServiceSettings
    файла
    appsettings.Development.json
    . Код класса показан ниже:


    namespace AutoLot.Services.ApiWrapper

    {

      public class ApiServiceSettings

      {

       public ApiServiceSettings() { }

       public string Uri { get; set; }

       public string CarBaseUri { get; set; }

       public string MakeBaseUri { get; set; }

      }

    }

    Оболочка службы API

    В версии ASP.NET Core 2.1 появился интерфейс

    IHTTPClientFactory
    , который позволяет конфигурировать строго типизированные классы для вызова внутри служб REST. Создание строго типизированного класса дает возможность инкапсулировать все обращения к API в одном месте. Это централизует взаимодействие со службой, конфигурацию клиента HTTP, обработку ошибок и т.д. Затем класс можно добавить в контейнер DI для дальнейшего применения в приложении. Контейнер DI и реализация
    IHTTPClientFactory
    обрабатывают создание и освобождение
    HTTPClient
    .

    Интерфейс IApiServiceWrapper

    Интерфейс оболочки службы

    AutoLot
    содержит методы для обращения к службе
    AutoLot.Api
    . Создайте в каталоге
    ApiWrapper
    новый файл интерфейса
    IApiServiceWrapper.cs
    и приведите операторы
    using
    к следующему виду:


    using System.Collections.Generic;

    using System.Threading.Tasks;

    using AutoLot.Models.Entities;


    Модифицируйте код интерфейса, как показано ниже:


    namespace AutoLot.Services.ApiWrapper

    {

      public interface IApiServiceWrapper

      {

       Task> GetCarsAsync();

       Task> GetCarsByMakeAsync(int id);

       Task GetCarAsync(int id);

       Task AddCarAsync(Car entity);

       Task UpdateCarAsync(int id, Car entity);

       Task DeleteCarAsync(int id, Car entity);

       Task> GetMakesAsync();

      }

    }

    Класс ApiServiceWrapper

    Создайте в каталоге

    ApiWrapper
    проекта
    AutoLot.Services
    новый файл класса по имени
    ApiServiceWrapper.cs
    и модифицируйте его операторы
    using
    следующим образом:


    using System;

    using System.Collections.Generic;

    using System.Net.Http;

    using System.Net.Http.Json;

    using System.Text;

    using System.Text.Json;

    using System.Threading.Tasks;

    using AutoLot.Models.Entities;

    using Microsoft.Extensions.Options;


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

    HttpClient
    и экземпляр реализации
    IOptionsMonitor
    . Создайте закрытую переменную типа
    ServiceSettings
    и присвойте ей значение с использованием свойства
    CurrentValue
    параметра
    IOptionsMonitor
    . Код показан ниже:


    public class ApiServiceWrapper : IApiServiceWrapper

    {

      private readonly HttpClient _client;

      private readonly ApiServiceSettings _settings;

      public ApiServiceWrapper(HttpClient client,

        IOptionsMonitor settings)

      {

        _settings = settings.CurrentValue;

       _client = client;

       _client/BaseAddress = new Uri(_settins.Uri);

      }

    }


    На заметку! В последующих разделах содержится много кода без какой-либо обработки ошибок. Поступать так настоятельно не рекомендуется! Обработка ошибок здесь опущена из-за экономии пространства.

    Внутренние поддерживающие методы

    Класс содержит четыре поддерживающих метода, которые применяются открытыми методами.

    Вспомогательные методы для POST и PUT

    Следующие методы являются оболочками для связанных методов

    HttpClient
    :


    internal async Task PostAsJson(string uri, string json)

    {

      return await _client.PostAsync(uri, new StringContent(json, Encoding.UTF8,

                     "application/json"));

    }


    internal async Task PutAsJson(string uri, string json)

    {

      return await _client.PutAsync(uri, new StringContent(json, Encoding.UTF8,

                     "application/json"));

    }

    Вспомогательный метод для DELETE

    Последний вспомогательный метод используется для выполнения НТТР-метода

    DELETE
    . Спецификация HTTP 1.1 (и более поздние версии) позволяет передавать тело в HTTP-методе
    DELETE
    , но для этого пока еще не предусмотрено расширяющего метода
    HttpClient
    . Экземпляр
    HttpRequestMessage
    потребуется создавать с нуля.

    Первым делом необходимо создать сообщение запроса с применением инициализации объектов для установки

    Content
    ,
    Method
    и
    RequestUri
    . Затем сообщение отправляется, после чего ответ возвращается вызывающему коду. Вот код метода:


    internal async Task DeleteAsJson(string uri, string json)

    {

      HttpRequestMessage request = new HttpRequestMessage

      {

       Content = new StringContent(json, Encoding.UTF8, "application/json"),

       Method = HttpMethod.Delete,

       RequestUri = new Uri(uri)

      };

      return await _client.SendAsync(request);

    }

    Вызовы HTTP-метода GET

    Есть четыре вызова НТТР-метода

    GET
    : один для получения всех записей
    Car
    , один для получения записей
    Car
    по производителю
    Make
    , один для получения одиночной записи
    Car
    и один для получения всех записей
    Make
    . Все они следуют тому же самому шаблону. Метод
    GetAsync()
    вызывается для возвращения экземпляра
    HttpResponseMessage
    . Успешность или неудача вызова проверяется с помощью метода
    EnsureSuccessStatusCode()
    , который генерирует исключение, если вызов не возвратил код состояния успеха. Затем тело ответа сериализируется в тип свойства (сущность или список сущностей) и возвращается вызывающему коду. Ниже приведен код всех методов:


    public async Task> GetCarsAsync()

    {

      var response = await _client.GetAsync($"{_settings.Uri}{_settings.CarBaseUri}");

      response.EnsureSuccessStatusCode();

      var result = await response.Content.ReadFromJsonAsync>();

      return result;

    }


    public async Task> GetCarsByMakeAsync(int id)

    {

     var response = await

      _client.GetAsync($"{_settings.Uri}{_settings.CarBaseUri}/bymake/
    {id}");

      response.EnsureSuccessStatusCode();

      var result = await response.Content.ReadFromJsonAsync>();

      return result;

    }


    public async Task GetCarAsync(int id)

    {

      var response = await

     _client.GetAsync($"{_settings.Uri}{_settings.CarBaseUri}/{id}");

      response.EnsureSuccessStatusCode();

      var result = await response.Content.ReadFromJsonAsync();

      return result;

    }


    public async Task> GetMakesAsync()

    {

      var response = await

     _client.GetAsync($"{_settings.Uri}{_settings.MakeBaseUri}");

      response.EnsureSuccessStatusCode();

      var result = await response.Content.ReadFromJsonAsync>();

      return result;

    }

    Вызов HTTP-метода POST

    Метод для добавления записи

    Car
    использует HTTP-метод
    POST
    . Он применяет вспомогательный метод для отправки сущности в формате JSON и возвращает запись
    Car
    из тела ответа. Вот его код:


    public async Task AddCarAsync(Car entity)

    {

      var response = await PostAsJson($"{_settings.Uri}{_settings.CarBaseUri}",

       JsonSerializer.Serialize(entity));

      if (response == null)

      {

       throw new Exception("Unable to communicate with the service");

      }

      return await response.Content.ReadFromJsonAsync();

    }

    Вызов HTTP-метода PUT

    Метод для обновления записи

    Car
    использует HTTP-метод
    PUT
    . Он применяет вспомогательный метод для отправки записи
    Car
    в формате JSON и возвращает обновленную запись
    Car
    из тела ответа:


    public async Task UpdateCarAsync(int id, Car entity)

    {

      var response = await PutAsJson($"{_settings.Uri}{_settings.CarBaseUri}/{id}",

       JsonSerializer.Serialize(entity));

      response.EnsureSuccessStatusCode();

      return await response.Content.ReadFromJsonAsync();

    }

    Вызов HTTP-метода DELETE

    Последний добавляемый метод предназначен для выполнения НТТР-метода

    DELETE
    . Шаблон соответствует остальным методам: использование вспомогательного метода и проверка ответа на предмет успешности. Он ничего не возвращает вызывающему коду, поскольку сущность была удалена. Ниже показан код метода:


    public async Task DeleteCarAsync(int id, Car entity)

    {

      var response = await DeleteAsJson($"{_settings.Uri}{_settings.CarBaseUri}/{id}",

       JsonSerializer.Serialize(entity));

      response.EnsureSuccessStatusCode();

    }

    Конфигурирование служб

    Создайте в каталоге

    ApiWrapper
    проекта
    AutoLot.Service
    новый файл класса по имени
    ServiceConfiguration.cs
    . Приведите операторы
    using
    к следующему виду:


    using Microsoft.Extensions.Configuration;

    using Microsoft.Extensions.DependencyInjection;


    Сделайте класс открытым и статическим, после чего добавьте открытый статический расширяющий метод для

    IServiceCollection
    :


    namespace AutoLot.Services.ApiWrapper

    {

      public static class ServiceConfiguration

      {

       public static IServiceCollection ConfigureApiServiceWrapper(

          this IServiceCollection 
    services, IConfiguration config)

       {

        return services;

       }

      }

    }


    В первой строке расширяющего метода в контейнер DI добавляется

    ApiServiceSettings
    . Во второй строке в контейнер DI добавляется
    IApiServiceWrapper
    и регистрируется класс с помощью фабрики
    HTTPClient
    . Это позволяет внедрять
    IApiServiceWrapper
    в другие классы, а фабрика
    HTTPClient
    будет управлять внедрением и временем существования
    HTTPClient
    :


    public static IServiceCollection ConfigureApiServiceWrapper(this IServiceCollection 

    services, IConfiguration config)

    {

      services.Configure(

        config.GetSection(nameof(ApiServiceSettings)));

      services.AddHttpClient();

      return services;

    }


    Откройте файл

    Startup.cs
    и добавьте следующий оператор
    using
    :


    using AutoLot.Services.ApiWrapper;


    Перейдите к методу

    ConfigureServices()
    и добавьте в него показанную ниже строку:


    services.ConfigureApiServiceWrapper(Configuration);

    Построение класса CarsController

    Текущая версия

    CarsController
    жестко привязана к хранилищам в библиотеке доступа к данным. Следующая итерация
    CarsController
    для связи с базой данных будет применять оболочку службы. Переименуйте
    CarsController
    в
    CarsDalController
    (включая конструктор) и добавьте в каталог
    Controllers
    новый класс по имени
    CarsController
    . Код этого класса является практически точной копией
    CarsController
    , но они хранятся по отдельности с целью прояснения разницы между использованием хранилищ и службы.


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


    Приведите операторы

    using
    к следующему виду:


    using System.Threading.Tasks;

    using AutoLot.Dal.Repos.Interfaces;

    using AutoLot.Models.Entities;

    using AutoLot.Services.ApiWrapper;

    using AutoLot.Services.Logging;

    using Microsoft.AspNetCore.Mvc;

    using Microsoft.AspNetCore.Mvc.Rendering;


    Далее сделайте класс открытым, унаследуйте его от

    Controller
    и добавьте атрибут
    Route
    . Создайте конструктор, который принимает экземпляры реализаций
    IAutoLotServiceWrapper
    и
    IAppLogging
    , после чего присвойте оба экземпляра переменным уровня класса. Вот начальный код:


    namespace AutoLot.Mvc.Controllers

    {

    [Route("[controller]/[action]")]

    public class CarsController : Controller

    {

      private readonly IApiServiceWrapper _serviceWrapper;

      private readonly IAppLogging _logging;

      public CarsController(IApiServiceWrapper serviceWrapper,

        IAppLogging 
    logging)

      {

       _serviceWrapper = serviceWrapper;

       _logging = logging;

      }

    }

    Вспомогательный метод GetMakes()

    Вспомогательный метод

    GetMakes()
    строит экземпляр
    SelectList
    со всеми записями
    Make
    в базе данных. Он использует
    Id
    в качестве значения и
    Name
    в качестве отображаемого текста:


    internal async Task GetMakesAsync()=>

      new SelectList(

       await _serviceWrapper.GetMakesAsync(),

       nameof(Make.Id),

       nameof(Make.Name));

    Вспомогательный метод GetOneCar()

    Вспомогательный метод

    GetOneCar()
    получает одиночную запись
    Car
    :


    internal async Task GetOneCarAsync(int? id)

      => !id.HasValue ? null : await _serviceWrapper.GetCarAsync(id.Value);

    Открытые методы действий

    Единственное отличие между открытыми методами действий в этом контроллере и аналогичными методами в

    CarsDalController
    связано с доступом к данным, а также с тем,что все методы определены как асинхронные. Поскольку вы уже понимаете, для чего предназначено то или иное действие, ниже приведены остальные методы, изменения в которых выделены полужирным:


    [Route("/[controller]")]

    [Route("/[controller]/[action]")]

    public async Task Index()

      => View(await _serviceWrapper.GetCarsAsync());


    [HttpGet("{makeId}/{makeName}")]

    public async Task ByMake(int makeId, string makeName)

    {

      ViewBag.MakeName = makeName;

      return View(await _serviceWrapper.GetCarsByMakeAsync(makeId));

    }


    [HttpGet("{id?}")]

    public async Task Details(int? id)

    {

      if (!id.HasValue)

      {

       return BadRequest();

      }

      var car = await GetOneCarAsync(id);

      if (car == null)

      {

       return NotFound();

      }

      return View(car);

    }


    [HttpGet]

    public async Task Create()

    {

      ViewData["MakeId"] = await GetMakesAsync();

      return View();

    }


    [HttpPost]

    [ValidateAntiForgeryToken]

    public async Task Create(Car car)

    {

      if (ModelState.IsValid)

      {

       await _serviceWrapper.AddCarAsync(car);

       return RedirectToAction(nameof(Index));

      }

      ViewData["MakeId"] = await GetMakesAsync();

      return View(car);

    }


    [HttpGet("{id?}")]

    public async Task Edit(int? id)

    {

      var car = await GetOneCarAsync(id);

      if (car == null)

      {

       return NotFound();

      }

      ViewData["MakeId"] = await GetMakesAsync();

      return View(car);

    }


    [HttpPost("{id}")]

    [ValidateAntiForgeryToken]

    public async Task Edit(int id, Car car)

    {

      if (id != car.Id)

      {

       return BadRequest();

      }

      if (ModelState.IsValid)

      {

       await _serviceWrapper.UpdateCarAsync(id,car);

       return RedirectToAction(nameof(Index));

      }

      ViewData["MakeId"] = await GetMakesAsync();

      return View(car);

    }


    [HttpGet("{id?}")]

    public async Task Delete(int? id)

    {

      var car = await GetOneCarAsync(id);

      if (car == null)

      {

       return NotFound();

      }

      return View(car);

    }


    [HttpPost("{id}")]

    [ValidateAntiForgeryToken]

    public async Task Delete(int id, Car car)

    {

      await _serviceWrapper.DeleteCarAsync(id,car);

      return RedirectToAction(nameof(Index));

    }

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

    В текущий момент внутри компонента представления

    MenuViewComponent
    применяется уровень доступа к данным и синхронная версия
    Invoke()
    . Внесите в класс следующие изменения:


    using System.Linq;

    using System.Threading.Tasks;

    using AutoLot.Dal.Repos.Interfaces;

    using AutoLot.Services.ApiWrapper;

    using Microsoft.AspNetCore.Mvc;

    using Microsoft.AspNetCore.Mvc.ViewComponents;


    namespace AutoLot.Mvc.ViewComponents

    {

      public class MenuViewComponent : ViewComponent

      {

       private readonly IApiServiceWrapper _serviceWrapper;

       public MenuViewComponent(IApiServiceWrapper serviceWrapper)

       {

       _serviceWrapper = serviceWrapper;

       }


       public async Task InvokeAsync()

       {

        var makes = await _serviceWrapper.GetMakesAsync();

        if (makes == null)

        {

        return new ContentViewComponentResult("Unable to get the makes");

        }

        return View("MenuView", makes);

       }

      }

    }

    Совместный запуск приложений AutoLot.Mvc и AutoLot.Api

    Приложение

    AutoLot.Mvc
    рассчитывает на то, что приложение
    AutoLot.Api
    должно быть запущено. Это можно сделать с помощью Visual Studio, командной строки или через комбинацию того и другого.


    На заметку! Вспомните, что приложения

    AutoLot.Mvc
    и
    AutoLot.Api
    сконфигурированы на воссоздание базы данных при каждом их запуске. Обязательно отключите воссоздание хотя бы в одном из приложений, иначе возникнет конфликт. Чтобы ускорить отладку, отключите воссоздание в обоих приложений при тестировании функциональности, которая не изменяет данные.

    Использование Visual Studio

    Вы можете сконфигурировать среду Visual Studio на запуск нескольких проектов одновременно. Щелкните правой кнопкой мыши на имени решения в окне Solution Explorer, выберите в контекстном меню пункт Select Startup Projects (Выбрать стартовые проекты) и установите действия для проектов

    AutoLot.Api
    и
    AutoLot.Mvc
    в Start (Запуск), как показано на рис. 31.11.



    После нажатия клавиши <F5> (или щелчка на кнопке запуска с зеленой стрелкой) оба проекта запустятся. При этом возникает ряд сложностей. Первая сложность — среда Visual Studio запоминает последний профиль, который применялся для запуска приложения. Это значит, что если вы использовали для запуска

    AutoLot.Api
    веб-сервер IIS Express, то запуск обоих приложений приведет к запуску
    AutoLot.Api
    с применением IIS Express, поэтому порт в настройках служб окажется некорректным.

    Проблему легко устранить. Либо измените порты в файле

    appsettings.development.json
    , либо запустите приложение под управлением Kestrel, прежде чем конфигурировать совместный запуск приложений.

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

    AutoLot.Api
    на воссоздание базы данных при каждом его запуске, тогда она не будет готова для приложения
    AutoLot.Mvc
    , когда компонент представления запускается с целью построения меню. Проблему решит быстрое обновление браузера, отображающего
    AutoLot.Mvc
    (как только вы увидите пользовательский интерфейс Swagger в
    AutoLot.Api
    ).

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

    Откройте окно командной строки в каждом каталоге проекта и введите команду

    dotnet watch run
    . Это позволит управлять порядком и синхронизацией, а также гарантирует, что приложения выполняются с применением Kestrel, но не IIS. Информацию об отладке при запуске из командной строки ищите в главе 29.

    Резюме

    В настоящей главе вы завершили изучение ASP.NET Core, равно как и построение приложения

    AutoLot.Mvc
    . Процесс изучения начинался с исследования представлений, частичных представлений, а также шаблонов редактирования и отображения. Затем вы узнали о вспомогательных функциях дескрипторов, смешивающих разметку клиентской стороны с кодом серверной стороны.

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

    Затем с использованием

    HTTPClientFactory
    и конфигурационной системы ASP.NET Core была создана оболочка службы, взаимодействующая с
    AutoLot.Api
    , которая применялась для создания компонента представления, отвечающего за построение динамической системы меню. После краткого обсуждения способов одновременной загрузки обоих приложений (
    AutoLot.Api
    и
    AutoLot.Mvc
    ) была разработана основная часть приложения.

    Разработка начиналась с создания контроллера

    CarsController
    и всех методов действий. Далее были добавлены специальные вспомогательные функции дескрипторов и в заключение созданы все представления, касающиеся записей
    Car
    . Конечно, был построен только один контроллер и его представления, но с помощью продемонстрированного шаблона можно создать контроллеры и представления для всех сущностей
    AutoLot
    .

    < Назад Далее >
    ТЕЛЕГРАМ

    Канал с обзорами, анонсами новинок и книжными подборками

    Книжный Вестник

    Бот для удобного поиска книг (если не нашлось на сайте)

    Поиск книг

    Свежие любовные романы в удобных форматах

    Любовные романы

    Детективы и триллеры, все новинки

    Детективы

    Фантастика и фэнтези, все новинки

    Фантастика

    Отборные классические книги

    Классика
    ВКОНТАКТЕ

    Цитаты, афоризмы, стихи, книжные подборки, обсуждения и многое другое

    Книжный Вестник
    БИБЛИОТЕКИ

    Библиотека с любовными романами, которая наверняка придётся по вкусу женской части аудитории

    Любовные романы

    Библиотека с фантастикой и фэнтези, а также смежных жанров

    Фантастика

    Самые популярные книги в формате фб2

    Топ фб2 книги

    Оглавление

    Пожаловаться
    • К описанию
    • Оглавление
    • Об авторах
    • О технических рецензентах
    • Благодарности
    • Введение
    • Авторы и читатели — одна команда
    • Краткий обзор книги
    • Часть I. Язык программирования C# и платформа .NET 5
    • Часть II. Основы программирования на C#
    • Часть III. Объектно-ориентированное программирование на C#
    • Часть IV. Дополнительные конструкции программирования на C#
    • Часть V. Программирование с использованием сборок .NET Core
    • Часть VI. Работа с файлами, сериализация объектов и доступ к данным
    • Часть VII. Entity Framework Core
    • Часть IV. Дополнительные конструкции программирования на C#
    • Часть IX. ASP.NET Core
    • Ждем ваших отзывов!
    • Часть I Язык программирования C# и платформа .NET 5
    • Глава 1 Введение в C# и .NET (Core) 5
    • Некоторые основные преимущества инфраструктуры .NET Core
    • Понятие жизненного цикла поддержки .NET Core
    • Предварительный обзор строительных блоков .NET Core (.NET Runtime, CTS и CLS)
    • Роль библиотек базовых классов
    • Роль .NET Standard
    • Что привносит язык C#
    • Основные средства в предшествующих выпусках
    • Новые средства в C# 9
    • Сравнение управляемого и неуправляемого кода
    • Использование дополнительных языков программирования, ориентированных на .NET Core
    • Обзор сборок .NET
    • Роль языка CIL
    • Преимущества языка CIL
    • Компиляция кода CIL в инструкции, специфичные для платформы
    • Предварительная компиляция кода CIL в инструкции, специфичные для платформы
    • Роль метаданных типов .NET Core
    • Роль манифеста сборки
    • Понятие общей системы типов
    • Типы классов CTS
    • Типы интерфейсов CTS
    • Типы структур CTS
    • Типы перечислений CTS
    • Типы делегатов CTS
    • Члены типов CTS
    • Встроенные типы данных CTS
    • Понятие общеязыковой спецификации
    • Обеспечение совместимости с CLS
    • Понятие .NET Core Runtime
    • Различия между сборкой пространством имен и типом
    • Доступ к пространству имен программным образом
    • Ссылка на внешние сборки
    • Исследование сборки с помощью ildasm.exe
    • Резюме
    • Глава 2 Создание приложений на языке C#
    • Установка .NET 5
    • Понятие схемы нумерации версий .NET 5
    • Подтверждение успешности установки .NET 5
    • Использование более ранних версий .NET (Core) SDK
    • Построение приложений .NET Core с помощью Visual Studio
    • Установка Visual Studio 2019 (Windows)
    • Испытание Visual Studio 2019
    • Использование нового диалогового окна для создания проекта и редактора кода C#
    • Изменение целевой инфраструктуры .NET Core
    • Использование функциональных средств C# 9
    • Запуск и отладка проекта
    • Использование окна Solution Explorer
    • Использование визуального конструктора классов
    • Построение приложений .NET Core с помощью Visual Studio Code
    • Испытание Visual Studio Code
    • Создание решений и проектов
    • Исследование рабочей области Visual Studio Code
    • Восстановление пакетов, компиляция и запуск программ
    • Отладка проекта
    • Документация по .NET Core и C#
    • Резюме
    • Часть II Основы программирования на C#
    • Глава 3 Главные конструкции программирования на С#: часть 10
    • Структура простой программы C#
    • Использование вариаций метода Main() (обновление в версии 7.1)
    • Использование операторов верхнего уровня (нововведение в версии 9.0)
    • Указание кода ошибки приложения (обновление в версии 9.0)
    • Обработка аргументов командной строки
    • Указание аргументов командной строки в Visual Studio
    • Интересное отступление от темы: некоторые дополнительные члены класса System.Environment
    • Использование класса System.Console
    • Выполнение базового ввода и вывода с помощью класса Console
    • Форматирование консольного вывода
    • Форматирование числовых данных
    • Форматирование числовых данных за рамками консольных приложений
    • Работа с системными типами данных и соответствующими ключевыми словами C#
    • Объявление и инициализация переменных
    • Литерал default (нововведение в версии 7.1)
    • Использование внутренних типов данных и операции new (обновление в версии 9.0)
    • Иерархия классов для типов данных
    • Члены числовых типов данных
    • Члены System.Boolean
    • Члены System.Char
    • Разбор значений из строковых данных
    • Использование метода TryParse() для разбора значений из строковых данных
    • Использование типов System.DateTime и System.TimeSpan
    • Работа с пространством имен System.Numerics
    • Использование разделителей групп цифр (нововведение в версии 7.0)
    • Использование двоичных литералов (нововведение в версии 7.0/7.2)
    • Работа со строковыми данными
    • Выполнение базовых манипуляций со строками
    • Выполнение конкатенации строк
    • Использование управляющих последовательностей
    • Выполнение интерполяции строк
    • Определение дословных строк (обновление в версии 8.0)
    • Работа со строками и операциями равенства
    • Модификация поведения сравнения строк
    • Строки неизменяемы
    • Использование типа System.Text.StringBuilder
    • Сужающие и расширяющие преобразования типов данных
    • Использование ключевого слова checked
    • Настройка проверки переполнения на уровне проекта
    • Настройка проверки переполнения на уровне проекта (Visual Studio)
    • Использование ключевого слова unchecked
    • Неявно типизированные локальные переменные
    • Неявное объявление чисел
    • Ограничения неявно типизированных переменных
    • Неявно типизированные данные строго типизированы
    • Полезность неявно типизированных локальных переменных
    • Работа с итерационными конструкциями C#
    • Использование цикла for
    • Использование цикла foreach
    • Использование неявной типизации в конструкциях foreach
    • Использование циклов while и do/while
    • Краткое обсуждение области видимости
    • Работа с конструкциями принятия решений и операциями отношения/равенства
    • Использование оператора if/else
    • Использование операций отношения и равенства
    • Использование операторов if/else и сопоставления с образцом (нововведение в версии 7.0)
    • Внесение улучшений в сопоставление с образцом (нововведение в версии 9.0)
    • Использование условной операции (обновление в версиях 7.2, 9.0)
    • Использование логических операций
    • Использование оператора switch
    • Выполнение сопоставления с образцом в операторах switch (нововведение в версии 7.0, обновление в версии 9.0)
    • Использование выражений switch (нововведение в версии 8.0)
    • Резюме
    • Глава 4 Главные конструкции программирования на С#: часть 2
    • Понятие массивов C#
    • Синтаксис инициализации массивов C#
    • Понятие неявно типизированных локальных массивов
    • Определение массива объектов
    • Работа с многомерными массивами
    • Использование массивов в качестве аргументов и возвращаемых значений
    • Использование базового класса System.Array
    • Использование индексов и диапазонов (нововведение в версии 8.0)
    • Понятие методов
    • Члены, сжатые до выражений
    • Локальные функции (нововведение в версии 7.0, обновление в версии 9.0)
    • Статические локальные функции (нововведение в версии 8.0)
    • Понятие параметров методов
    • Модификаторы параметров для методов
    • Стандартное поведение передачи параметров
    • Стандартное поведение для типов значений
    • Стандартное поведение для ссылочных типов
    • Использование модификатора out (обновление в версии 7.0)
    • Отбрасывание параметров out (нововведение в версии 7.0)
    • Модификатор out в конструкторах и инициализаторах (нововведение в версии 7.3)
    • Использование модификатора ref
    • Использование модификатора in (нововведение в версии 7.2)
    • Использование модификатора params
    • Определение необязательных параметров
    • Использование именованных параметров (обновление в версии 7.2)
    • Понятие перегрузки методов
    • Понятие типа enum
    • Управление хранилищем, лежащим в основе перечисления
    • Объявление переменных типа перечисления
    • Использование типа System.Enum
    • Динамическое обнаружение пар "имя-значение" перечисления
    • Использование перечислений, флагов и побитовых операций
    • Понятие структуры (как типа значения)
    • Создание переменных типа структур
    • Использование структур, допускающих только чтение (нововведение в версии 7.2)
    • Использование членов, допускающих только чтение (нововведение в версии 8.0)
    • Использование структур ref (нововведение в версии 7.2)
    • Использование освобождаемых структур ref (нововведение в версии 8.0)
    • Типы значений и ссылочные типы
    • Использование типов значений ссылочных типов и операции присваивания
    • Использование типов значений, содержащих ссылочные типы
    • Передача ссылочных типов по значению
    • Передача ссылочных типов по ссылке
    • Заключительные детали относительно типов значений и ссылочных типов
    • Понятие типов С#, допускающих null
    • Использование типов значений, допускающих null
    • Использование ссылочных типов, допускающих null (нововведение в версии 8.0)
    • Включение ссылочных типов, допускающих null
    • Ссылочные типы, допускающие null, в действии
    • Рекомендации по переносу кода
    • Работа с типами, допускающими значение null
    • Операция объединения с null
    • Операция присваивания с объединением с null (нововведение в версии 8.0)
    • null- условная операция
    • Понятие кортежей (нововведение и обновление в версии 7.0)
    • Начало работы с кортежами
    • Использование выведенных имен переменных (обновление в версии C# 7.1)
    • Понятие эквивалентности/неэквивалентности кортежей (нововведение в версии 7.3)
    • Использование отбрасывания с кортежами
    • Использование отбрасывания с кортежами
    • Использование выражений switch с сопоставлением с образцом для кортежей (нововведение в версии 8.0)
    • Деконструирование кортежей
    • Деконструирование кортежей с позиционным сопоставлением с образцом (нововведение в версии 8.0)
    • Резюме
    • Часть III Объектно-ориентированное программирование на C#
    • Глава 5 Инкапсуляция
    • Знакомство с типом класса C#
    • Размещение объектов с помощью ключевого слова new
    • Понятие конструкторов
    • Роль стандартного конструктора
    • Определение специальных конструкторов
    • Конструкторы в виде членов, сжатых до выражений (нововведение в версии 7.0)
    • Конструкторы с параметрами out (нововведение в версии 7.3)
    • Еще раз о стандартном конструкторе
    • Роль ключевого слова this
    • Построение цепочки вызовов конструкторов с использованием this
    • Исследование потока управления конструкторов
    • Еще раз о необязательных аргументах
    • Понятие ключевого слова static
    • Определение статических полей данных
    • Определение статических методов
    • Определение статических конструкторов
    • Определение статических классов
    • Импортирование статических членов с применением ключевого слова using языка C#
    • Основные принципы объектно-ориентированного программирования
    • Роль инкапсуляции
    • Роль наследования
    • Роль полиморфизма
    • Модификаторы доступа C# (обновление в версии 7.2)
    • Использование стандартных модификаторов доступа
    • Использование модификаторов доступа и вложенных типов
    • Первый принцип объектно-ориентированного программирования: службы инкапсуляции C#
    • Инкапсуляция с использованием традиционных методов доступа и изменения
    • Инкапсуляция с использованием свойств
    • Свойства как члены, сжатые до выражений (нововведение в версии 7.0)
    • Использование свойств внутри определения класса
    • Свойства, допускающие только чтение
    • Свойства, допускающие только запись
    • Смешивание закрытых и открытых методов get/set в свойствах
    • Еще раз о ключевом слове static: определение статических свойств
    • Сопоставление с образцом и шаблоны свойств (нововведение в версии 8.0)
    • Понятие автоматических свойств
    • Взаимодействие с автоматическими свойствами
    • Автоматические свойства и стандартные значения
    • Инициализация автоматических свойств
    • Понятие инициализации объектов
    • Обзор синтаксиса инициализации объектов
    • Использование средства доступа только для инициализации (нововведение в версии 9.0)
    • Вызов специальных конструкторов с помощью синтаксиса инициализации
    • Инициализация данных с помощью синтаксиса инициализации
    • Работа с константными полями данных и полями данных, допускающими только чтение
    • Понятие константных полей данных
    • Понятие полей данных, допускающих только чтение
    • Понятие статических полей, допускающих только чтение
    • Понятие частичных классов
    • Использование записей (нововведение в версии 9.0)
    • Эквивалентность с типами записей
    • Копирование типов записей с использованием выражений with
    • Резюме
    • Глава 6 Наследование и полиморфизм
    • Базовый механизм наследования
    • Указание родительского класса для существующего класса
    • Замечание относительно множества базовых классов
    • Использование ключевого слова sealed
    • Еще раз о диаграммах классов Visual Studio
    • Второй принцип объектно-ориентированного программирования: детали наследования
    • Вызов конструкторов базового класса с помощью ключевого слова base
    • Хранение секретов семейства: ключевое слово protected
    • Добавление запечатанного класса
    • Наследование с типами записей (нововведение в версии 9.0)
    • Эквивалентность с унаследованными типами записей
    • Реализация модели включения/делегации
    • Определения вложенных типов
    • Третий принцип объектно-ориентированного программирования: поддержка полиморфизма в C#
    • Использование ключевых слов virtual и override
    • Переопределение виртуальных членов с помощью Visual Studio/Visual Studio Code
    • Запечатывание виртуальных членов
    • Абстрактные классы
    • Полиморфные интерфейсы
    • Сокрытие членов
    • Правила приведения для базовых и производных классов
    • Использование ключевого слова as
    • Использование ключевого слова is (обновление в версиях 7.0, 9.0)
    • Использование отбрасывания вместе с ключевым словом is (нововведение в версии 7.0)
    • Еще раз о сопоставлении с образцом (нововведение в версии 7.0)
    • Использование отбрасывания вместе с операторами switch (нововведение в версии 7.0)
    • Главный родительский класс: System.Object
    • Переопределение метода System.Object.ToString()
    • Переопределение метода System.Object.Equals()
    • Переопределение метода System.Object.GetHashCode()
    • Тестирование модифицированного класса Person
    • Использование статических членов класса System.Object
    • Резюме
    • Глава 7 Структурированная обработка исключений
    • Ода ошибкам, дефектам и исключениям
    • Роль обработки исключений .NET
    • Строительные блоки обработки исключений в .NET
    • Базовый класс System.Exception
    • Простейший пример
    • Генерация общего исключения
    • Перехват исключений
    • Выражение throw (нововведение в версии 7.0)
    • Конфигурирование состояния исключения
    • Свойство TargetSite
    • Свойство StackTrace
    • Свойство HelpLink
    • Свойство Data
    • Исключения уровня системы (System.SystemException)
    • Исключения уровня приложения (Systern.ApplicationException)
    • Построение специальных исключений, способ первый
    • Построение специальных исключений, способ второй
    • Построение специальных исключений, способ третий
    • Обработка множества исключений
    • Общие операторы catch
    • Повторная генерация исключений
    • Внутренние исключения
    • Блок finally
    • Фильтры исключений
    • Отладка необработанных исключений с использованием Visual Studio
    • Резюме
    • Глава 8 Работа с интерфейсами
    • Понятие интерфейсных типов
    • Сравнение интерфейсных типов и абстрактных базовых классов
    • Определение специальных интерфейсов
    • Реализация интерфейса
    • Обращение к членам интерфейса на уровне объектов
    • Получение ссылок на интерфейсы: ключевое слово as
    • Получение ссылок на интерфейсы: ключевое слово is (обновление в версии 7.0)
    • Стандартные реализации (нововведение в версии 8.0)
    • Статические конструкторы и члены (нововведение в версии 8.0)
    • Использование интерфейсов в качестве параметров
    • Использование интерфейсов в качестве возвращаемых значений
    • Массивы интерфейсных типов
    • Автоматическая реализация интерфейсов
    • Явная реализация интерфейсов
    • Проектирование иерархий интерфейсов
    • Иерархии интерфейсов со стандартными реализациями (нововведение в версии 8.0)
    • Множественное наследование с помощью интерфейсных типов
    • Интерфейсы IEnumerable и IEnumerator
    • Построение итераторных методов с использованием ключевого слова yield
    • Защитные конструкции с использованием локальных функций (нововведение в версии 7.0)
    • Построение именованного итератора
    • Интерфейс ICloneable
    • Более сложный пример клонирования
    • Интерфейс IComparable
    • Указание множества порядков сортировки с помощью IComparer
    • Специальные свойства и специальные типы сортировки
    • Резюме
    • Глава 9 Время существования объектов
    • Классы, объекты и ссылки
    • Базовые сведения о времени жизни объектов
    • Код CIL для ключевого слова new
    • Установка объектных ссылок в null
    • Выяснение, нужен ли объект
    • Понятие поколений объектов
    • Эфемерные поколения и сегменты
    • Типы сборки мусора
    • Фоновая сборка мусора
    • Тип System.GC
    • Принудительный запуск сборщика мусора
    • Построение финализируемых объектов
    • Переопределение метода System.Object.Finalize()
    • Подробности процесса финализации
    • Построение освобождаемых объектов
    • Повторное использование ключевого слова using в C#
    • Объявления using (нововведение в версии 8.0)
    • Создание финализируемых и освобождаемых типов
    • Формализованный шаблон освобождения
    • Ленивое создание объектов
    • Настройка процесса создания данных Lazy < >
    • Резюме
    • Часть IV Дополнительные конструкции программирования на C#
    • Глава 10 Коллекции и обобщения
    • Побудительные причины создания классов коллекций
    • Пространство имен System.Collections
    • Иллюстративный пример: работа с ArrayList
    • Обзор пространства имен System.Collections.Specialized
    • Проблемы, присущие необобщенным коллекциям
    • Проблема производительности
    • Проблема безопасности в отношении типов
    • Первый взгляд на обобщенные коллекции
    • Роль параметров обобщенных типов
    • Указание параметров типа для обобщенных классов и структур
    • Указание параметров типа для обобщенных членов
    • Указание параметров типов для обобщенных интерфейсов
    • Пространство имен System.Collections.Generic
    • Синтаксис инициализации коллекций
    • Работа с классом List < T >
    • Работа с классом Stack < T >
    • Работа с классом Queue < T >
    • Работа с классом SortedSet < T >
    • Работа с классом Dictionary < TKey,TValue >
    • Пространство имен System.Collections.ObjectModel
    • Работа с классом ObservableCollection < T >
    • Создание специальных обобщенных методов
    • Выведение параметров типа
    • Создание специальных обобщенных структур и классов
    • Выражения default вида значений в обобщениях
    • Выражения default литерального вида (нововведение в версии 7.1)
    • Сопоставление с образцом в обобщениях (нововведение в версии 7.1)
    • Ограничение параметров типа
    • Примеры использования ключевого слова where
    • Отсутствие ограничений операций
    • Резюме
    • Глава 11 Расширенные средства языка C#
    • Понятие индексаторных методов
    • Индексация данных с использованием строковых значений
    • Перегрузка индексаторных методов
    • Многомерные индексаторы
    • Определения индексаторов в интерфейсных типах
    • Понятие перегрузки операций
    • Перегрузка бинарных операций
    • А как насчет операций += и -=?
    • Перегрузка унарных операций
    • Перегрузка операций эквивалентности
    • Перегрузка операций сравнения
    • Финальные соображения относительно перегрузки операций
    • Понятие специальных преобразований типов
    • Повторение: числовые преобразования
    • Повторение: преобразования между связанными типами классов
    • Создание специальных процедур преобразования
    • Дополнительные явные преобразования для типа Square
    • Определение процедур неявного преобразования
    • Понятие расширяющих методов
    • Определение расширяющих методов
    • Вызов расширяющих методов
    • Импортирование расширяющих методов
    • Расширение типов, реализующих специфичные интерфейсы
    • Поддержка расширяющего метода GetEnumerator() (нововведение в версии 9.0)
    • Понятие анонимных типов
    • Определение анонимного типа
    • Внутреннее представление анонимных типов
    • Реализация методов ToString() и GetHashCode()
    • Семантика эквивалентности анонимных типов
    • Анонимные типы, содержащие другие анонимные типы
    • Работа с типами указателей
    • Ключевое слово unsafe
    • Работа с операциями * и &
    • Небезопасная (и безопасная) функция обмена
    • Доступ к полям через указатели (операция - > )
    • Ключевое слово stackalloc
    • Закрепление типа посредством ключевого слова fixed
    • Ключевое слово sizeof
    • Резюме
    • Глава 12 Делегаты, события и лямбда-выражения
    • Понятие типа делегата
    • Определение типа делегата в C#
    • Базовые классы System.MulticastDelegate и System.Delegate
    • Пример простейшего делегата
    • Исследование объекта делегата
    • Отправка уведомлений о состоянии объекта с использованием делегатов
    • Включение группового вызова
    • Удаление целей из списка вызовов делегата
    • Синтаксис групповых преобразований методов
    • Понятие обобщенных делегатов
    • Обобщенные делегаты Action < > и Func < >
    • Понятие событий C#
    • Ключевое слово event
    • " За кулисами" событий
    • Прослушивание входящих событий
    • Упрощение регистрации событий с использованием Visual Studio
    • Создание специальных аргументов событий
    • Обобщенный делегат EventHandler < T >
    • Понятие анонимных методов C#
    • Доступ к локальным переменным
    • Использование ключевого слова static с анонимными методами (нововведение в версии 9.0)
    • Использование отбрасывания с анонимными методами (нововведение в версии 9.0)
    • Понятие лямбда-выражений
    • Анализ лямбда-выражения
    • Обработка аргументов внутри множества операторов
    • Лямбда-выражения с несколькими параметрами и без параметров
    • Использование ключевого слова static с лямбда-выражениями (нововведение в версии 9.0)
    • Использование отбрасывания с лямбда-выражениями (нововведение в версии 9.0)
    • Модернизация примера CarEvents с использованием лямбда-выражений
    • Лямбда-выражения и члены, сжатые до выражений (обновление в версии 7.0)
    • Резюме
    • Глава 13 LINQ to Objects
    • Программные конструкции, специфичные для LINQ
    • Неявная типизация локальных переменных
    • Синтаксис инициализации объектов и коллекций
    • Лямбда-выражения
    • Расширяющие методы
    • Анонимные типы
    • Роль LINQ
    • Выражения LINQ строго типизированы
    • Основные сборки LINQ
    • Применение запросов LINQ к элементарным массивам
    • Решение с использованием расширяющих методов
    • Решение без использования LINQ
    • Выполнение рефлексии результирующего набора LINQ
    • LINQ и неявно типизированные локальные переменные
    • LINQ и расширяющие методы
    • Роль отложенного выполнения
    • Роль немедленного выполнения
    • Возвращение результатов запроса LINQ
    • Возвращение результатов LINQ посредством немедленного выполнения
    • Применение запросов LINQ к объектам коллекций
    • Доступ к содержащимся в контейнере подобъектам
    • Применение запросов LINQ к необобщенным коллекциям
    • Фильтрация данных с использованием метода OfТуре < Т > ()
    • Исследование операций запросов LINQ
    • Базовый синтаксис выборки
    • Получение подмножества данных
    • Проецирование в новые типы данных
    • Проецирование в другие типы данных
    • Подсчет количества с использованием класса Enumerable
    • Изменение порядка следования элементов в результирующих наборах на противоположный
    • Выражения сортировки
    • LINQ как лучшее средство построения диаграмм Венна
    • Устранение дубликатов
    • Операции агрегирования LINQ
    • Внутреннее представление операторов запросов LINQ
    • Построение выражений запросов с применением операций запросов
    • Построение выражений запросов с использованием типа Enumerable и лямбда-выражений
    • Построение выражений запросов с использованием типа Enumerable и анонимных методов
    • Построение выражений запросов с использованием типа Enumerable и низкоуровневых делегатов
    • Резюме
    • Глава 14 Процессы, домены приложении и контексты загрузки
    • Роль процесса Windows
    • Роль потоков
    • Взаимодействие с процессами используя платформу .NET Core
    • Перечисление выполняющихся процессов
    • Исследование конкретного процесса
    • Исследование набора потоков процесса
    • Исследование набора модулей процесса
    • Запуск и останов процессов программным образом
    • Управление запуском процесса с использованием класса ProcessStartInfo
    • Использование команд операционной системы с классом ProcessStartInfo
    • Домены приложений .NET
    • Класс System.AppDomain
    • Взаимодействие со стандартным доменом приложения
    • Перечисление загруженных сборок
    • Изоляция сборок с помощью контекстов загрузки приложений
    • Итоговые сведения о процессах, доменах приложений и контекстах загрузки
    • Резюме
    • Глава 15 Многопоточное, параллельное и асинхронное программирование
    • Отношения между процессом, доменом приложения, контекстом и потоком
    • Сложность, связанная с параллелизмом
    • Роль синхронизации потоков
    • Пространство имен System.Threading
    • Класс System.Threading.Thread
    • Получение статистических данных о текущем потоке выполнения
    • Свойство Name
    • Свойство Priority
    • Ручное создание вторичных потоков
    • Работа с делегатом ThreadStart
    • Работа с делегатом ParametrizedThreadStart
    • Класс AutoResetEvent
    • Потоки переднего плана и фоновые потоки
    • Проблема параллелизма
    • Синхронизация с использованием ключевого слова lock языка C#
    • Синхронизация с использованием типа System.Threading.Monitor
    • Синхронизация с использованием типа System.Threading.Interlocked
    • Программирование с использованием обратных вызовов Timer
    • Использование автономного отбрасывания (нововведение в версии 7.0)
    • Класс ThreadPool
    • Параллельное программирование с использованием TPL
    • Пространство имен System.Threading.Tasks
    • Роль класса Parallel
    • Обеспечение параллелизма данных с помощью класса Parallel
    • Доступ к элементам пользовательского интерфейса во вторичных потоках
    • Класс Task
    • Обработка запроса на отмену
    • Обеспечение параллелизма задач с помощью класса Parallel
    • Запросы Parallel LINQ (PLINQ)
    • Создание запроса PLINQ
    • Отмена запроса PLINQ
    • Асинхронные вызовы с помощью async/await
    • Знакомство с ключевыми словами async и await языка C# (обновление в версиях 7.1, 9.0)
    • Класс SynchronizationContext и async/await
    • Роль метода ConfigureAwait()
    • Соглашения об именовании асинхронных методов
    • Асинхронные методы, возвращающие void
    • Асинхронные методы, возвращающие void и поддерживающие await
    • Асинхронные методы, возвращающие void и работающие в стиле "запустил и забыл"
    • Асинхронные методы с множеством контекстов await
    • Вызов асинхронных методов из неасинхронных методов
    • Ожидание с помощью await в блоках catch и finally
    • Обобщенные возвращаемые типы в асинхронных методах (нововведение в версии 7.0)
    • Локальные функции (нововведение в версии 7.0)
    • Отмена операций async/await
    • Асинхронные потоки (нововведение в версии 8.0)
    • Итоговые сведения о ключевых словах async и await
    • Резюме
    • Часть V Программирование с использованием сборок .NET Core
    • Глава 16 Построение и конфигурирование библиотек классов
    • Определение специальных пространств имен
    • Разрешение конфликтов имен с помощью полностью заданных имен
    • Разрешение конфликтов имен с помощью псевдонимов
    • Создание вложенных пространств имен
    • Изменение стандартного пространства имен в Visual Studio
    • Роль сборок .NET Core
    • Сборки содействуют многократному использованию кода
    • Сборки устанавливают границы типов
    • Сборки являются единицами, поддерживающими версии
    • Сборки являются самоописательными
    • Формат сборки .NET Core
    • Установка инструментов профилирования C++
    • Заголовок файла операционной системы (Windows)
    • Заголовок файла CLR
    • Код CIL, метаданные типов и манифест сборки
    • Дополнительные ресурсы сборки
    • Отличия между библиотеками классов и консольными приложениями
    • Отличия между библиотеками классов .NET Standard и .NET Core
    • Конфигурирование приложений
    • Построение и потребление библиотеки классов .NET Core
    • Исследование манифеста
    • Исследование кода CIL
    • Исследование метаданных типов
    • Построение клиентского приложения C#
    • Построение клиентского приложения Visual Basic
    • Межъязыковое наследование в действии
    • Открытие доступа к внутренним типам для других сборок
    • Использование атрибута assembly
    • Использование файла проекта
    • NuGet и .NET Core
    • Пакетирование сборок с помощью NuGet
    • Ссылка на пакеты NuGet
    • Опубликование консольных приложений (обновление в версии .NET 5)
    • Опубликование приложений, зависящих от инфраструктуры
    • Опубликование автономных приложений
    • Опубликование автономных приложений в виде единственного файла
    • Определение местонахождения сборок исполняющей средой .NET Core
    • Резюме
    • Глава 17 Рефлексия типов, позднее связывание и программирование на основе атрибутов
    • Потребность в метаданных типов
    • Просмотр (частичных) метаданных для перечисления EngineStateEnum
    • Просмотр (частичных) метаданных для типа Car
    • Исследование блока TypeRef
    • Документирование определяемой сборки
    • Документирование ссылаемых сборок
    • Документирование строковых литералов
    • Понятие рефлексии
    • Класс System.Туре
    • Получение информации о типе с помощью System.Object.GetType()
    • Получение информации о типе с помощью typeof()
    • Получение информации о типе с помощью System.Туре.GetType()
    • Построение специального средства для просмотра метаданных
    • Рефлексия методов
    • Рефлексия полей и свойств
    • Рефлексия реализованных интерфейсов
    • Отображение разнообразных дополнительных деталей
    • Добавление операторов верхнего уровня
    • Рефлексия статических типов
    • Рефлексия обобщенных типов
    • Рефлексия параметров и возвращаемых значений методов
    • Динамическая загрузка сборок
    • Рефлексия сборок инфраструктуры
    • Понятие позднего связывания
    • Класс System.Activato
    • Вызов методов без параметров
    • Вызов методов с параметрами
    • Роль атрибутов .NET
    • Потребители атрибутов
    • Применение атрибутов в C#
    • Сокращенная система обозначения атрибутов C#
    • Указание параметров конструктора для атрибутов
    • Атрибут [Obsolete] в действии
    • Построение специальных атрибутов
    • Применение специальных атрибутов
    • Синтаксис именованных свойств
    • Ограничение использования атрибутов
    • Атрибуты уровня сборки
    • Использование файла проекта для атрибутов сборки
    • Рефлексия атрибутов с использованием раннего связывания
    • Рефлексия атрибутов с использованием позднего связывания
    • Практическое использование рефлексии позднего связывания и специальных атрибутов
    • Построение расширяемого приложения
    • Построение мультипроектного решения ExtendableApp
    • Создание решения и проектов с помощью интерфейса командной строки
    • Добавление событий PostBuild в файлы проектов
    • Создание решения и проектов с помощью Visual Studio
    • Установка зависимостей проектов при компиляции
    • Добавление событий PostBuild
    • Построение сборки CommonSnappableTypes.dll
    • Построение оснастки на C#
    • Построение оснастки на Visual Basic
    • Добавление кода для ExtendableApp
    • Резюме
    • Глава 18 Динамические типы и среда DLR
    • Роль ключевого слова dynamic языка C#
    • Вызов членов на динамически объявленных данных
    • Область использования ключевого слова dynamic
    • Ограничения ключевого слова dynamic
    • Практическое использование ключевого слова dynamic
    • Роль исполняющей среды динамического языка
    • Роль деревьев выражений
    • Динамический поиск в деревьях выражений во время выполнения
    • Упрощение вызовов с поздним связыванием посредством динамических типов
    • Использование ключевого слова dynamic для передачи аргументов
    • Упрощение взаимодействия с СОМ посредством динамических данных (только Windows)
    • Роль основных сборок взаимодействия
    • Встраивание метаданных взаимодействия
    • Общие сложности взаимодействия с СОМ
    • Взаимодействие с СОМ с использованием динамических данных C#
    • Резюме
    • Глава 19 Язык CIL и роль динамических сборок
    • Причины для изучения грамматики языка CIL
    • Директивы, атрибуты и коды операций CIL
    • Роль директив CIL
    • Роль атрибутов CIL
    • Роль кодов операций СIL
    • Разница между кодами операций и их мнемоническими эквивалентами в СIL
    • Заталкивание и выталкивание: основанная на стеке природа CIL
    • Возвратное проектирование
    • Роль меток в коде CIL
    • Взаимодействие c CIL: модификация файла *.il
    • Компиляция кода CIL
    • Директивы и атрибуты CIL
    • Указание ссылок на внешние сборки в CIL
    • Определение текущей сборки в CIL
    • Определение пространств имен в CIL
    • Определение типов классов в CIL
    • Определение и реализация интерфейсов в CIL
    • Определение структур в CIL
    • Определение перечислений в CIL
    • Определение обобщений в CIL
    • Компиляция файла CILTypes.il
    • Соответствия между типами данных в библиотеке базовых классов .NET Core, C# и CIL
    • Определение членов типов в CIL
    • Определение полей данных в CIL
    • Определение конструкторов типа в CIL
    • Определение свойств в CIL
    • Определение параметров членов
    • Исследование кодов операций CIL
    • Директива .maxstack
    • Объявление локальных переменных в CIL
    • Отображение параметров на локальные переменные в CIL
    • Скрытая ссылка this
    • Представление итерационных конструкций в CIL
    • Заключительные слова о языке CIL
    • Динамические сборки
    • Исследование пространства имен System.Reflection.Emit
    • Роль типа System.Reflection.Emit.ILGenerator
    • Выпуск динамической сборки
    • Выпуск сборки и набора модулей
    • Роль типа ModuleBuilder
    • Выпуск типа HelloClass и строковой переменной-члена
    • Выпуск конструкторов
    • Выпуск метода SayHello()
    • Использование динамически сгенерированной сборки
    • Резюме
    • Часть VI Работа с файлами, сериализация объектов и доступ к данным
    • Глава 20 Файловый ввод-вывод и сериализация объектов
    • Исследование пространства имен System.IO
    • Классы Directory(Directorylnfо) и File(FileInfo)
    • Абстрактный базовый класс FileSystemInfo
    • Работа с типом DirectoryInfо
    • Перечисление файлов с помощью типа DirectoryInfо
    • Создание подкаталогов с помощью типа DirectoryInfo
    • Работа с типом Directory
    • Работа с типом DriveInfo
    • Работа с типом FileInfo
    • Метод FileInfo.Create()
    • Метод FileInfо.Open()
    • Методы FileInfо.OpenRead() и FileInfо.OpenWrite()
    • Метод FileInfо.OpenText()
    • Методы FileInfo.CreateText() и FileInfo.AppendText()
    • Работа с типом File
    • Дополнительные члены типа File
    • Абстрактный класс Stream
    • Работа с типом FileStream
    • Работа с типами StreamWriter и StreamReader
    • Запись в текстовый файл
    • Чтение из текстового файла
    • Прямое создание объектов типа StreamWriter/StreamReader
    • Работа с типами StringWriter и StringReader
    • Работа с типами BinaryWriter и BinaryReader
    • Программное слежение за файлами
    • Понятие сериализации объектов
    • Роль графов объектов
    • Создание примеров типов и написание операторов верхнего уровня
    • Сериализация и десериализация с помощью XmlSerializer
    • Управление генерацией данных XML
    • Сериализация объектов с использованием XmlSerializer
    • Сериализация коллекций объектов
    • Десериализация объектов и коллекций объектов
    • Сериализация и десериализация с помощью System.Text.Json
    • Управление генерацией данных JSON
    • Сериализация объектов с использованием JsonSerializer
    • Включение полей
    • Понятный для человека вывод данных JSON
    • Именование элементов JSON в стиле Pascal или в "верблюжьем" стиле
    • Обработка чисел с помощью JsonSerializer
    • Потенциальные проблемы, связанные с производительностью, при использовании JsonSerializerOption
    • Стандартные настройки свойств JsonSerializer для веб-приложений
    • Сериализация коллекций объектов
    • Десериализация объектов и коллекций объектов
    • Резюме
    • Глава 21 Доступ к данным с помощью ADO.NET
    • Сравнение ADO.NET и ADO
    • Поставщики данных ADO.NET
    • Поставщики данных ADO.NET
    • Типы из пространства имен System.Data
    • Роль интерфейса IDbConnection
    • Роль интерфейса IDbTransaction
    • Роль интерфейса IDbCommand
    • Роль интерфейсов IDbDataParameter и IDataParameter
    • Роль интерфейсов IDbDataAdapter и IDataAdapter
    • Роль интерфейсов IDataReader и IDataRecord
    • Абстрагирование поставщиков данных с использованием интерфейсов
    • Установка SQL Server и Azure Data Studio
    • Установка SQL Server
    • Установка SQL Server в контейнер Docker
    • Получение образа и запуск SQL Server 2019
    • Установка SQL Server 2019
    • Установка IDE-среды SQL Server
    • Подключение к SQL Server
    • Подключение к SQL Server в контейнере Docker
    • Подключение к SQL Server LocalDb
    • Подключение к любому другому экземпляру SQL Server
    • Восстановление базы данных AutoLot из резервной копии
    • Копирование файла резервной копии в имеющийся контейнер
    • Восстановление базы данных с помощью SSMS
    • Восстановление базы данных в экземпляр SQL Server (Docker)
    • Восстановление базы данных в экземпляр SQL Server (Windows)
    • Восстановление базы данных с помощью Azure Data Studio
    • Создание базы данных AutoLot
    • Создание базы данных
    • Создание таблиц
    • Создание таблицы Inventory
    • Создание таблицы Makes
    • Создание таблицы Customers
    • Создание таблицы Orders
    • Создание таблицы CreditRisks
    • Создание отношений между таблицами
    • Создание отношения между таблицами Inventory и Makes
    • Создание отношения между таблицами Inventory и Orders
    • Создание отношения между таблицами Orders и Customers
    • Создание отношения между таблицами Customers и CreditRisks
    • Создание хранимой процедуры GetPetName
    • Добавление тестовых записей
    • Записи таблицы Makes
    • Записи таблицы Inventory
    • Добавление тестовых записей в таблицу Customers
    • Добавление тестовых записей в таблицу Orders
    • Добавление тестовых записей в таблицу CreditRisks
    • Модель фабрики поставщиков данных ADO.NET
    • Полный пример фабрики поставщиков данных
    • Потенциальный недостаток модели фабрики поставщиков данных
    • Погружение в детали объектов подключений, команд и чтения данных
    • Работа с объектами подключений
    • Работа с объектами ConnectionStringBuilder
    • Работа с объектами команд
    • Работа с объектами чтения данных
    • Получение множества результирующих наборов с использованием объекта чтения данных
    • Работа с запросами создания обновления и удаления
    • Создание классов Car и CarViewModel
    • Добавление класса InventoryDal
    • Добавление конструкторов
    • Открытие и закрытие подключения
    • Добавление реализации IDisposable
    • Добавление методов выборки
    • Вставка новой записи об автомобиле
    • Создание строго типизированного метода InsertCar()
    • Добавление логики удаления
    • Добавление логики обновления
    • Работа с параметризированным и объектами команд
    • Указание параметров с использованием типа DbParameter
    • Обновление метода GetCar()
    • Обновление метода DeleteCar()
    • Обновление метода UpdateCarPetName()
    • Обновление метода InsertAuto()
    • Выполнение хранимой процедуры
    • Создание консольного клиентского приложения
    • Понятие транзакций базы данных
    • Основные члены объекта транзакции ADO.NET
    • Добавление метода транзакции в inventoryDal
    • Тестирование транзакции базы данных
    • Выполнение массового копирования с помощью ADO.NET
    • Исследование класса SqlBulkCopy
    • Создание специального класса чтения данных
    • Выполнение массового копирования
    • Тестирование массового копирования
    • Резюме
    • Часть VII Entity Framework Core
    • Глава 22 Введение в Entity Framework Core
    • Инструменты объектно-реляционного отображения
    • Роль Entity Framework Core
    • Строительные блоки Entity Framework Core
    • Класс DbContext
    • Создание класса, производного от DbContext
    • Конфигурирование экземпляра DbContext
    • Фабрика DbContext этапа проектирования
    • Метод OnModelCreating()
    • Сохранение изменений
    • Поддержка транзакций и точек сохранения
    • Транзакции и стратегии выполнения
    • События SavingChanges/SavedChanges
    • Класс DbSet < T >
    • Типы запросов
    • Гибкое сопоставление с запросом или таблицей
    • Экземпляр ChangeTracker
    • События ChangeTracker
    • Сброс состояния DbContext
    • Сущности
    • Сопоставление свойств со столбцами
    • Сопоставление классов с таблицами
    • Сопоставление "таблица на иерархию" (ТРН)
    • Сопоставление "таблица на тип" (ТРТ)
    • Навигационные свойства и внешние ключи
    • Отсутствие свойств для внешних ключей
    • Отношения "один ко многим"
    • Отношения "один к одному"
    • Отношения "многие ко многим" (нововведение в версии EF Core 5)
    • Каскадное поведение
    • Необязательные отношения
    • Обязательные отношения
    • Соглашения, связанные с сущностями
    • Отображение свойств на столбцы
    • Аннотации данных Entity Framework
    • Аннотации и навигационные свойства
    • Интерфейс Fluent API
    • Отображение классов и свойств
    • Стандартные значения
    • Вычисляемые столбцы
    • Отношения "один ко многим"
    • Отношения "один к одному"
    • Отношения "многие ко многим"
    • Соглашения, аннотации данных и Fluent API — что выбрать?
    • Выполнение запросов
    • Смешанное выполнение на клиентской и серверной сторонах
    • Сравнение отслеживаемых и неотслеживаемых запросов
    • Важные функциональные средства EF Core
    • Обработка значений, генерируемых базой данных
    • Проверка параллелизма
    • Устойчивость подключений
    • Связанные данные
    • Энергичная загрузка
    • Фильтрованные включаемые данные
    • Энергичная загрузка с разделением запросов
    • Явная загрузка
    • Ленивая загрузка
    • Глобальные фильтры запросов
    • Глобальные фильтры запросов на навигационных свойствах
    • Явная загрузка с глобальными фильтрами запросов
    • Выполнение низкоуровневых запросов SQL с помощью LINQ
    • Пакетирование операторов
    • Принадлежащие сущностные типы
    • Сопоставление с функциями базы данных
    • Команды CLI глобального инструмента EF Core
    • Команды для управления миграциями
    • Команда add
    • Исключение таблиц из миграций
    • Команда remove
    • Команда list
    • Команда script
    • Команды для управления базой данных
    • Команда drop
    • Команда update
    • Команды для управления типами DbContext
    • Команда scaffold
    • Резюме
    • Глава 23 Построение уровня доступа к данным с помощью Entity Framework Core
    • " Сначала код" или "сначала база данных"
    • Создание проектов AutoLot.Dal и AutoLot.Models
    • Создание шаблонов для класса, производного от DbContext, и сущностных классов
    • Переключение на подход "сначала код"
    • Создание фабрики экземпляров класса, производного от DbContext, на этапе проектирования
    • Создание начальной миграции
    • Применение миграции
    • Обновление модели
    • Сущности
    • Класс BaseEntity
    • Принадлежащий сущностный класс Person
    • Сущность Car(Inventory)
    • Сущность Customer
    • Сущность Make
    • Сущность CreditRisk
    • Сущность Order
    • Сущность SeriLogEntry
    • Класс ApplicationDbContext
    • Обновление кода Fluent API
    • Сущность SeriLogEntry
    • Сущность CreditRisk
    • Сущность Customer
    • Сущность Make
    • Сущность Order
    • Сущность Car
    • Специальные исключения
    • Переопределение метода SaveChanges()
    • Обработка событий DbContext и ChangeTracker
    • Создание миграции и обновление базы данных
    • Добавление представления базы данных и хранимой процедуры
    • Добавление класса MigrationHelpers
    • Обновление и применение миграции
    • Добавление модели представления
    • Добавление класса модели представления
    • Добавление класса модели представления к ApplicationDbContext
    • Добавление хранилищ
    • Добавление базового интерфейса IRepo
    • Добавление класса BaseRepo
    • Реализация метода SaveChanges()
    • Реализация общих методов чтения
    • Реализация методов добавления, обновления и удаления
    • Интерфейсы хранилищ, специфичных для сущностей
    • Интерфейс хранилища данных об автомобилях
    • Интерфейс хранилища данных о кредитных рисках
    • Интерфейс хранилища данных о заказчиках
    • Интерфейс хранилища данных о производителях
    • Интерфейс хранилища данных о заказах
    • Реализация классов хранилищ, специфичных для сущностей
    • Хранилище данных об автомобилях
    • Хранилище данных о кредитных рисках
    • Хранилище данных о заказчиках
    • Хранилище данных о производителях
    • Хранилище данных о заказах
    • Программная работа с базой данных и миграциями
    • Удаление, создание и очистка базы данных
    • Инициализация базы данных
    • Создание выборочных данных
    • Загрузка выборочных данных
    • Настройка тестов
    • Создание проекта
    • Конфигурирование проекта
    • Создание класса TestHelpers
    • Добавление класса BaseTest
    • Добавление вспомогательных методов для выполнения тестов в транзакциях
    • Добавление класса тестовой оснастки EnsureAutoLotDatabase
    • Добавление классов интеграционных тестов
    • Тестовые методы [Fact] и [Theory]
    • Выполнение тестов
    • Запрашивание базы данных
    • Состояние сущности
    • Запросы LINQ
    • Выполнение запросов LINQ
    • Получение всех записей
    • Фильтрация записей
    • Сортировка записей
    • Сортировка записей в обратном порядке
    • Извлечение одиночной записи
    • Использование First()/FirstOrDefault()
    • Использование Last()/LastOrDefault()
    • Использование Single()/SingleOrDefault()
    • Глобальные фильтры запросов
    • Отключение глобальных фильтров запросов
    • Фильтры запросов для навигационных свойств
    • Энергичная загрузка связанных данных
    • Разделение запросов к связанным данным
    • Фильтрация связанных данных
    • Явная загрузка связанных данных
    • Явная загрузка связанных данных с фильтрами запросов
    • Выполнение запросов SQL с помощью LINQ
    • Методы агрегирования
    • Any() и All()
    • Получение данных из хранимых процедур
    • Создание записей
    • Состояние сущности
    • Добавление одной записи
    • Добавление одной записи с использованием метода Attach()
    • Добавление нескольких записей одновременно
    • Соображения относительно столбца идентичности при добавлении записей
    • Добавление объектного графа
    • Обновление записей
    • Состояние сущности
    • Обновление отслеживаемых сущностей
    • Обновление неотслеживаемых сущностей
    • Проверка параллелизма
    • Удаление записей
    • Состояние сущности
    • Удаление отслеживаемых сущностей
    • Удаление неотслеживаемых сущностей
    • Перехват отказов каскадного удаления
    • Проверка параллелизма
    • Резюме
    • Часть VIII Разработка клиентских приложений для Windows
    • Глава 24 Введение в Windows Presentation Foundation и XAML
    • Побудительные причины создания WPF
    • Унификация несходных API-интерфейсов
    • Обеспечение разделения обязанностей через XAML
    • Обеспечение оптимизированной модели визуализации
    • Упрощение программирования сложных пользовательских интерфейсов
    • Исследование сборок WPF
    • Роль класса Application
    • Построение класса приложения
    • Перечисление элементов коллекции Windows
    • Роль класса Window
    • Роль класса System.Windows.Controls.ContentControl
    • Роль класса System.Windows.Controls.Control
    • Роль класса System.Windows.FrameworkElement
    • Роль класса System.Windows.UIElement
    • Роль класса System.Windows.Media.Visual
    • Роль класса System.Windows.DependencyObject
    • Роль класса System.Windows.Threading.DispatcherObject
    • Синтаксис XAML для WPF
    • Введение в Kaxaml
    • Пространства имен XML и "ключевые слова" XAML
    • Управление видимостью классов и переменных-членов
    • Элементы XAML, атрибуты XAML и преобразователи типов
    • Понятие синтаксиса "свойство-элемент" в XAML
    • Понятие присоединяемых свойств XAML
    • Понятие расширений разметки XAML
    • Построение приложений WPF с использованием Visual Studio
    • Шаблоны проектов WPF
    • Панель инструментов и визуальный конструктор/редактор XAML
    • Установка свойств с использованием окна Properties
    • Обработка событий с использованием окна Properties
    • Обработка событий в редакторе XAML
    • Окно Document Outline
    • Включение и отключение отладчика XAML
    • Исследование файла Арр.xaml
    • Отображение разметки XAML окна на код C#
    • Роль BAML
    • Разгадывание загадки Main()
    • Взаимодействие с данными уровня приложения
    • Обработка закрытия объекта Window
    • Перехват событий мыши
    • Перехват событий клавиатуры
    • Резюме
    • Глава 25 Элементы управления, компоновки, события и привязка данных в WPF
    • Обзор основных элементов управления WPF
    • Элементы управления для работы с Ink API
    • Элементы управления для работы с документами WPF
    • Общие диалоговые окна WPF
    • Краткий обзор визуального конструктора WPF в Visual Studio
    • Работа с элементами управления WPF в Visual Studio
    • Работа с окном Document Outline
    • Управление компоновкой содержимого с использованием панелей
    • Позиционирование содержимого внутри панелей Canvas
    • Позиционирование содержимого внутри панелей WrapPanel
    • Позиционирование содержимого внутри панелей StackPanel
    • Позиционирование содержимого внутри панелей Grid
    • Установка размеров столбцов и строк в панели Grid
    • Панели Grid с типами GridSplitter
    • Позиционирование содержимого внутри панелей DockPanel
    • Включение прокрутки в типах панелей
    • Конфигурирование панелей с использованием визуальных конструкторов Visual Studio
    • Построение окна с использованием вложенных панелей
    • Построение системы меню
    • Визуальное построение меню
    • Построение панели инструментов
    • Построение строки состояния
    • Завершение проектирования пользовательского интерфейса
    • Реализация обработчиков событий MouseEnter/MouseLeave
    • Реализация логики проверки правописания
    • Понятие команд WPF
    • Внутренние объекты команд
    • Подключение команд к свойству Command
    • Подключение команд к произвольным действиям
    • Работа с командами Open и Save
    • Понятие маршрутизируемых событий
    • Роль пузырьковых маршрутизируемых событий
    • Продолжение или прекращение пузырькового распространения
    • Роль туннельных маршрутизируемых событий
    • Более глубокое исследование API-интерфейсов и элементов управления WPF
    • Работа с элементом управления TabControl
    • Построение вкладки Ink API
    • Проектирование панели инструментов
    • Элемент управления RadioButton
    • Добавление кнопок сохранения, загрузки и удаления
    • Добавление элемента управления InkCanvas
    • Предварительный просмотр окна
    • Обработка событий для вкладки Ink API
    • Добавление элементов управления в панель инструментов
    • Элемент управления InkCanvas
    • Элемент управления ComboBox
    • Сохранение, загрузка и очистка данных InkCanvas
    • Введение в модель привязки данных WPF
    • Построение вкладки Data Binding
    • Установка привязки данных
    • Свойство DataContext
    • Форматирование привязанных данных
    • Преобразование данных с использованием интерфейса IValueConverter
    • Установление привязок данных в коде
    • Построение вкладки DataGrid
    • Роль свойств зависимости
    • Исследование существующего свойства зависимости
    • Важные замечания относительно оболочек свойств CLR
    • Построение специального свойства зависимости
    • Добавление процедуры проверки достоверности данных
    • Реагирование на изменение свойства
    • Резюме
    • Глава 26 Службы визуализации графики WPF
    • Понятие служб визуализации графики WPF
    • Варианты графической визуализации WPF
    • Визуализация графических данных с использованием фигур
    • Добавление прямоугольников, эллипсов и линий на поверхность Canvas
    • Удаление прямоугольников, эллипсов и линий с поверхности Canvas
    • Работа с элементами Polyline и Polygon
    • Работа с элементом Path
    • " Мини-язык" моделирования путей
    • Кисти и перья WPF
    • Конфигурирование кистей с использованием Visual Studio
    • Конфигурирование кистей в коде
    • Конфигурирование перьев
    • Применение графических трансформаций
    • Первый взгляд на трансформации
    • Трансформация данных Canvas
    • Работа с редактором трансформаций Visual Studio
    • Построение начальной компоновки
    • Применение трансформаций на этапе проектирования
    • Трансформация холста в коде
    • Визуализация графических данных с использованием рисунков и геометрических объектов
    • Построение кисти DrawingBrush с использованием геометрических объектов
    • Рисование с помощью DrawingBrush
    • Включение типов Drawing в DrawingImage
    • Работа с векторными изображениями
    • Преобразование файла с векторной графикой в файл XAML
    • Импортирование графических данных в проект WPF
    • Взаимодействие с изображением
    • Визуализация графических данных с использованием визуального уровня
    • Базовый класс Visual и производные дочерние классы
    • Первый взгляд на класс DrawingVisual
    • Визуализация графических данных в специальном диспетчере компоновки
    • Реагирование на операции проверки попадания
    • Резюме
    • Глава 27 Ресурсы, анимация, стили и шаблоны WPF
    • Система ресурсов WPF
    • Работа с двоичными ресурсами
    • Включение в проект несвязанных файлов ресурсов
    • Конфигурирование несвязанных ресурсов
    • Программная загрузка изображения
    • Встраивание ресурсов приложения
    • Работа с объектными (логическими) ресурсами
    • Роль свойства Resources
    • Определение ресурсов уровня окна
    • Расширение разметки {StaticResource}
    • Расширение разметки {DynamicResource}
    • Ресурсы уровня приложения
    • Определение объединенных словарей ресурсов
    • Определение сборки, включающей только ресурсы
    • Службы анимации WPF
    • Роль классов анимации
    • Свойства То, From и By
    • Роль базового класса Timeline
    • Реализация анимации в коде C#
    • Управление темпом анимации
    • Запуск в обратном порядке и циклическое выполнение анимации
    • Реализация анимации в разметке XAML
    • Роль раскадровок
    • Роль триггеров событий
    • Анимация с использованием дискретных ключевых кадров
    • Роль стилей WPF
    • Определение и применение стиля
    • Переопределение настроек стиля
    • Влияние атрибута TargetType на стили
    • Создание подклассов существующих стилей
    • Определение стилей с триггерами
    • Определение стилей с множеством триггеров
    • Стили с анимацией
    • Применение стилей в коде
    • Логические деревья, визуальные деревья и стандартные шаблоны
    • Программное инспектирование логического дерева
    • Программное инспектирование визуального дерева
    • Программное инспектирование стандартного шаблона элемента управления
    • Построение шаблона элемента управления с помощью инфраструктуры триггеров
    • Шаблоны как ресурсы
    • Встраивание визуальных подсказок с использованием триггеров
    • Роль расширения разметки {TemplateBinding}
    • Роль класса ContentPresenter
    • Встраивание шаблонов в стили
    • Резюме
    • Глава 28 Уведомления WPF, проверка достоверности, команды и MWM
    • Введение в паттерн MWM
    • Модель
    • Представление
    • Модель представления
    • Анемичные модели или анемичные модели представлений
    • Система уведомлений привязки WPF
    • Наблюдаемые модели и коллекции
    • Добавление привязок и данных
    • Изменение данных об автомобиле в коде
    • Наблюдаемые модели
    • Использование операции nameof
    • Наблюдаемые коллекции
    • Использование класса ObservableCollection < T >
    • Реализация флага изменения
    • Обновление источника через взаимодействие с пользовательским интерфейсом
    • Итоговые сведения об уведомлениях и наблюдаемых моделях
    • Проверка достоверности WPF
    • Модификация примера для демонстрации проверки достоверности
    • Класс Validation
    • Варианты проверки достоверности
    • Уведомление по исключениям
    • Интерфейс IDataErrorInfo
    • Интерфейс INotifyDataErrorInfo
    • Реализация поддерживающего кода
    • Использование интерфейса INotifyDataErrorInfo для проверки достоверности
    • Комбинирование IDataErrorInfo С INotifyDataErrorInfo для проверки достоверности
    • Отображение всех ошибок
    • Перемещение поддерживающего кода в базовый класс
    • Использование аннотаций данных в WPF
    • Добавление аннотаций данных к модели
    • Контроль ошибок проверки достоверности на основе аннотаций данных
    • Настройка свойства ErrorTemplate
    • Итоговые сведения о проверке достоверности
    • Создание специальных команд
    • Реализация интерфейса ICommand
    • Добавление класса ChangeColorCommand
    • Присоединение команды к CommandManager
    • Изменение файла MainWindow.xaml.cs
    • Изменение файла MainWindow.xaml
    • Тестирование приложения
    • Создание класса CommandBase
    • Добавление класса AddCarCommand
    • Изменение файла MainWindow.xaml.cs
    • Изменение файла MainWindow.xaml
    • Изменение класса ChangeColorCommand
    • Объекты RelayCommand
    • Создание базового класса RelayCommand
    • Создание класса RelayCommand < T >
    • Изменение файла MainWindow.xaml.cs
    • Добавление и реализация кнопки удаления записи об автомобиле
    • Итоговые сведения о командах
    • Перенос кода и данных в модель представления
    • Перенос кода MainWindow.xaml.cs
    • Обновление кода и разметки MainWindow
    • Обновление разметки элементов управления
    • Итоговые сведения о моделях представлений
    • Обновление проекта AutoLot.Dal для MWM
    • Резюме
    • Часть IX ASP.NET Core
    • Глава 29 Введение в ASP.NET Core
    • Краткий экскурс в прошлое
    • Введение в паттерн MVC
    • Модель
    • Представление
    • Контроллер
    • ASP.NET Core и паттерн MVC
    • ASP.NET Core и .NET Core
    • Одна инфраструктура, много сценариев использования
    • Функциональные средства ASP.NET Core из MVC/Web API
    • Соглашения по конфигурации
    • Соглашения об именовании
    • Структура каталогов
    • Папка Controllers
    • Папка Views
    • Папка Shared
    • Папка wwwroot (нововведение в ASP.NET Core)
    • Контроллеры и действия
    • Класс Controller
    • Класс ControllerBase
    • Действия
    • Привязка моделей
    • Словарь ModelState
    • Добавление специальных ошибок в словарь ModelState
    • Неявная привязка моделей
    • Явная привязка моделей
    • Атрибут Bind
    • Управление источниками привязки моделей в ASP.NET Core
    • Проверка достоверности моделей
    • Маршрутизация
    • Шаблоны URL и маркеры маршрутов
    • Маршрутизация и REST-службы ASP.NET Core
    • Маршрутизация на основе соглашений
    • Именованные маршруты
    • Маршрутизация с помощью атрибутов
    • Именованные маршруты
    • Маршрутизация и методы HTTP
    • Методы HTTP при маршрутизации в веб-приложениях (MVC)
    • Маршрутизация для служб API
    • Перенаправление с использованием маршрутизации
    • Фильтры
    • Фильтры авторизации
    • Фильтры ресурсов
    • Фильтры действий
    • Фильтры исключений
    • Фильтры результатов
    • Нововведения в ASP.NET Core
    • Встроенное внедрение зависимостей
    • Осведомленность о среде
    • Выяснение среды времени выполнения
    • Конфигурация приложений
    • Извлечение настроек
    • Развертывание приложений ASP.NET Core
    • Легковесный и модульный конвейер запросов HTTP
    • Создание и конфигурирование решения
    • Использование Visual Studio
    • Создание решения и проектов
    • Добавление проектов AutoLot.Models и AutoLot.Dal
    • Добавление ссылок на проекты
    • Добавление пакетов NuGet
    • Использование командной строки
    • Запуск приложений ASP.NET Core
    • Конфигурирование настроек запуска
    • Использование Visual Studio
    • Использование командной строки или окна терминала Visual Studio Code
    • Изменение кода во время отладки
    • Использование Visual Studio Code
    • Изменение кода во время отладки
    • Отладка приложений ASP.NET Core
    • Присоединение с помощью Visual Studio
    • Присоединение с помощью Visual Studio Code
    • Обновление портов AutoLot.Api
    • Создание и конфигурирование экземпляра WebHost
    • Файл Program.cs
    • Файл Startup.cs
    • Доступные службы для класса Startup
    • Конструктор
    • Метод ConfigureServices()
    • AutoLot.Api
    • Добавление строки подключения к настройкам приложения
    • AutoLot.Mvc
    • Добавление строки подключения к настройкам приложения
    • Метод Configure()
    • AutoLot.Api
    • AutoLot.Mvc
    • Ведение журнала
    • Интерфейс IAppLogging
    • Класс AppLogging
    • Конфигурация ведения журнала
    • Обновление настроек приложения
    • Обновление Program.cs
    • Обновление Startup.cs
    • Обновление контроллера
    • Испытание инфраструктуры ведения журнала
    • Резюме
    • Глава 30 Создание служб REST с помощью ASP.NET Core
    • Введение в REST-службы ASP.NET Core
    • Создание действий контроллера с использованием служб REST
    • Результаты ответов в формате JSON
    • Атрибут ApiController
    • Обязательность маршрутизации с помощью атрибутов
    • Автоматические ответы с кодом состояния 400
    • Выведение источников для привязки параметров
    • Детальные сведения о проблемах для кодов состояния ошибок
    • Обновление настроек Swagger/OpenAPI
    • Обновление обращений к Swagger в классе Startup
    • Добавление файла XML-документации
    • Добавление XML-комментариев в процесс генерации Swagger
    • Дополнительные возможности документирования для конечных точек API
    • Построение методов действий API
    • Конструктор
    • Методы GetXXX()
    • Метод UpdateOne()
    • Метод AddOne()
    • Метод DeleteOne()
    • Класс CarsController
    • Оставшиеся контроллеры
    • Фильтры исключений
    • Создание специального фильтра исключений
    • Добавление фильтров в конвейер обработки
    • Тестирование фильтра исключений
    • Добавление поддержки запросов между источниками
    • Создание политики CORS
    • Добавление политики CORS в конвейер обработки HTTP
    • Резюме
    • Глава 31 Создание приложений MVC с помощью ASP.NET Core
    • Введение в представления ASP.NET Core
    • Экземпляры класса ViewResult и методы действий
    • Механизм визуализации и синтаксис Razor
    • Представления
    • Каталог Views
    • Каталог Shared
    • Каталог DisplayTemplates
    • Шаблон отображения DateTime
    • Шаблон отображения Car
    • Шаблон отображения CarWithColor
    • Каталог EditorTemplates
    • Шаблон редактирования Car
    • Компоновки
    • Указание стандартной компоновки для представлений
    • Частичные представления
    • Обновление компоновки с использованием частичных представлений
    • Создание частичных представлений
    • Частичное представление Head
    • Частичное представление Menu
    • Частичное представление JavaScriptFiles
    • Отправка данных представлениям
    • Строго типизированные представления и модели представлений
    • Объекты ViewBag, ViewData и TempData
    • Вспомогательные функции дескрипторов
    • Включение вспомогательных функций дескрипторов
    • Вспомогательная функция дескриптора для формы
    • Форма создания для сущности Car
    • Вспомогательная функция дескриптора для действия формы
    • Вспомогательная функция дескриптора для якоря
    • Вспомогательная функция дескриптора для элемента ввода
    • Вспомогательная функция дескриптора для текстовой области
    • Вспомогательная функция дескриптора для элемента выбора
    • Вспомогательные функции дескрипторов для проверки достоверности
    • Вспомогательная функция дескриптора для среды
    • Вспомогательная функция дескриптора для ссылки
    • Вспомогательная функция дескриптора для сценария
    • Вспомогательная функция дескриптора для изображения
    • Специальные вспомогательные функции дескрипторов
    • Подготовительные шаги
    • Обновление Startup.cs
    • Создание расширяющего метода для типа string
    • Создание базового класса
    • Вспомогательная функция дескриптора для вывода сведений об элементе
    • Вспомогательная функция дескриптора для удаления элемента
    • Вспомогательная функция дескриптора для редактирования сведений об элементе
    • Вспомогательная функция дескриптора для создания элемента
    • Вспомогательная функция дескриптора для вывода списка элементов
    • Обеспечение видимости специальных вспомогательных функций дескрипторов
    • Вспомогательные функции HTML
    • Вспомогательная функция DisplayFor()
    • Вспомогательная функция DisplayForModel()
    • Вспомогательные функции EditorFor() и EditorForModel()
    • Управление библиотеками клиентской стороны
    • Установка диспетчера библиотек как глобального инструмента .NET Core
    • Добавление в проект AutoLot.Mvc библиотек клиентской стороны
    • Добавление файла libman.json
    • Visual Studio
    • Командная строка
    • Обновление файла libman.json
    • Обновление ссылок на файлы JavaScript и CSS
    • Завершение работы над представлениями CarsController и Cars
    • Класс CarsController
    • Частичное представление списка автомобилей
    • Представление Index
    • Представление ВуMake
    • Представление Details
    • Представление Create
    • Методы действий Create()
    • Вспомогательный метод GetMakes()
    • Метод действия Create() для GET
    • Метод действия Create() для POST
    • Представление Edit
    • Методы действий Edit()
    • Метод действия Edit() для GET
    • Метод действия Edit() для POST
    • Представление Delete
    • Методы действий Delete()
    • Метод действия Delete() для GET
    • Метод действия Delete() для POST
    • Компоненты представлений
    • Код серверной стороны
    • Построение частичного представления
    • Вызов компонентов представлений
    • Вызов компонентов представлений как специальных вспомогательных функций дескрипторов
    • Обновление меню
    • Пакетирование и минификация
    • Пакетирование
    • Минификация
    • Решение WebOptimizer
    • Обновление Startup.cs
    • Обновление _Viewlmports.cshtml
    • Шаблон параметров в ASP.NET Core
    • Добавление информации об автодилере
    • Создание оболочки службы
    • Обновление конфигурации приложения
    • Создание класса ApiServiceSettings
    • Оболочка службы API
    • Интерфейс IApiServiceWrapper
    • Класс ApiServiceWrapper
    • Внутренние поддерживающие методы
    • Вспомогательные методы для POST и PUT
    • Вспомогательный метод для DELETE
    • Вызовы HTTP-метода GET
    • Вызов HTTP-метода POST
    • Вызов HTTP-метода PUT
    • Вызов HTTP-метода DELETE
    • Конфигурирование служб
    • Построение класса CarsController
    • Вспомогательный метод GetMakes()
    • Вспомогательный метод GetOneCar()
    • Открытые методы действий
    • Обновление компонента представления
    • Совместный запуск приложений AutoLot.Mvc и AutoLot.Api
    • Использование Visual Studio
    • Использование командной строки
    • Резюме
    • Примечания
    Загрузка...
    Наш паблик в ВК Наш Телеграм канал Наш канал

    Нравится библиотека?

    Присоединяйтесь к нашим литературным сообществам!

    ВКОНТАКТЕ В ТЕЛЕГРАМ