Uploaded by udinds

aspnet-core-aspnetcore-7.0

advertisement
Оставьте отзыв о скачивании PDF-файла.
Документация по ASP.NET
Узнайте, как с помощью ASP.NET Core создавать быстрые и безопасные кроссплатформенные
или облачные веб-приложения и службы. Изучайте руководства, примеры кода, основные
понятия, справочник по API и многое другое.
НАЧАЛО РАБОТЫ
ОБЩИЕ СВЕДЕНИЯ
СКАЧАТЬ
НОВОЕ
НАЧАЛО РАБОТЫ
НАЧАЛО РАБОТЫ
НАЧАЛО РАБОТЫ
ОБЩИЕ СВЕДЕНИЯ
Создание приложения
ASP.NET Core на любой
платформе за 5 минут
Скачать .NET
Обзор ASP.NET Core
Новые возможности в
документации по ASP.NET Core
Создание первого
пользовательского вебинтерфейса
Создание первого вебприложения в реальном
времени
Создание первого веб-API
Документация по ASP.NET 4.x
Разработка приложений ASP.NET Core
Выбирайте между интерактивными веб-приложениями, веб-API, приложениями на базе
шаблонов MVC, приложениями реального времени и многим другим.
Интерактивные
приложения Blazor на
Приложения API HTTP
Разработка служб HTTP с
Ориентированный на
страницы веб-
стороне клиента
помощью ASP.NET Core
интерфейс с…
Применяйте в разработке
взаимозаменяемые
компоненты
пользовательского…
b Создание минимального
Разрабатывайте основанные
на страницах вебприложения с четким
разделением…
e Обзор
b Создание приложения
Blazor
b Создание первого
приложения Blazor с
взаимозаменяемыми
компонентами
p Модели размещения
Blazor
веб-API с помощью
ASP.NET Core
d Созданию веб-API с
помощью контроллеров
ASP.NET Core
g Создание страниц
справки по веб-API с
использованием
Swagger/OpenAPI
p Типы возвращаемых
значений действий
контроллера
p Форматирование данных
ответа
p Обработка ошибок
g Вызов веб-API
b Создание первого веб-
приложения Razor Pages
g Создание страничного
пользовательского вебинтерфейса с
применением веб-API
p Синтаксис Razor
p Фильтры
p Маршрутизация
p Доступные веб-
приложения ASP.NET
Core
ASP.NET Core с помощью
JavaScript
Ориентированный на
страницы вебинтерфейс с MVC
Веб-приложения
реального времени
с SignalR
Приложения
удаленного вызова
процедур (RPC) —…
Разрабатывайте вебприложения с помощью
шаблона проектирования
MVC (модель —…
Добавьте в свое вебприложение функции
реального времени и
используйте код на стороне…
Разрабатывайте
высокопроизводительные
службы на базе модели
"сначала контракт" с…
e Обзор
e Обзор
e Обзор
b Создание первого веб-
b Создание первого
b Создание клиента и
g SignalR с Blazor
p Основные понятия служб
g Использование SignalR с
s Примеры
приложения MVC в
ASP.NET Core
p Представления
p Частичные представления
p Контроллеры
приложения SignalR
WebAssembly
TypeScript
p Маршрутизация к
s Примеры
p Модульный тест
p Функции клиентов SignalR
действиям контроллера
p Концентраторы
p Размещение и
масштабирование
сервера gRPC
gRPC в C#
p Сравнение служб gRPC с
API-интерфейсами HTTP
g Добавление службы gRPC
в приложение
ASP.NET Core
g Вызов служб gRPC с
помощью клиента .NET
g Использование gRPC в
приложениях на основе
браузера
Веб-приложения на
основе данных
Предыдущие версии
платформы ASP.NET
Создавайте веб-приложения,
работающие на основе
данных, в ASP.NET Core.
Изучайте обзоры, учебники,
основные понятия,
архитектуру и справочники
по API для предыдущих…
g SQL в ASP.NET Core
p Привязка данных к Blazor
p ASP.NET 4.x
в ASP.NET Core
Учебные видео по
ASP.NET Core
q Серия видео об ASP.NET
Core 101
q Серия видео об Entity
Framework Core 101, .NET
Core и ASP.NET Core
q Архитектура микрослужб
и ASP.NET Core
g SQL Server Express и
Razor Pages
q Серия видео о Blazor
g Entity Framework Core и
q .NET Channel
Razor Pages
g Entity Framework Core и
MVC в ASP.NET Core
g Хранилище Azure
g Хранилище BLOBобъектов
p Хранилище таблиц Azure
p Сценарии Microsoft Graph
для ASP.NET Core
Концепции и функции
Справочник по API для ASP.NET Core
Размещение и развертывание
Браузер API .NET
Обзор
Развертывание в службе приложений Azure
DevOps для разработчиков ASP.NET Core
Linux c Apache
Linux с Nginx
Kestrel
IIS
Docker
Безопасность и идентификация
Глобализация и локализация
Обзор
Обзор
Аутентификация
Локализация переносимых объектов
Авторизация
Расширяемость локализации
Курс. Защита веб-приложения ASP.NET Core с
помощью Identity Framework
Устранение неполадок
Защита данных
Управление секретами
Принудительное использование HTTPS
Размещение Docker с использованием HTTPS
Тестирование, отладка и устранение
неполадок
Azure и ASP.NET Core
Модульные тесты Razor Pages
ASP.NET Core и Docker
Удаленная отладка
Отладка моментальных снимков
Размещение веб-приложения в Службе
приложений Azure
Интеграционные тесты
Служба приложений и База данных SQL Azure
Нагрузочное тестирование
Управляемое удостоверение для ASP.NET Core и
Базы данных SQL Azure
Устранение неполадок и отладка
Развертывание веб-приложения ASP.NET Core
Ведение журнала
Веб-API с поддержкой CORS в Службе
приложений Azure
Нагрузочное тестирование веб-приложений
Azure с помощью Azure DevOps
Сбор журналов веб-приложений с помощью
журналов диагностики Службы приложений
Производительность
Дополнительные функции
Обзор
Привязка модели
Память и сборка мусора
Проверка модели
Кэширование откликов
Написание ПО промежуточного слоя
Сжатие ответов
Операции запросов и ответов
Диагностические средства
Переопределение URL-адресов
Нагрузочное тестирование
Миграция
Architecture
С версии ASP.NET Core 5.0 на 6.0
Выбор между традиционными вебприложениями и одностраничными
приложениями (SPA)
Примеры кода с версии ASP.NET Core 5.0 на 6.0
для минимальной модели размещения
С версии ASP.NET Core 3.1 на 5.0
С версии ASP.NET Core 3.0 на 3.1
С версии ASP.NET Core 2.2 на 3.0
С версии ASP.NET Core 2.1 на 2.2
Архитектурные принципы
Общие архитектуры веб-приложений
Распространенные клиентские веб-технологии
Процесс разработки для Azure
Переход с ASP.NET Core 2.0 на 2.1
Миграция с ASP.NET Core 1.x на 2.0
Миграция с ASP.NET на ASP.NET Core
Примите участие в создании документации по ASP.NET Core. Ознакомьтесь с нашим руководством для
соавторов .
Документация по ASP.NET Core —
новые возможности
Добро пожаловать в раздел о новых возможностях в документации по ASP.NET
Core. На этой странице вы сможете быстро найти последние изменения.
Поиск обновлений в документации по ASP.NET Core
h
НОВОЕ
Июнь 2022 г.
Март 2022 г.
Февраль 2022 г.
Январь 2022 г.
Декабрь 2021 г.
Ноябрь 2021 г.
Принимайте участие в разработке документации по ASP.NET Core
e
ОБЩИЕ СВЕДЕНИЯ
Репозиторий документов по ASP.NET Core
Структура и метки проекта для проблем и запросов на вытягивание
p
КОНЦЕПЦИЯ
Руководство для соавторов документации Майкрософт
Руководство для соавторов документации по ASP.NET Core
Руководство для соавторов справочной документации по API ASP.NET Core
Сообщество
h
НОВОЕ
Сообщество
Сообщество
Связанные страницы о новых возможностях
h
НОВОЕ
Обновления документации по Xamarin
Заметки о выпуске .NET Core
Заметки о выпуске ASP.NET Core
Заметки о выпуске компилятора C# (Roslyn)
Заметки о выпуске Visual Studio
Заметки о выпуске Visual Studio для Mac
Заметки о выпуске Visual Studio Code
Общие сведения об ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 8 мин
Авторы: Дэниэл Рот (Daniel Roth) , Рик Андерсон (Rick Anderson)
и Шон Луттин
(Shaun Luttin)
ASP.NET Core является кроссплатформенной, высокопроизводительной средой с
открытым исходным кодом
для создания современных облачных приложений,
подключенных к Интернету.
ASP.NET Core позволяет выполнять следующие задачи:
Создавать веб-приложения и службы, приложения Интернета вещей
(IoT) и
серверные части для мобильных приложений.
Использовать избранные средства разработки в Windows, macOS и Linux.
Выполнять развертывания в облаке или локальной среде.
Запускать в .NET Core.
Преимущества, обеспечиваемые ASP.NET
Core
Миллионы разработчиков использовали и продолжают использовать ASP.NET 4.x
для создания веб-приложений. ASP.NET Core — это модификация ASP.NET 4.x с
архитектурными изменениями, формирующими более рациональную и более
модульную платформу.
ASP.NET Core предоставляет следующие преимущества:
Единое решение для создания пользовательского веб-интерфейса и веб-API.
Разработано для тестируемости.
Razor Pages упрощает написание кода для сценариев страниц и повышает его
эффективность.
Blazor позволяет использовать в браузере язык C# вместе с JavaScript.
совместное использование серверной и клиентской логик приложений,
написанных с помощью .NET;
Возможность разработки и запуска в ОС Windows, macOS и Linux.
Открытый исходный код и ориентация на сообщество .
Интеграция современных клиентских платформ и рабочих процессов
разработки.
Поддержка размещения служб удаленного вызова процедур (RPC) с помощью
gRPC.
Облачная система конфигурации на основе среды.
Встроенное введение зависимостей.
Упрощенный высокопроизводительный
модульный конвейер HTTP-
запросов.
Следующие возможности размещения:
Kestrel
Службы IIS
HTTP.sys
Nginx
Apache
Docker
Управление параллельными версиями.
Инструментарий, упрощающий процесс современной веб-разработки.
Создание веб-API и пользовательского вебинтерфейса с помощью ASP.NET Core MVC
ASP.NET Core MVC предоставляет функции, которые позволяют создавать вебинтерфейсы API и веб-приложения.
Шаблон Model-View-Controller (MVC) помогает сделать веб-API и вебприложения тестируемыми.
Razor Pages — это основанная на страницах модель программирования,
которая упрощает и повышает эффективность создания пользовательского
веб-интерфейса.
Разметка Razor предоставляет эффективный синтаксис для страниц Razor
Pages и представлений MVC.
Вспомогательные функции тегов позволяют серверному коду участвовать в
создании и отображении HTML-элементов в файлах Razor.
Благодаря встроенной поддержке нескольких форматов данных и
согласованию содержимого веб-API становятся доступными для множества
клиентов, включая браузеры и мобильные устройства.
Привязка модели автоматически сопоставляет данные из HTTP-запросов с
параметрами методов действия.
Проверка модели автоматически выполняется на стороне сервера и клиента.
Клиентская разработка
ASP.NET Core легко интегрируется с распространенными клиентскими
платформами и библиотеками, в том числе Blazor, Angular, React и Bootstrap .
Подробнее см. в статье ASP .NET CoreBlazor и сопутствующих материалах в разделе
Разработка на стороне клиента.
Целевые версии платформы ASP.NET Core
ASP.NET Core 3.x или более поздней версии можно использовать только для .NET
Core. Как правило, ASP.NET Core состоит из библиотек .NET Standard. Библиотеки,
написанные на .NET Standard 2.0 под управлением любой платформы .NET с
реализацией .NET Standard 2.0.
При использовании .NET Core существуют некоторые преимущества, и их число
увеличивается с каждым выпуском. Преимущества .NET Core по сравнению с
.NET Framework включают:
Кроссплатформенность. Выполняется в Windows, macOS и Linux.
Повышение производительности
Управление параллельными версиями
Новые интерфейсы API
Открытый код
Рекомендуемая схема обучения
Для знакомства с разработкой приложений ASP.NET Core рекомендуется изучить
следующую последовательность учебников.
1. Пройдите учебник по тому типу приложения, которое вы собираетесь
разрабатывать или обслуживать.
Тип приложения
Сценарий
Учебник
Веб-приложение
Разработка нового веб-интерфейса на
стороне сервера
Начало работы
с Razor Pages
Веб-приложение
Обслуживание приложения MVC
Начало работы
с MVC
Веб-приложение
Разработка веб-интерфейса на стороне
клиента
Начало работы
с Blazor
Веб-интерфейс API
Службы HTTP RESTful
Создание вебAPI†
Тип приложения
Сценарий
Учебник
Приложение
удаленного вызова
процедур
Разработка в соответствии с парадигмой
"Сначала контракт" с использованием Protocol
Buffers
Начало работы
со
службой gRPC
Приложение
Двунаправленный обмен данными между
Начало работы
режима реального
времени
сервером и подключенными к нему
клиентами
с SignalR
2. Пройдите учебник, посвященный основам доступа к данным.
Сценарий
Учебник
Разработка нового приложения
Razor Pages с Entity Framework Core
Обслуживание приложения MVC
MVC с Entity Framework Core
3. Ознакомьтесь с обзором основ ASP.NET Core, относящихся ко всем типам
приложений.
4. Просмотрите содержание, чтобы найти другие интересующие вас темы.
†Доступен интерактивный учебник по веб-API. Локальная установка средств
разработки не требуется. Код выполняется в Azure Cloud Shell
в браузере, а для
тестирования используется curl .
Миграция с .NET Framework
Справочное руководство по миграции приложений ASP.NET 4.х на ASP.NET Core см.
в статье Миграция с ASP.NET на ASP.NET Core.
Загрузка примера
Многие статьи и учебники содержат ссылки на примеры кода.
1. Загрузите ZIP-файл репозитория ASP.NET .
2. Распакуйте файл AspNetCore.Docs-main.zip .
3. Чтобы получить доступ к примеру приложения из статьи в распакованном
репозитории, используйте URL-адрес примера ссылки из статьи для перехода
к папке примера. Как правило, пример ссылки из статьи отображается в ее
верхней части. Текст ссылки: Просмотрите или загрузите пример кода.
Директивы препроцессора в примере кода
Для демонстрации нескольких сценариев в примерах приложений используются
директивы препроцессора #define и #if-#else/#elif-#endif , выборочно
компилирующие и запускающие разные фрагменты примеров кода. В примерах,
где применяется этот подход, задайте в начале файлов C# директиву #define для
определения символа, связанного со сценарием, который нужно запустить. Для
запуска сценария в некоторых примерах потребуется определить символ в начале
нескольких файлов.
Например, в следующем списке символов #define видно, что доступно четыре
сценария (один сценарий на символ). В текущем примере конфигурации
запускается сценарий TemplateCode :
C#
#define TemplateCode // or LogFromMain or ExpandDefault or FilterInCode
Чтобы запустить в примере сценарий ExpandDefault , задайте символ ExpandDefault
и оставьте остальные символы раскомментированными:
C#
#define ExpandDefault // TemplateCode or LogFromMain or FilterInCode
Дополнительные сведения об использовании директив препроцессора C# для
выборочной компиляции фрагментов кода см. в разделах #define (Справочник по
C#) и #if (Справочник по C#).
Регионы в примере кода
Некоторые примеры приложений содержат фрагменты кода внутри директив C#
#region и #endregion. Система сборки документации вставляет эти регионы в
обработанные разделы документации.
Имена регионов обычно содержат слово "фрагмент". В следующем примере
показан регион с именем snippet_WebHostDefaults :
C#
#region snippet_WebHostDefaults
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
#endregion
На предыдущий фрагмент кода C# указывает ссылка в следующей строке в файле
Markdown раздела:
Markdown
[!code-csharp[](sample/SampleApp/Program.cs?name=snippet_WebHostDefaults)]
Вы можете спокойно проигнорировать или удалить директивы #region и
#endregion вокруг кода. Не изменяйте код внутри этих директив, если планируете
запустить примеры сценариев, описанные в разделе. Вы можете изменить код,
экспериментируя с другими сценариями.
Дополнительные сведения см. в разделеУчастие в написании документации
ASP.NET: Фрагменты кода .
Критические изменения и советы по
безопасности
Критические изменения и рекомендации по безопасности отображаются в
репозитории объявлений . Объявления можно ограничить определенной
версией, выбрав фильтр меток.
Дальнейшие действия
Дополнительные сведения см. в следующих ресурсах:
Начиная работать с ASP.NET Core
Публикация приложения ASP.NET Core в Azure с помощью Visual Studio
Основы ASP.NET Core
В еженедельном выпуске ASP.NET Community Standup
рассматривается ход
работы и планы команды. Помимо этого, публикуются новые блоги и
стороннее программное обеспечение.
Выбор между ASP.NET 4.x и ASP.NET
Core
Статья • 28.01.2023 • Чтение занимает 2 мин
ASP.NET Core является переработанной версией ASP.NET 4.x. В этой статье
перечислены различия между ними.
ASP.NET Core
ASP.NET Core — это кроссплатформенная среда с открытым кодом для создания
современных облачных веб-приложений в Windows, macOS или Linux.
ASP.NET Core предоставляет следующие преимущества:
Единое решение для создания пользовательского веб-интерфейса и веб-API.
Разработано для тестируемости.
Razor Pages упрощает написание кода для сценариев страниц и повышает его
эффективность.
Blazor позволяет использовать в браузере язык C# вместе с JavaScript.
совместное использование серверной и клиентской логик приложений,
написанных с помощью .NET;
Возможность разработки и запуска в ОС Windows, macOS и Linux.
Открытый исходный код и ориентация на сообщество .
Интеграция современных клиентских платформ и рабочих процессов
разработки.
Поддержка размещения служб удаленного вызова процедур (RPC) с помощью
gRPC.
Облачная система конфигурации на основе среды.
Встроенное введение зависимостей.
Упрощенный высокопроизводительный
запросов.
Следующие возможности размещения:
Kestrel
Службы IIS
HTTP.sys
Nginx
Apache
Docker
Управление параллельными версиями.
модульный конвейер HTTP-
Инструментарий, упрощающий процесс современной веб-разработки.
ASP.NET 4.x
ASP.NET 4.x — это развитая платформа, предоставляющая необходимые службы
для создания серверных веб-приложений корпоративного класса в Windows.
Выбор платформы
В следующей таблице сравниваются ASP.NET Core и ASP.NET 4.x.
ASP.NET Core
ASP.NET 4.x
Предназначена для Windows, macOS или Linux
Предназначена для Windows
Razor Pages — рекомендуемый метод создания
Использование веб-форм,
пользовательского веб-интерфейса в ASP.NET Core 2.x.
См. также сведения об MVC, веб-API и SignalR.
SignalR, MVC, веб-API, вебперехватчиков или веб-страниц
Несколько версий для одного компьютера
Одна версия для одного
компьютера
Разработка в Visual Studio , Visual Studio для Mac
Visual Studio Code с использованием C# или F#
или
Разработка с Visual Studio с
использованием C#, VB или F#
Более высокая производительность, чем в ASP.NET 4.x
Хорошая производительность
Использование среды выполнения .NET Core
Использование среды
выполнения .NET Framework
Дополнительные сведения о поддержке ASP.NET Core 2.x на платформе .NET
Framework см. в разделе ASP.NET Core для платформы .NET Framework.
Сценарии ASP.NET Core
Веб-сайты
API-интерфейсы
Режим реального времени
Развертывание приложения ASP.NET Core в Azure
Сценарии ASP.NET 4.x
Веб-сайты
API-интерфейсы
Режим реального времени
Создание веб-приложение ASP.NET 4.x в Azure
Дополнительные ресурсы
Введение в ASP.NET
Введение в ASP.NET Core
Развертывание приложений ASP.NET Core в Службе приложений Azure
.NET и .NET Framework для серверных
приложений
Статья • 05.10.2022 • Чтение занимает 5 мин
Для создания серверных приложений доступны две поддерживаемые реализации
.NET.
Реализация
Включенные версии
.NET
.NET Core 1.0–3.1, .NET 5 и более поздние версии .NET.
.NET Framework
.NET Framework 1.0–4.8
В них используется множество одинаковых компонентов, а код можно
использовать как в одной среде, так и в другой. Однако между ними есть
фундаментальные различия, и ваш выбор зависит от того, что вы хотите достичь. В
этой статье содержатся рекомендации по использованию каждой из двух сред
выполнения.
Используйте среду .NET для создания серверных приложений в следующих случаях:
для создания кроссплатформенных решений;
для создания решений, ориентированных на микрослужбы;
при использовании контейнеров Docker;
если нужны масштабируемые системы с высокой производительностью;
для создания приложений с поддержкой разных версий .NET.
Используйте среду .NET Framework для создания серверных приложений в
следующих случаях:
приложение в настоящий момент использует среду .NET Framework (мы
рекомендуем расширять приложения, а не переносить их в другую среду);
приложение использует сторонние библиотеки или пакеты NuGet,
недоступные для .NET;
приложение использует технологии .NET Framework, недоступные для .NET;
приложение использует платформу, не поддерживающую .NET.
Случаи использования .NET
В следующих разделах более подробно описаны ранее перечисленные причины
для выбора платформы .NET вместо .NET Framework.
Создание кроссплатформенных приложений
Если веб-приложение или служба будут работать на нескольких платформах
(Windows, Linux и macOS), используйте платформу .NET.
В среде .NET также можно использовать упомянутые ранее операционные системы
в качестве рабочих станций для разработки. Visual Studio предоставляет
интегрированную среду разработки (IDE) для Windows и macOS. Можно также
использовать редактор Visual Studio Code, который выполняется на платформах
macOS, Linux и Windows. Visual Studio Code поддерживает .NET, включая
технологию IntelliSense и отладку. С .NET работает большинство сторонних
редакторов, например Sublime, Emacs и VI. Эти сторонние редакторы получают
доступ к функциям в редакторе IntelliSense с помощью Omnisharp . Вы также
можете избежать любого редактора кода и напрямую использовать .NET CLI,
который доступен для всех поддерживаемых платформ.
Архитектура микрослужб
Архитектура микрослужб позволяет использовать сочетание технологий за
пределами службы. Такое сочетание технологий позволяет постепенно добавлять
новые микрослужбы в .NET для параллельного использования с другими службами
и микрослужбами. Например, можно комбинировать микрослужбы или службы,
созданные на основе .NET Framework, Java, Ruby или других монолитные
технологий.
Пользователям на выбор предоставляется множество инфраструктурных платформ.
Для больших и сложных систем микрослужб можно использовать Azure Service
Fabric
. Служба приложений Azure
лучше всего подойдет для микрослужб без
сохранения состояния. Альтернативы микрослужб на основе Docker подходят к
любым микрослужбам, как описано в разделе "Контейнеры ". Все эти платформы
поддерживают .NET и идеально подходят для размещения микрослужб.
Дополнительные сведения об архитектуре микрослужб см. в статье Микрослужбы
.NET: архитектура контейнерных приложений .NET.
Контейнеры
Контейнеры обычно используются с архитектурой микрослужб. Их также можно
использовать, чтобы поместить в контейнер веб-приложения или службы на базе
любого архитектурного шаблона. платформа .NET Framework можно использовать
в контейнерах Windows. Тем не менее, модульность и упрощенный характер .NET
делают его лучшим выбором для контейнеров. При создании и развертывании
контейнера размер его образа намного меньше в .NET, чем при использовании
платформа .NET Framework. Так как она кроссплатформенная, вы можете
развертывать серверные приложения в контейнерах Docker для Linux.
Контейнеры Docker можно размещать в собственной инфраструктуре Linux или
Windows или в облачной службе, например в Служба Azure Kubernetes . Служба
Azure Kubernetes может выполнять оркестрацию и масштабировать приложения на
основе контейнеров, а также управлять ими в облаке.
Масштабируемые системы с высокой
производительностью
Если для вашей системы требуется максимальная производительность и
возможности масштабирования, мы рекомендуем использовать среды .NET и
ASP.NET Core. Высокопроизводительная среда выполнения сервера для Windows
Server и Linux делает ASP.NET Core высокопроизводительной веб-платформы на
тестах TechEmpower .
Производительность и масштабируемость особенно важны для архитектур
микрослужб, где могут работать сотни микрослужб. Среда ASP.NET Core позволяет
уменьшить количество серверов и виртуальных машин, необходимых для системы.
Сокращенные серверы и виртуальные машины экономят затраты на
инфраструктуру и размещение.
Параллельные версии .NET на уровне приложения
Если требуется установить приложения с зависимостями в разных версиях
платформ .NET, рекомендуется использовать среду .NET. Эта реализация
поддерживает параллельную установку разных версий среды выполнения .NET на
одном компьютере. Параллельная установка позволяет использовать несколько
служб на одном сервере, каждая из которых имеет собственную версию .NET. Это
позволяет устранить риски и сократить расходы на обновление приложений и ИТоперации.
Параллельная установка невозможна при использовании .NET Framework. Это
компонент Windows, и на компьютере может существовать только одна версия
этого компонента. Каждая версия .NET Framework заменяет предыдущую версию.
Если вы устанавливаете новое приложение, предназначенное для более поздней
версии платформа .NET Framework, вы можете нарушить существующие
приложения, которые выполняются на компьютере, так как предыдущая версия
была заменена.
Случаи использования .NET Framework
Среда .NET предоставляет значительные преимущества для новых приложений и
шаблонов приложений. Но платформа .NET Framework остается оптимальным
выбором во многих ситуациях, поэтому .NET не заменит .NET Framework для всех
серверных приложений.
Готовые приложения .NET Framework
В большинстве случаев вам не потребуется переносить готовые приложения в
среду .NET. Вместо этого рекомендуется использовать .NET при расширении
существующего приложения, например при написании новой веб-службы в
ASP.NET Core.
Сторонние библиотеки .NET и пакеты NuGet,
недоступные для .NET
.NET Standard позволяет совместно использовать код во всех реализациях .NET,
включая .NET Core/5+. В .NET Standard 2.0 этот режим совместимости позволяет
проектам .NET Standard и .NET ссылаться на библиотеки .NET Framework.
Дополнительные сведения см. в статье Поддержка библиотек платформы .NET
Framework.
Платформу .NET Framework следует применять только в случаях, где библиотеки
или пакеты NuGet используют технологии, которые недоступны в .NET Standard и
.NET.
Технологии .NET Framework, недоступные в .NET
Некоторые технологии .NET Framework недоступны в среде .NET. Ниже приведен
список самых распространенных технологий, которые недоступны в .NET:
ASP.NET Web Forms приложения: ASP.NET Web Forms доступны только в
платформа .NET Framework. ASP.NET Core нельзя использовать для ASP.NET
Web Forms.
веб-страницы ASP.NET приложения: веб-страницы ASP.NET не включены в
ASP.NET Core.
Службы, связанные с рабочими процессами: Windows Workflow Foundation
(WF), службы рабочих процессов (WCF + WF в одной службе) и WCF Data
Services (прежнее название — "службы данных ADO.NET") доступны только в
платформа .NET Framework.
Поддержка языка: Visual Basic и F# в настоящее время поддерживаются в
.NET, но не для всех типов проектов. Список поддерживаемых шаблонов
проектов см. в статье о параметрах шаблона для dotnet new.
Дополнительные сведения см. в статье Технологии .NET Framework, недоступные в
.NET Core и .NET 5 и более поздних версий.
Платформа не поддерживает .NET
Некоторые платформы Майкрософт и платформы сторонних поставщиков не
поддерживают среду .NET. Некоторые службы Azure предоставляют пакеты SDK,
недоступные в среде .NET. В таких случаях в качестве альтернативы клиентскому
пакету SDK можно использовать REST API.
См. также
Выбор между ASP.NET и ASP.NET Core
ASP.NET Core с целевой платформой .NET Framework
Целевые платформы
Общие сведения о платформе .NET
Перенос кода в .NET 5 из .NET Framework
Общие сведения о .NET и Docker
Реализации .NET
Микрослужбы .NET. Архитектура контейнерных приложений .NET
Учебник. Начало работы с ASP.NET
Core
Статья • 28.01.2023 • Чтение занимает 2 мин
В этом руководстве показано, как с помощью .NET Core CLI создать и запустить вебприложение ASP.NET Core.
Вы научитесь:
" создавать проект веб-приложения;
" устанавливать доверие к сертификату разработки;
" Запустите приложение.
" Измените страницу Razor.
В итоге вы получите рабочее веб-приложение на локальном компьютере.
Предварительные требования
Пакет SDK для .NET 6.0
Создание проекта веб-приложения
Откройте окно командной оболочки и введите следующую команду:
Интерфейс командной строки.NET
dotnet new webapp -o aspnetcoreapp
Предыдущая команда позволяет:
создать веб-сайт;
с помощью параметра -o aspnetcoreapp создать каталог aspnetcoreapp с
исходными файлами приложения.
Установка доверия к сертификату разработки
Установите доверие к сертификату разработки HTTPS.
Windows
Интерфейс командной строки.NET
dotnet dev-certs https --trust
Приведенная выше команда отображает следующее диалоговое окно.
Выберите Да, если согласны доверять сертификату разработки.
Дополнительные сведения см. в разделе Trust the ASP.NET Core HTTPS development
certificate (Настройка доверия к сертификату разработки HTTPS ASP.NET Core).
Запуск приложения
Выполните следующие команды:
Интерфейс командной строки.NET
cd aspnetcoreapp
dotnet watch run
Когда в командной оболочке будет показано, что приложение запущено, откройте
страницу https://localhost:{port} , где {port} — случайный порт.
Изменение страницы Razor
Откройте Pages/Index.cshtml , а затем измените и сохраните страницу, добавив
выделенное исправление:
CSHTML
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Hello, world! The time on the server is @DateTime.Now</p>
</div>
Перейдите на страницу https://localhost:{port} , обновите ее и проверьте,
отобразились ли изменения.
Следующие шаги
В этом руководстве вы узнали, как:
" создавать проект веб-приложения;
" устанавливать доверие к сертификату разработки;
" Запустите проект.
" вносить изменения.
Дополнительные сведения об ASP.NET Core:
Общие сведения об ASP.NET Core
Новые возможности в ASP.NET
Core 7.0
Статья • 28.01.2023 • Чтение занимает 24 мин
В этой статье описываются наиболее важные изменения в ASP.NET Core 7.0 со
ссылками на соответствующую документацию.
ПО промежуточного слоя для ограничения
скорости в ASP.NET Core
ПО промежуточного слоя Microsoft.AspNetCore.RateLimiting предоставляет ПО
промежуточного слоя для ограничения скорости. Приложения настраивают
политики ограничения скорости, а затем присоединяют политики к конечным
точкам. Дополнительные сведения см. в статье ПО промежуточного слоя для
ограничения скорости в ASP.NET Core.
Проверка подлинности использует одну
схему в качестве DefaultScheme
В рамках работы по упрощению проверки подлинности, если зарегистрирована
только одна схема проверки подлинности, она автоматически используется в
качестве DefaultScheme и не требуется указывать. Дополнительные сведения см. в
разделе DefaultScheme.
MVC и Razor Pages
Поддержка моделей, допускающих значение NULL, в
представлениях MVC и Razor Pages
Модели страниц или представлений, допускающих значение NULL,
поддерживаются для улучшения работы при проверке состояния NULL в
приложениях ASP.NET Core:
C#
@model Product?
Привязка с помощью IParsable<T>.TryParse в
контроллерах API и MVC
API IParsable<TSelf>.TryParse поддерживает привязку значений параметров
действий контроллера. Дополнительные сведения см. в разделе Привязка с
помощью IParsable<T>.TryParse.
Настройка значения согласия cookie
В версиях ASP.NET Core, предшествующих 7, при проверке согласия cookie
используется значение yes cookie, чтобы указать согласие. Теперь можно указать
значение, представляющее согласие. Например, можно использовать true вместо
yes :
C#
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
options.ConsentCookieValue = "true";
});
var app = builder.Build();
Дополнительные сведения см. в статье о настройке значения согласия cookie.
Контроллеры API
Привязка параметров с помощью внедрения
зависимостей в контроллерах API
Привязка параметров для действий контроллера API позволяет привязать
параметры с помощью внедрения зависимостей при настройке типа в качестве
службы. Это означает, что больше не требуется явно применять атрибут
[FromServices] к параметру. В следующем коде оба действия возвращают время:
C#
[Route("[controller]")]
[ApiController]
public class MyController : ControllerBase
{
public ActionResult GetWithAttribute([FromServices] IDateTime dateTime)
=> Ok(dateTime.Now);
[Route("noAttribute")]
public ActionResult Get(IDateTime dateTime) => Ok(dateTime.Now);
}
В редких случаях автоматическое внедрение зависимостей может прервать работу
приложения, если оно имеет тип, который также принимается в методах действий
контроллеров API. Обычно тип во внедрении зависимостей и тип, используемый в
качестве аргумента в действии контроллера API, не совпадают. Чтобы отключить
автоматическую привязку параметров, задайте
DisableImplicitFromServicesParameters:
C#
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSingleton<IDateTime, SystemDateTime>();
builder.Services.Configure<ApiBehaviorOptions>(options =>
{
options.DisableImplicitFromServicesParameters = true;
});
var app = builder.Build();
app.MapControllers();
app.Run();
В ASP.NET Core 7.0 типы во внедрении зависимостей проверяются при запуске
приложения с помощью IServiceProviderIsService, чтобы определить, поступает ли
аргумент в действии контроллера API из внедрения зависимостей или из других
источников.
Новый механизм вывода источника привязки параметров действия контроллера
API использует следующие правила:
1. Указанный ранее BindingInfo.BindingSource не перезаписывается.
2. Назначается параметр сложного типа, зарегистрированный в контейнере
внедрения зависимостей: BindingSource.Services.
3. Назначается параметр сложного типа, не зарегистрированный в контейнере
внедрения зависимостей: BindingSource.Body.
4. Параметру с именем, которое появляется как значение маршрута в любом
шаблоне маршрута, присваивается BindingSource.Path.
5. В качестве остальных параметров используется BindingSource.Query.
Имена свойств JSON в ошибках проверки
По умолчанию при возникновении ошибки проверки в результате проверки
модели создается ModelStateDictionary с именем свойства в качестве ключа
ошибки. Некоторые приложения, например одностраничные, используют имена
свойств JSON для ошибок проверки, созданных из веб-API. В следующем коде для
проверки настраивается использование SystemTextJsonValidationMetadataProvider
для использования имен свойств JSON:
C#
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options =>
{
options.ModelMetadataDetailsProviders.Add(new
SystemTextJsonValidationMetadataProvider());
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
В следующем коде для проверки настраивается использование
NewtonsoftJsonValidationMetadataProvider для использования имени свойства JSON
при применении Json.NET :
C#
using Microsoft.AspNetCore.Mvc.NewtonsoftJson;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options =>
{
options.ModelMetadataDetailsProviders.Add(new
NewtonsoftJsonValidationMetadataProvider());
}).AddNewtonsoftJson();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Дополнительные сведения см. в разделе Использование имен свойств JSON в
ошибках проверки.
Минимальные API
Фильтры в приложениях с минимальным API
Фильтры минимальных API позволяют разработчикам реализовать бизнес-логику,
которая поддерживает следующее:
Выполнение кода до и после запуска обработчика маршрутов.
Проверка и изменение параметров, предоставленных при вызове
обработчика маршрутов.
Перехват ответа обработчика маршрутов.
Фильтры могут быть полезны в следующих сценариях:
Проверка параметров и текста запросов, отправляемых в конечную точку.
Запись сведений о запросе и ответе в журнал.
Проверка того, что запрос предназначен для поддерживаемой версии API.
Дополнительные сведения см. в разделе Фильтры в минимальных приложениях
API.
Привязка массивов и строковых значений из
заголовков и строк запроса
В ASP.NET 7 поддерживается привязка строк запроса к массиву примитивных
типов, массивам строк и StringValues.
C#
// Bind query string values to a primitive type array.
// GET /tags?q=1&q=2&q=3
app.MapGet("/tags", (int[] q) =>
$"tag1: {q[0]} , tag2: {q[1]}, tag3: {q[2]}");
// Bind to a string array.
// GET /tags2?names=john&names=jack&names=jane
app.MapGet("/tags2", (string[] names) =>
$"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");
// Bind to StringValues.
// GET /tags3?names=john&names=jack&names=jane
app.MapGet("/tags3", (StringValues names) =>
$"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");
Привязка строк запроса или значений заголовков к массиву сложных типов
поддерживается, если для типа реализовать TryParse . Дополнительные сведения
см. в разделе Привязка массивов и строковых значений из заголовков и строк
запроса.
Дополнительные сведения см. в статье о добавлении сводок или описаний
конечных точек.
Привязка текста запроса в виде Stream или PipeReader
Тело запроса может привязываться как Stream или PipeReader для эффективной
поддержки сценариев, в которых пользователю необходимо обрабатывать данные
и:
Хранить данные в хранилище BLOB-объектов или поставить их в очередь у
поставщика очередей.
Обрабатывать хранимые данные с помощью рабочего процесса или
облачной функции.
Например, данные могут быть помещены в очередь в Хранилище очередей Azure
или храниться в Хранилище BLOB-объектов Azure.
Дополнительные сведения см. в статье Привязка текста запроса в качестве Stream
или PipeReader.
Новые перегрузки Results.Stream
Мы ввели новые перегрузки Results.Stream для сценариев, которым необходим
доступ к базовому потоку HTTP-ответов без буферизации. Эти перегрузки также
улучшают случаи, когда API передает данные в поток ответа HTTP, например из
Хранилища BLOB-объектов Azure. В следующем примере используется
ImageSharp
для возврата уменьшенного размера указанного изображения:
C#
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/process-image/{strImage}", (string strImage, HttpContext http,
CancellationToken token) =>
{
http.Response.Headers.CacheControl = $"public,max-age=
{TimeSpan.FromHours(24).TotalSeconds}";
return Results.Stream(stream => ResizeImageAsync(strImage, stream,
token), "image/jpeg");
});
async Task ResizeImageAsync(string strImage, Stream stream,
CancellationToken token)
{
var strPath = $"wwwroot/img/{strImage}";
using var image = await Image.LoadAsync(strPath, token);
int width = image.Width / 2;
int height = image.Height / 2;
image.Mutate(x =>x.Resize(width, height));
await image.SaveAsync(stream, JpegFormat.Instance, cancellationToken:
token);
}
Дополнительные сведения см. в разделе Примеры потоковой передачи.
Типизированные результаты для минимальных API
В .NET 6 появился интерфейс IResult, который представляет значения,
возвращаемые минимальными API, не использующими неявную поддержку JSON
для сериализации возвращенного объекта для ответов HTTP. Статический класс
Results используется для создания разных объектов IResult , которые представляют
разные типы ответов. Например, установка кода статуса ответа или
перенаправление на другой URL-адрес. Однако типы, реализующие IResult ,
возвращаемые этими методами, были внутренними, что затрудняло проверку
конкретного типа IResult , возвращаемого методами в модульном тесте.
В .NET 7 типы, реализующие IResult , являются общедоступными, что позволяет
использовать утверждения типов при тестировании. Пример:
C#
[TestClass()]
public class WeatherApiTests
{
[TestMethod()]
public void MapWeatherApiTest()
{
var result = WeatherApi.GetAllWeathers();
Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
}
}
Улучшена возможность модульного тестирования для
минимальных обработчиков маршрутов
IResult Типы реализации теперь общедоступны в
Microsoft.AspNetCore.Http.HttpResults пространстве имен. IResult Типы реализации
можно использовать для модульного тестирования минимальных обработчиков
маршрутов при использовании именованных методов вместо лямбда-выражений.
В следующем коде используется Ok<TValue> класс :
C#
[Fact]
public async Task GetTodoReturnsTodoFromDatabase()
{
// Arrange
await using var context = new MockDb().CreateDbContext();
context.Todos.Add(new Todo
{
Id = 1,
Title = "Test title",
Description = "Test description",
IsDone = false
});
await context.SaveChangesAsync();
// Act
var okResult = (Ok<Todo>)await TodoEndpointsV1.GetTodo(1, context);
//Assert
Assert.Equal(200, okResult.StatusCode);
var foundTodo = Assert.IsAssignableFrom<Todo>(okResult.Value);
Assert.Equal(1, foundTodo.Id);
}
Дополнительные сведения см. в разделе IResult Типы реализации.
Новые интерфейсы HttpResult
Следующие интерфейсы в Microsoft.AspNetCore.Http пространстве имен
предоставляют способ обнаружения IResult типа во время выполнения, что
является распространенным шаблоном в реализациях фильтров:
IContentTypeHttpResult
IFileHttpResult
INestedHttpResult
IStatusCodeHttpResult
IValueHttpResult
IValueHttpResult<TValue>
Дополнительные сведения см. в разделе Интерфейсы IHttpResult.
Улучшения OpenAPI для минимальных API
пакет NuGet Microsoft.AspNetCore.OpenApi ;
Пакет Microsoft.AspNetCore.OpenApi
позволяет взаимодействовать со
спецификациями OpenAPI для конечных точек. Этот пакет создает связь между
моделями OpenAPI, определенными в пакете Microsoft.AspNetCore.OpenApi , и
конечными точками, определенными в минимальных API. Этот пакет предоставляет
API, который проверяет параметры, ответы и метаданные конечной точки и
создает тип заметки OpenAPI, подходящий для описания этой конечной точки.
C#
app.MapPost("/todoitems/{id}", async (int id, Todo todo, TodoDb db) =>
{
todo.Id = id;
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
})
.WithOpenApi();
Вызов WithOpenApi с параметрами
Метод WithOpenApi
принимает функцию, которую можно использовать для
изменения заметки OpenAPI. Например, следующий код добавляет описание в
первый параметр конечной точки:
C#
app.MapPost("/todo2/{id}", async (int id, Todo todo, TodoDb db) =>
{
todo.Id = id;
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
})
.WithOpenApi(generatedOperation =>
{
var parameter = generatedOperation.Parameters[0];
parameter.Description = "The ID associated with the created Todo";
return generatedOperation;
});
Предоставление описаний и сводок конечных точек
Минимальные API-интерфейсы теперь поддерживают операции аннотирования с
описаниями и сводками для создания спецификаций OpenAPI. Можно вызывать
методы расширения WithDescription и WithSummary или использовать атрибуты
[EndpointDescription] и [EndpointSummary]).
Дополнительные сведения см. в статье OpenAPI в минимальных приложениях API.
Отправка файлов с помощью IFormFile и
IFormFileCollection
Минимальные API теперь поддерживают отправку файлов с применением
IFormFile и IFormFileCollection . Следующий код использует IFormFile и
IFormFileCollection, чтобы отправить файл:
C#
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.MapPost("/upload", async (IFormFile file) =>
{
var tempFile = Path.GetTempFileName();
app.Logger.LogInformation(tempFile);
using var stream = File.OpenWrite(tempFile);
await file.CopyToAsync(stream);
});
app.MapPost("/upload_many", async (IFormFileCollection myFiles) =>
{
foreach (var file in myFiles)
{
var tempFile = Path.GetTempFileName();
app.Logger.LogInformation(tempFile);
using var stream = File.OpenWrite(tempFile);
await file.CopyToAsync(stream);
}
});
app.Run();
Поддерживаются запросы на отправку файлов с проверкой подлинности
посредством заголовка авторизации , сертификата клиента или заголовка cookie.
Встроенная поддержка защиты от подделки отсутствует. Однако ее можно
реализовать с помощью службы IAntiforgery.
Атрибут [AsParameters] обеспечивает привязку
параметров для списков аргументов.
Атрибут[AsParameters] включает привязку параметров для списков аргументов.
Дополнительные сведения: Привязка параметров для списков аргументов с
помощью [AsParameters].
Минимальные API и контроллеры API
Новая служба сведений о проблеме
Служба сведений о проблеме IProblemDetailsService реализует интерфейс , который
поддерживает создание сведений о проблеме для API HTTP
.
Дополнительные сведения см. в разделе Служба сведений о проблеме.
Группы маршрутов
Метод MapGroup расширения помогает упорядочивать группы конечных точек с
общим префиксом. Это сокращает количество повторяющихся кодов и позволяет
настраивать целые группы конечных точек с помощью одного вызова таких
методов, как RequireAuthorization и WithMetadata , которые добавляют метаданные
конечной точки.
Например, следующий код создает две похожие группы конечных точек:
C#
app.MapGroup("/public/todos")
.MapTodosApi()
.WithTags("Public");
app.MapGroup("/private/todos")
.MapTodosApi()
.WithTags("Private")
.AddEndpointFilterFactory(QueryPrivateTodos)
.RequireAuthorization();
EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext
factoryContext, EndpointFilterDelegate next)
{
var dbContextIndex = -1;
foreach (var argument in factoryContext.MethodInfo.GetParameters())
{
if (argument.ParameterType == typeof(TodoDb))
{
dbContextIndex = argument.Position;
break;
}
}
// Skip filter if the method doesn't have a TodoDb parameter.
if (dbContextIndex < 0)
{
return next;
}
return async invocationContext =>
{
var dbContext = invocationContext.GetArgument<TodoDb>
(dbContextIndex);
dbContext.IsPrivate = true;
try
{
return await next(invocationContext);
}
finally
{
// This should only be relevant if you're pooling or otherwise
reusing the DbContext instance.
dbContext.IsPrivate = false;
}
};
}
C#
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
group.MapGet("/", GetAllTodos);
group.MapGet("/{id}", GetTodo);
group.MapPost("/", CreateTodo);
group.MapPut("/{id}", UpdateTodo);
group.MapDelete("/{id}", DeleteTodo);
return group;
}
В этом сценарии можно использовать относительный адрес для заголовка Location
в 201 Created результате:
C#
public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb
database)
{
await database.AddAsync(todo);
await database.SaveChangesAsync();
return TypedResults.Created($"{todo.Id}", todo);
}
Первая группа конечных точек будет соответствовать только запросам с
/public/todos префиксом и будет доступна без проверки подлинности. Вторая
группа конечных точек будет соответствовать только запросам с /private/todos
префиксом и требует проверки подлинности.
Фабрика QueryPrivateTodos фильтров конечных точек — это локальная функция,
которая изменяет параметры обработчика TodoDb маршрутов, чтобы разрешить
доступ к частным данным дел и их хранение.
Группы маршрутов также поддерживают вложенные группы и сложные шаблоны
префиксов с параметрами и ограничениями маршрута. В следующем примере и
обработчик маршрутов, сопоставленный с user группой, может записывать {org}
параметры маршрута и {group} , определенные в префиксах внешней группы.
Префикс также может быть пустым. Это может быть полезно для добавления
метаданных или фильтров конечной точки в группу конечных точек без изменения
шаблона маршрута.
C#
var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");
Добавление фильтров или метаданных в группу ведет себя так же, как и
добавление их по отдельности в каждую конечную точку перед добавлением
дополнительных фильтров или метаданных, которые могли быть добавлены во
внутреннюю группу или определенную конечную точку.
C#
var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");
inner.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/inner group filter");
return next(context);
});
outer.AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("/outer group filter");
return next(context);
});
inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
app.Logger.LogInformation("MapGet filter");
return next(context);
});
В приведенном выше примере внешний фильтр регистрирует входящий запрос до
внутреннего фильтра, даже если он был добавлен вторым. Так как фильтры были
применены к разным группам, порядок их добавления не имеет значения. Фильтры
порядка добавляются, если они применяются к той же группе или определенной
конечной точке.
Запрос к будет регистрировать /outer/inner/ следующие данные:
Интерфейс командной строки.NET
/outer group filter
/inner group filter
MapGet filter
gRPC
Перекодирование JSON
Перекодирование gRPC JSON — это расширение для ASP.NET Core, которое создает
REST API JSON для служб gRPC. Перекодирование gRPC JSON позволяет:
Приложениям вызывать службы gRPC, используя следующие привычные
понятия HTTP.
Приложениям gRPC ASP.NET Core поддерживать gRPC и API-интерфейсы
RESTful JSON без репликации функций.
Экспериментальная поддержка создания OpenAPI из перекодированных
RESTинтерфейсов API путем интеграции со Swashbuckle.
Дополнительные сведения см. в разделах Перекодирование gRPC JSON в ASP.NET
Core приложениях gRPC и Использование OpenAPI с перекодированием gRPC
JSON ASP.NET Core приложений.
Проверки работоспособности gRPC в ASP.NET Core
Протокол проверки работоспособности gRPC
является стандартом для передачи
сведений о работоспособности серверных приложений gRPC. Приложение
предоставляет проверки работоспособности как службу gRPC. Они обычно
используются с внешней службой мониторинга для проверки состояния
приложения.
в ASP.NET Core gRPC добавлена встроенная поддержка проверок
работоспособности gRPC с пакетомGrpc.AspNetCore.HealthChecks . Результаты
проверок работоспособности .NET передаются вызывающим объектам.
Дополнительные сведения см. в разделе Проверки работоспособности gRPC в
ASP.NET Core.
Улучшена поддержка учетных данных для звонков
Учетные данные вызова — это рекомендуемый способ настройки клиента gRPC для
отправки маркера проверки подлинности на сервер. Клиенты gRPC поддерживают
две новые функции, упрощают использование учетных данных для вызова:
Поддержка учетных данных вызова с подключениями в виде открытого
текста. Ранее вызов gRPC отправлял учетные данные вызова, только если
подключение было защищено с помощью TLS. Новый параметр в
GrpcChannelOptions с именем UnsafeUseInsecureChannelCallCredentials позволяет
настраивать такое поведение. Отсутствие защиты подключения с помощью
TLS влияет на безопасность.
В фабрике клиента gRPC доступен новый метод с именем AddCallCredentials .
AddCallCredentials — это быстрый способ настройки учетных данных вызова
для клиента gRPC и хорошо интегрируется с внедрением зависимостей (DI).
Следующий код настраивает фабрику клиента gRPC для отправки Authorization
метаданных:
C#
builder.Services
.AddGrpcClient<Greeter.GreeterClient>(o =>
{
o.Address = new Uri("https://localhost:5001");
})
.AddCallCredentials((context, metadata) =>
{
if (!string.IsNullOrEmpty(_token))
{
metadata.Add("Authorization", $"Bearer {_token}");
}
return Task.CompletedTask;
});
Дополнительные сведения см. в статье Настройка токена носителя с помощью
фабрики клиента gRPC.
SignalR
Получение результатов от клиента
Теперь сервер поддерживает запрос результатов от клиента. Для этого требуется,
чтобы сервер использовал ISingleClientProxy.InvokeAsync , а клиент возвращал
результат от своего обработчика .On . Строго типизированные концентраторы
также могут возвращать значения из методов интерфейса.
Дополнительные сведения см. в разделе Получение результатов от клиента.
Внедрение зависимостей для методов концентратора
SignalR
Методы концентратора SignalR теперь поддерживают внедрение служб
посредством внедрения зависимостей.
Конструкторы концентратора могут принимать службы от внедрения зависимостей
в качестве параметров, которые можно хранить в свойствах класса для
использования в методе концентратора. Дополнительные сведения см. в разделе
Внедрение служб в концентратор.
Blazor
Обработка событий изменения расположения и
состояния навигации
В .NET 7 Blazor поддерживает события изменения расположения и сохранение
состояния навигации. Это позволяет предупреждать пользователей о
несохраненных работах или выполнять связанные действия, когда пользователь
выполняет навигацию по страницам.
Дополнительные сведения см. в следующих разделах статьи Маршрутизация и
навигация :
Параметры навигации
Обработка и предотвращение изменений расположения
Пустые Blazor шаблоны проектов
Blazor имеет два новых шаблона проектов для запуска с пустого листа. Новые
шаблоны проектов App Empty и Blazor WebAssembly App Empty похожи на их
непустые аналоги, но без примера кода.Blazor Server Эти пустые шаблоны
содержат только базовую домашнюю страницу, и мы удалили начальную загрузку,
чтобы вы могли начать с другой платформы CSS.
Дополнительные сведения см. в следующих статьях:
Инструменты для ASP.NET CoreBlazor
Структура проекта ASP.NET Core Blazor
Пользовательские элементы Blazor
Пакет Microsoft.AspNetCore.Components.CustomElements
позволяет создавать
пользовательские элементы DOM на основе стандартов
с помощью Blazor.
Дополнительные сведения см. в статье Компоненты Razor ASP.NET Core.
Модификаторы привязки ( @bind:after , @bind:get ,
@bind:set )
) Важно!
В @bind:after // @bind:get @bind:set настоящее время функции получают
дополнительные обновления. Чтобы воспользоваться преимуществами
последних обновлений, убедитесь, что вы установили последнюю версию
пакета SDK.
Использование параметра обратного вызова события ( [Parameter] public
EventCallback<string> ValueChanged { get; set; } ) не поддерживается. Вместо
этого передайте Actionметод -returning или Task-returning
в/ @bind:set @bind:after .
Дополнительные сведения см. в следующих ресурсах:
Blazor@bind:after не работает в выпуске .NET 7 RTM (dotnet/aspnetcore
#44957)
BindGetSetAfter701 пример приложения (репозиторий GitHub
javiercn/BindGetSetAfter701)
В .NET 7 можно выполнять асинхронную логику после завершения события
привязки с помощью нового @bind:after модификатора. В следующем примере
PerformSearch асинхронный метод выполняется автоматически после обнаружения
любых изменений в тексте поиска:
razor
<input @bind="searchText" @bind:after="PerformSearch" />
@code {
private string searchText;
private async Task PerformSearch()
{
...
}
}
В .NET 7 также проще настроить привязку для параметров компонента.
Компоненты могут поддерживать двусторонние привязки данных, определяя пару
параметров:
@bind:get : задает значение для привязки.
@bind:set : задает обратный вызов при изменении значения.
Модификаторы @bind:get и @bind:set всегда используются вместе.
Примеры:
razor
@* Elements *@
<input type="text" @bind="text" @bind:after="() => { }" />
<input type="text" @bind:get="text" @bind:set="(value) => { }" />
<input type="text" @bind="text" @bind:after="AfterAsync" />
<input type="text" @bind:get="text" @bind:set="SetAsync" />
<input type="text" @bind="text" @bind:after="() => { }" />
<input type="text" @bind:get="text" @bind:set="(value) => { }" />
<input type="text" @bind="text" @bind:after="AfterAsync" />
<input type="text" @bind:get="text" @bind:set="SetAsync" />
@* Components *@
<InputText @bind-Value="text" @bind-Value:after="() => { }" />
<InputText @bind-Value:get="text" @bind-Value:set="(value) => { }" />
<InputText @bind-Value="text" @bind-Value:after="AfterAsync" />
<InputText @bind-Value:get="text" @bind-Value:set="SetAsync" />
<InputText @bind-Value="text" @bind-Value:after="() => { }" />
<InputText @bind-Value:get="text" @bind-Value:set="(value) => { }" />
<InputText @bind-Value="text" @bind-Value:after="AfterAsync" />
<InputText @bind-Value:get="text" @bind-Value:set="SetAsync" />
@code {
private string text = "";
private
private
private
private
void
void
Task
Task
After(){}
Set() {}
AfterAsync() { return Task.CompletedTask; }
SetAsync(string value) { return Task.CompletedTask; }
}
Дополнительные сведения о компоненте см. в InputText разделе ASP.NET Core
Blazor форм и входных компонентов.
улучшения Горячая перезагрузка
В .NET 7 Горячая перезагрузка поддержка включает следующее:
При удалении значения компоненты сбрасывают свои параметры до
значений по умолчанию.
Blazor WebAssembly:
Добавьте новые типы.
Добавление вложенных классов.
Добавьте статические методы и методы экземпляра в существующие типы.
Добавление статических полей и методов в существующие типы.
Добавление статических лямбда-выражений в существующие методы.
Добавьте лямбда-выражения, которые захватывают this существующие
методы, которые уже записаны this ранее.
Запросы динамической проверки подлинности с
помощью MSAL в Blazor WebAssembly
Новые возможности в .NET 7 Blazor WebAssembly поддерживают создание
динамических запросов проверки подлинности во время выполнения с
пользовательскими параметрами для обработки сценариев расширенной
проверки подлинности.
Дополнительные сведения см. в следующих статьях:
Защита ASP.NET Core Blazor WebAssembly
Сценарии обеспечения дополнительной безопасности ASP.NET Core Blazor
WebAssembly
Blazor WebAssembly улучшения отладки
Blazor WebAssembly Отладка имеет следующие улучшения:
Поддержка параметра Just My Code для отображения или скрытия элементов
типа, которые не входят в пользовательский код.
Поддержка проверки многомерных массивов.
Теперь в стеке вызовов отображается правильное имя для асинхронных
методов.
Улучшенная оценка выражений.
Правильная обработка ключевого слова в new производных членах.
Поддержка атрибутов, связанных с отладчиком, в System.Diagnostics .
System.Security.Cryptography поддержка в
WebAssembly
.NET 6 поддерживает семейство алгоритмов хэширования SHA при выполнении в
WebAssembly. .NET 7 обеспечивает больше алгоритмов шифрования, используя
преимущества SubtleCrypto , когда это возможно, и возвращаясь к реализации
.NET, когда SubtleCrypto ее нельзя использовать. Следующие алгоритмы
поддерживаются в WebAssembly в .NET 7:
SHA1
SHA256
SHA384
SHA512
HMACSHA1
HMACSHA256
HMACSHA384
HMACSHA512
AES-CBC
PBKDF2
HKDF
Дополнительные сведения см. в статье Разработчики, ориентированные на
browser-wasm, могут использовать API веб-шифрования (dotnet/runtime #40074).
Внедрение служб в настраиваемые атрибуты
проверки
Теперь вы можете внедрять службы в настраиваемые атрибуты проверки. Blazor
настраивает , ValidationContext чтобы его можно было использовать в качестве
поставщика услуг.
Дополнительные сведения см. в статье Формы и компоненты ввода в Blazor для
ASP.NET Core.
Input* компоненты за пределами EditContext / EditForm
Встроенные компоненты ввода теперь поддерживаются вне формы в Razor
разметке компонентов.
Дополнительные сведения см. в статье Формы и компоненты ввода в Blazor для
ASP.NET Core.
Изменения шаблона проекта
Когда в прошлом году была выпущена .NET 6, HTML-разметка _Host страницы
( Pages/_Host.chstml ) была разделена между страницей _Host и новой _Layout
страницей ( Pages/_Layout.chstml ) в шаблоне проекта .NET 6 Blazor Server .
В .NET 7 html-разметка повторно комбинирована со страницей _Host в шаблонах
проектов.
В шаблоны проектов было внесено несколько дополнительных Blazor изменений.
Невозможно перечислить все изменения в шаблонах в документации. Сведения о
переносе приложения в .NET 7 для внедрения всех изменений см. в статье
Миграция с ASP.NET Core 6.0 на 7.0.
Экспериментальный QuickGrid компонент
Новый QuickGrid компонент предоставляет удобный компонент сетки данных для
наиболее распространенных требований, а также в качестве эталонной
архитектуры и базовых показателей производительности для всех, кто создает
Blazor компоненты сетки данных.
Дополнительные сведения см. в статье Компоненты Razor ASP.NET Core.
Динамическая демонстрация: QuickGrid для Blazor примера приложения
Усовершенствования виртуализации
Усовершенствования виртуализации в .NET 7:
Компонент Virtualize поддерживает использование самого документа в
качестве корня прокрутки в качестве альтернативы применению какого-либо
другого элемента overflow-y: scroll .
Если компонент Virtualize помещается в элемент, которому требуется
определенное имя дочернего тега, SpacerElement разрешает получить или
задать имя тега разделителя в виртуализации.
Дополнительные сведения см. в следующих разделах статьи Виртуализация :
Виртуализация на корневом уровне
Управление именем тега элемента разделителя
MouseEventArgs Обновления
MovementX и MovementY добавлены в MouseEventArgs .
Дополнительные сведения см. в статье Обработка событий Blazor в ASP.NET Core.
Новая Blazor страница загрузки
Шаблон Blazor WebAssembly проекта имеет новый пользовательский интерфейс
загрузки, показывающий ход загрузки приложения.
Дополнительные сведения см. в статье Запуск ASP.NET Core Blazor.
Улучшенная диагностика для проверки подлинности в
Blazor WebAssembly
Для диагностики проблем с проверкой подлинности в Blazor WebAssembly
приложениях доступно подробное ведение журнала.
Дополнительные сведения см. Blazor в разделе ведение журнала ASP.NET Core.
Взаимодействие с JavaScript в WebAssembly
API взаимодействия JavaScript [JSImport] / [JSExport] — это новый низкоуровневый
механизм для использования .NET в Blazor WebAssembly приложениях на основе
JavaScript и . С помощью этой новой возможности взаимодействия JavaScript
можно вызывать код .NET из JavaScript с помощью среды выполнения .NET
WebAssembly и вызывать функции JavaScript из .NET без какой-либо зависимости от
Blazor модели компонентов пользовательского интерфейса.
Дополнительные сведения
Javascript JS Импорт иJS экспорт взаимодействия с ASP.NET CoreBlazor
WebAssembly: относится только к приложениямBlazor WebAssembly.
Запуск .NET из JavaScript: относится только к приложениям JavaScript, которые
не зависят от модели компонентов пользовательского Blazor интерфейса.
Условная регистрация поставщика состояния
проверки подлинности
До выпуска .NET 7 AuthenticationStateProvider был зарегистрирован в контейнере
службы с AddScoped помощью . Это усложняет отладку приложений, так как при
предоставлении пользовательской реализации требуется определенный порядок
регистрации служб. Из-за изменений внутренней платформы с течением времени
больше не нужно регистрировать AuthenticationStateProvider в AddScoped .
В коде разработчика внесите следующие изменения в регистрацию службы
поставщика состояния проверки подлинности:
diff
- builder.Services.AddScoped<AuthenticationStateProvider,
ExternalAuthStateProvider>();
+ builder.Services.TryAddScoped<AuthenticationStateProvider,
ExternalAuthStateProvider>();
В предыдущем примере ExternalAuthStateProvider — это реализация службы
разработчика.
Улучшения средств сборки .NET WebAssembly
Новые функции рабочей нагрузки wasm-tools для .NET 7, которые помогают
повысить производительность и обрабатывать исключения:
Поддержка webAssembly Single Instruction, Multiple Data (SIMD)
(только с
AOT, не поддерживается Apple Safari)
Поддержка обработки исключений WebAssembly
Дополнительные сведения см. в статье Инструментарий для ASP.NET Core Blazor.
Blazor Hybrid
Внешние URL-адреса
Добавлен параметр, позволяющий открывать внешние веб-страницы в браузере.
Дополнительные сведения см. в статье Маршрутизация ASP.NET Core Blazor Hybrid
и навигация.
Безопасность
Для сценариев безопасности доступно Blazor Hybrid новое руководство.
Дополнительные сведения см. в следующих статьях:
Проверка подлинности и авторизация в Blazor Hybrid ASP.NET Core
Вопросы обеспечения безопасности в ASP.NET Core Blazor Hybrid
Производительность
ПО промежуточного слоя для кэширования выходных
данных
Кэширование выходных данных — это новое ПО промежуточного слоя, которое
хранит ответы от веб-приложения и обслуживает их из кэша, а не вычисляет их
каждый раз. Кэширование выходных данных отличается от кэширования ответов
следующими способами:
Поведение кэширования настраивается на сервере.
Записи кэша можно программно сделать недействительными.
Блокировка ресурсов снижает риск штамповки кэша
и грома стада .
Повторная проверка кэша означает, что сервер может возвращать 304 Not
Modified код состояния HTTP вместо кэшированного текста ответа.
Среда хранения кэша является расширяемой.
Дополнительные сведения см. в разделе ОбзорПО промежуточного слоя для
кэширования и кэширования выходных данных.
Улучшения HTTP/3
Этот выпуск:
Обеспечивает полную поддержку HTTP/3 ASP.NET Core, она больше не
является экспериментальной.
Улучшена Kestrelподдержка HTTP/3. Двумя основными областями улучшения
являются четность функций с HTTP/1.1 и HTTP/2, а также производительность.
Обеспечивает полную поддержку UseHttps(ListenOptions, X509Certificate2) с
http/3. Kestrelпредлагает дополнительные параметры для настройки
сертификатов подключения, например подключение к указанию имени
сервера (SNI).
Добавлена поддержка HTTP/3 в HTTP.sys и IIS.
В следующем примере показано, как использовать обратный вызов SNI для
разрешения параметров TLS:
C#
using
using
using
using
Microsoft.AspNetCore.Server.Kestrel.Core;
Microsoft.AspNetCore.Server.Kestrel.Https;
System.Net.Security;
System.Security.Cryptography.X509Certificates;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenAnyIP(8080, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
listenOptions.UseHttps(new TlsHandshakeCallbackOptions
{
OnConnection = context =>
{
var options = new SslServerAuthenticationOptions
{
ServerCertificate =
MyResolveCertForHost(context.ClientHelloInfo.ServerName)
};
return new ValueTask<SslServerAuthenticationOptions>
(options);
},
});
});
});
В .NET 7 была проделана значительная работа по сокращению выделений HTTP/3.
Некоторые из этих улучшений можно увидеть в следующих запросах на
вытягивание GitHub:
HTTP/3. Избегайте выделения маркеров отмены для каждого запроса
HTTP/3: предотвращение выделения connectionAbortedException
HTTP/3: пул ValueTask
Улучшения производительности HTTP/2
.NET 7 представляет значительную переработанную архитектуру того, как Kestrel
обрабатывает запросы HTTP/2. В приложениях ASP.NET Core с загруженными
подключениями HTTP/2 будет наблюдаться снижение использования ЦП и
повышение пропускной способности.
Ранее реализация мультиплексирования HTTP/2 зависела от блокировки,
контролирующей то, какой запрос может выполнять запись в базовое
подключение TCP. Потокобезопасная очередь
заменяет блокировку записи.
Теперь вместо того, чтобы потоки конкурировали за использование блокировки
записи, запросы ставятся в очередь, и их обрабатывает выделенный потребитель.
Ранее потраченные ресурсы ЦП были доступными для остальной части
приложения.
Одним из мест, где можно заметить эти улучшения, является gRPC, популярная
платформа RPC, использующая HTTP/2. Показатели Kestrel + gRPC показывают
существенное улучшение:
В коде записи кадра HTTP/2 были внесены изменения, повышающие
производительность при наличии нескольких потоков, пытающихся записать
данные по одному подключению HTTP/2. Теперь мы отправим работу TLS в пул
потоков и быстрее освобождаем блокировку записи, которую другие потоки могут
получить для записи своих данных. Сокращение времени ожидания может
значительно повысить производительность в случаях, когда существует состязание
за эту блокировку записи. Тест производительности gRPC с 70 потоками по одному
подключению (с TLS) показал улучшение запросов в секунду (RPS) на 15 %
благодаря этому изменению.
Поддержка WebSockets по HTTP/2
В .NET 7 реализована поддержка Websocket через HTTP/2 для Kestrel, SignalR
клиента JavaScript и SignalR с Blazor WebAssembly.
При использовании соединений WebSocket по HTTP/2 доступны новые
возможности, в том числе следующие:
сжатие заголовка;
мультиплексирование, которое сокращает время и ресурсы, необходимые для
выполнения нескольких запросов к серверу.
Эти функции доступны в Kestrel на всех платформах с поддержкой HTTP/2.
Согласование версии выполняется в браузерах и Kestrel автоматически, поэтому
новые интерфейсы API не требуются.
Дополнительные сведения см. в разделе Поддержка Http/2 WebSocket.
Улучшенная производительность Kestrel на
компьютерах с большим количеством ядер
Kestrel использует ConcurrentQueue<T> для многих целей. Одна из них —
планирование операций ввода-вывода при транспортировке Kestrel на основе
сокетов по умолчанию. Секционирование ConcurrentQueue на основе связанного
сокета сокращает состязание и увеличивает пропускную способность на
компьютерах с большим количеством ядер ЦП.
Профилирование на компьютерах с большим количеством ядер на .NET 6
продемонстрировало значительный показатель состязания в одном из других
экземпляров ConcurrentQueue Kestrel, в пуле PinnedMemoryPool , который Kestrel
использует для кэширования буферов байтов.
В .NET 7 пул памяти Kestrel секционируется так же, как и его очередь ввода-вывода,
что значительно сокращает состязание и увеличивает пропускную способность на
компьютерах с большим количеством ядер. На виртуальных машинах ARM64 с
80 ядрами наблюдается повышение числа ответов в секунду (RPS) более чем на
500 % по результатам теста производительности TechEmpower с открытым текстом.
На виртуальных машинах AMD с 48 ядрами повышение составляет почти 100 % по
результатам теста производительности JSON для HTTPS.
Событие ServerReady для измерения времени запуска
Приложения, использующие EventSource, могут измерять время запуска, чтобы
определить и оптимизировать его производительность. Новое событие
ServerReady
в Microsoft.AspNetCore.Hosting представляет точку, в которой сервер
готов отвечать на запросы.
Сервер
Событие New ServerReady для измерения времени
запуска
Событие ServerReady
было добавлено для измерения времени запуска
приложений ASP.NET Core.
Службы IIS
Теневое копирование в IIS
Теневое копирование сборок приложений в модуль ASP.NET Core (ANCM) для IIS
может быть удобнее для пользователя, чем остановка приложения путем
развертывания автономного файла приложения.
Дополнительные сведения см. в разделе Теневое копирование в IIS.
Прочее
Kestrel полные улучшения цепочки сертификатов
HttpsConnectionAdapterOptions имеет новое свойство ServerCertificateChain типа
X509Certificate2Collection, которое упрощает проверку цепочек сертификатов,
позволяя указывать полную цепочку, включая промежуточные сертификаты.
Дополнительные сведения см. в разделе dotnet/aspnetcore#21513
.
dotnet watch
Улучшенные выходные данные консоли для dotnet watch
Выходные данные консоли из dotnet watch были улучшены, чтобы соответствовать
ведению журнала в ASP.NET Core, и дополнены 😮эмодзи😍.
Вот пример того, как выглядят новые выходные данные:
Дополнительные сведения см. в этом запросе на вытягивание на GitHub .
Настройка dotnet watch для перезапуска при грубых
изменениях
Грубые изменения — это изменения, которые не подлежат горячей перезагрузке.
Чтобы настроить dotnet watch для перезапуска без запроса в случае грубых
изменений, задайте для переменной среды DOTNET_WATCH_RESTART_ON_RUDE_EDIT
значение true .
Темный режим страницы исключений для
разработчиков
Поддержка темного режима была добавлена на страницу исключений для
разработчиков благодаря участию Патрика Вестерхоффа
(Patrick Westerhoff).
Чтобы протестировать темный режим в браузере, на странице инструментов
разработчика установите темный режим. Например, в Firefox:
В Chrome:
Параметр шаблона проекта для использования
метода Program.Main вместо инструкций верхнего
уровня
В шаблонах .NET 7 можно не использовать инструкции верхнего уровня и не
создавать namespace и метод Main , объявенный в классе Program .
С помощью .NET CLI задайте параметр --use-program-main :
Интерфейс командной строки.NET
dotnet new web --use-program-main
В Visual Studio установите флажок Не использовать инструкции верхнего уровня
при создании проекта:
Обновленные шаблоны Angular и React
Шаблон проекта Angular обновлен до Angular 14. Шаблон проекта React обновлен
до React 18.2.
Управление токенами JSON Web Token при разработке
с использованием dotnet user-jwts
Новое средство командной строки dotnet user-jwts позволяет создавать
локальные токены JSON Web Token
(JWT) и управлять ими. Дополнительные
сведения см. в статье Управление токенами JSON Web Token при разработке с
использованием dotnet user-jwts.
Поддержка дополнительных заголовков запросов в
W3CLogger
Теперь вы можете указывать дополнительные заголовки запросов для регистрации
при использовании средства ведения журнала W3C. Для этого вызовите
AdditionalRequestHeaders() для класса W3CLoggerOptions:
C#
services.AddW3CLogging(logging =>
{
logging.AdditionalRequestHeaders.Add("x-forwarded-for");
logging.AdditionalRequestHeaders.Add("x-client-ssl-protocol");
});
Дополнительные сведения см. в разделе Параметры W3CLogger.
Распаковка запросов
Новое ПО промежуточного слоя для распаковки запросов предоставляет такие
преимущества:
позволяет конечным точкам API принимать запросы со сжатым содержимым;
использует заголовок HTTP Content-Encoding , чтобы автоматически
обнаруживать и распаковывать запросы, содержащие сжатое содержимое;
устраняет необходимость писать код для обработки сжатых запросов.
Дополнительные сведения см. в разделе ПО промежуточного слоя для распаковки
запросов.
Новые возможности в ASP.NET
Core 6.0
Статья • 28.01.2023 • Чтение занимает 27 мин
В этой статье описываются наиболее важные изменения в ASP.NET Core 6.0 со
ссылками на соответствующую документацию.
Улучшения ASP.NET Core MVC и Razor
Минимальные API
Архитектура минимальных API позволяет создавать API для HTTP с минимальным
числом зависимостей. Они идеально подходят для микрослужб и приложений,
которым нужен небольшой набор файлов, компонентов и зависимостей на
платформе ASP.NET Core. Дополнительные сведения см. в разделе:
Руководство. Создание минимального API с помощью ASP.NET Core
Различия между минимальными API и обычными API с контроллерами
Краткий справочник по минимальным API
Примеры кода перенесены на новую минимальную модель размещения в 6.0
SignalR
Тег длительно выполняемого действия для
подключений SignalR
SignalR использует новый объект
Microsoft.AspNetCore.Http.Features.IHttpActivityFeature.Activity для добавления тега
http.long_running к действию запроса. IHttpActivityFeature.Activity используется
службами APM
, например Azure Monitor Application Insights для фильтрации
запросов SignalR на создание предупреждений о длительных запросах.
Улучшения производительности SignalR
Распределить HubCallerClients
вызова метода концентратора.
один раз на соединение вместо каждого
Устранено выделение закрытия в SignalR DefaultHubDispatcher.Invoke .
Состояние передается в локальную статическую функцию через параметры,
чтобы избежать выделения закрытия. Дополнительные сведения см. в этом
запросе на вытягивание на GitHub
.
Выделение одного StreamItemMessage на поток, а не на каждый элемент
потока в потоковой передаче с сервера клиенту. Дополнительные сведения
см. в этом запросе на вытягивание на GitHub .
Компилятор Razor
Компилятор Razor обновлен для использования
генераторов исходного кода
Теперь компилятор Razor основан на генераторах исходного кода C#. Генераторы
исходного кода запускаются во время компиляции и проверяют, что
компилируется для создания дополнительных файлов, компилируемых вместе с
остальной частью проекта. Использование генераторов исходного кода упрощает
компилятор Razor и значительно сокращает время сборки.
Компилятор Razor больше не создает отдельную
сборку Views
В компиляторе Razor ранее использовался двухэтапный процесс компиляции, при
котором создавалась отдельная сборка Views, содержащая созданные
представления и страницы (файлы .cshtml ), определенные в приложении.
Созданные типы были общедоступными и находились в пространстве имен
AspNetCore .
Обновленный компилятор Razor создает представления и типы страниц в главной
сборке проекта. Эти типы теперь по умолчанию создаются как внутренние
запечатанные в пространстве имен AspNetCoreGeneratedDocument . Это изменение
улучшает производительность сборки, позволяет развертывать один файл и
позволяет этим типам участвовать в Горячей перезагрузке.
Дополнительные сведения об этом изменении см. в этой статье об ошибке
GitHub.
на
улучшения производительности ASP.NET
Core и API
Внесено множество изменений для уменьшения количества выделений и
повышения производительности в стеке:
Метод расширения app.Use без выделения. Новая перегрузка метода app.Use
требует передачи контекста в next , который сохраняет два внутренних
выделения памяти для каждого запроса, которые трубеются при другой
перегрузке.
Уменьшение количества распределений памяти при получении доступа к
HttpRequest.Cookies. Дополнительные сведения см. в этой статье об ошибке на
GitHub .
Используйте LoggerMessage.Define только для веб-сервера HTTP.sys Windows.
Вызовы методов расширения ILogger были заменены вызовами
LoggerMessage.Define .
Снизить накладные расходы на каждое соединение в SocketConnection
на
~30 %. Дополнительные сведения см. в этом запросе на вытягивание на
GitHub .
Сокращено количество выделений за счет удаления делегатов ведения
журналов в универсальных типах. Дополнительные сведения см. в этом
запросе на вытягивание на GitHub
.
Более быстрый доступ GET (примерно на 50% быстрее) к часто используемым
функциям, включая IHttpRequestFeature, IHttpResponseFeature,
IHttpResponseBodyFeature, IRouteValuesFeature и IEndpointFeature.
Дополнительные сведения см. в этом запросе на вытягивание на GitHub .
Использование строк одного экземпляра для известных имен заголовков,
даже если они не находятся в сохраненном блоке заголовка. Использование
строки с одним экземпляром помогает предотвратить несколько дубликатов
одной строки в долгосрочных соединениях, например в
Microsoft.AspNetCore.WebSockets. Дополнительные сведения см. в этой статье
об ошибке на GitHub .
Повторно используйте HttpProtocol CancellationTokenSource
в Kestrel.
Используйте новый метод CancellationTokenSource.TryReset для
CancellationTokenSource , чтобы повторно использовать токены, если они не
были отменены. Дополнительные сведения см. в описании этой ошибки
на
GitHub и в этом видео .
Реализуйте и используйте AdaptiveCapacityDictionary
Microsoft.AspNetCore.HttpЗапросCookieКоллекция
в пункте
для более эффективного
доступа к словарям. Дополнительные сведения см. в этом запросе на
вытягивание на GitHub
.
Уменьшение объема занимаемой памяти для
бездействующих подключений TLS
Для длительных подключений TLS с редко передаваемыми данными мы
значительно уменьшили объем памяти, занимаемый приложениями ASP.NET Core
в .NET 6. Это поможет улучшить масштабируемость таких сценариев, как серверы
WebSocket. Это стало возможно благодаря многочисленным улучшениям в
System.IO.Pipelines, SslStream и Kestrel. В следующих разделах подробно описаны
некоторые улучшения, которые привели к уменьшению объема занимаемой
памяти.
Уменьшение размера System.IO.Pipelines.Pipe
Для каждого установленного соединения в Kestrel выделяются два канала:
транспортный уровень к приложению для запроса;
уровень приложения к транспортировке для ответа.
За счет уменьшения размера System.IO.Pipelines.Pipe с 368 байт до 264 байт
(примерно на 28,2%) обеспечивается экономия 208 байт на подключение (104 байт
на канал).
SocketSender пула
Объекты SocketSender (подкласс SocketAsyncEventArgs) имеют размер около 350
байт во время выполнения. Вместо выделения нового объекта SocketSender для
каждого соединения их можно поместить в пул. Объекты SocketSender можно
поместить в пул, так как отправка обычно выполняется очень быстро.
Использование пулов сокращает затраты на подключение. Вместо выделения 350
байт на одно соединение, выделяется только 350 байт на IOQueue . Выделение
выполняется по очереди во избежание конкуренции. На нашем сервер WebSocket
с 5000 неактивными подключениями вместо выделения 1,75 МБ (350 байт * 5000)
выделяется 2,8 КБ (350 байт * 8) для объектов SocketSender .
Операции чтения нуля байтов с SslStream
Операции чтения без буферизации — это методика, применяемая в ASP.NET Core,
чтобы избежать аренды памяти из пула памяти, если на сокете нет доступных
данных. До этого изменения наш сервер WebSocket с 5000 неактивными
подключениями требовал 200 МБ без TLS по сравнению с 800 МБ с TLS. Некоторые
из этих выделений (4 КБ на подключение) были связаны с необходимостью Kestrel
хранить ArrayPool<T> в буфере во время ожидания завершения операций чтения
SslStream. Учитывая, что эти соединения были неактивными, ни одна из операций
чтения не завершилась и не вернула буфер в ArrayPool , из-за чего ArrayPool
нужно было выделять больше памяти. Оставшиеся выделения памяти были в
самом SslStream : 4 КБ для подтверждений TLS и буфер 32 КБ для обычных
операций чтения. В .NET 6, когда пользователь выполняет чтение нуля байтов из
SslStream и не имеет доступных данных, SslStream внутренне выполняет чтение
нуля байтов в базовом потоке. В лучшем случае (неактивное подключение) эти
изменения приводят к экономии 40 КБ на подключение, одновременно позволяя
объекту-получателю (Kestrel) получать уведомления о доступности данных без
удержания неиспользуемых буферов.
Операции чтения нуля байтов с PipeReader
Поскольку для SslStream теперь поддерживаются операции чтения без
буферизации, была добавлена возможность выполнять операции чтения нуля
байтов в StreamPipeReader , внутреннем типе, который адаптирует Stream в
PipeReader . В Kestrel StreamPipeReader используется для адаптации базового
SslStream в PipeReader . Поэтому было необходимо предоставить эту семантику
чтения нуля байтов в PipeReader .
Теперь можно создать объект PipeReader , который поддерживает чтение нуля байт
для любого используемого потока Stream , поддерживающего соответствующую
семантику (например, SslStream , NetworkStream и др.). Для создания используйте
следующий API:
Интерфейс командной строки.NET
var reader = PipeReader.Create(stream, new
StreamPipeReaderOptions(useZeroByteReads: true));
Удаление slab из SlabMemoryPool
Чтобы уменьшить фрагментацию кучи, в Kestrel применялась методика выделения
slab памяти размером 128 КБ в составе пула памяти. Затем эти slab делились на
блоки размером 4 КБ, которые использовались внутри Kestrel. Требовался размер
этих slab больше 85 КБ, чтобы принудительно выделить память в куче больших
объектов и предотвратить перемещение этого массива в ходе сборки мусора.
Однако с появлением нового поколения сборки мусора, кучи закрепленных
объектов
, больше не имеет смысла распределять блоки на slab. Kestrel теперь
напрямую выделяет блоки на куче закрепленных объектов, уменьшая сложность,
связанную с управлением пулом памяти. Это изменение должно упростить
выполнение будущих улучшений, например упростить уменьшение пула памяти,
используемого Kestrel.
Поддержка IAsyncDisposable
Теперь контроллеры, Razor Pages и компоненты представления могут использовать
IAsyncDisposable. К соответствующим интерфейсам в фабриках и активаторах были
добавлены асинхронные версии:
Новые методы предлагают реализацию интерфейса по умолчанию, которая
делегирует синхронную версию и вызывает Dispose.
Реализации переопределяют реализацию по умолчанию и обрабатывают
реализации уничтожения IAsyncDisposable .
Если реализован IAsyncDisposable и IDisposable , реализации предпочитают
первый интерфейс.
Расширители должны переопределять новые методы, включаемые для
поддержки экземпляров IAsyncDisposable .
IAsyncDisposable полезно использовать при работе с:
асинхронными перечислителями, например в асинхронных потоках;
неуправляемыми ресурсами, имеющими ресурсоемкие операции вводавывода, требующие освобождения ресурсов.
При реализации этого интерфейса используйте метод DisposeAsync для
освобождения ресурсов.
Рассмотрим контроллер, создающий и использующий Utf8JsonWriter.
Utf8JsonWriter является ресурсом IAsyncDisposable :
C#
public class HomeController : Controller, IAsyncDisposable
{
private Utf8JsonWriter? _jsonWriter;
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
_jsonWriter = new Utf8JsonWriter(new MemoryStream());
}
IAsyncDisposable должен реализовывать DisposeAsync :
C#
public async ValueTask DisposeAsync()
{
if (_jsonWriter is not null)
{
await _jsonWriter.DisposeAsync();
}
_jsonWriter = null;
}
Порт Vcpkg для клиента C++ SignalR
Vcpkg
— это кроссплатформенный диспетчер пакетов командной строки для
библиотек C и C++. Мы недавно добавили порт в vcpkg , чтобы обеспечить
нативную поддержку CMake для клиента C++ SignalR. vcpkg также работает с
MSBuild.
Клиент SignalR можно добавить в проект CMake с помощью следующего
фрагмента кода, если vcpkg включен в файл цепочки инструментов:
Интерфейс командной строки.NET
find_package(microsoft-signalr CONFIG REQUIRED)
link_libraries(microsoft-signalr::microsoft-signalr)
С помощью предыдущего фрагмента SignalRклиент C++ готов к использованию
#include
и используется в проекте без дополнительной конфигурации. Полный
пример приложения на C++, использующего SignalRклиент C++, можно найти в
репозитории halter73/SignalR-Client-Cpp-Sample
Blazor
Изменения шаблона проекта
.
Для приложений Blazor внесено несколько изменений в шаблон проекта, в
частности, файл Pages/_Layout.cshtml использовался для компоновки
содержимого, которое в более ранних приложениях Blazor Server отображалось в
файле _Host.cshtml . Изучите изменения, создав приложение из шаблона
проекта 6.0 или обратившись к справочному источнику ASP.NET Core по шаблонам
проектов:
Blazor Server
Blazor WebAssembly
Поддержка зависимостей в машинном коде Blazor
WebAssembly
Приложения Blazor WebAssembly могут использовать зависимости в машинном
коде, созданные для выполнения в WebAssembly. Дополнительные сведения. см. в
статье Собственные зависимости Blazor WebAssembly ASP.NET Core.
Компиляция AOT и повторная компоновка во время
выполнения WebAssembly
Blazor WebAssembly поддерживает компиляцию в режиме Ahead Of Time (AOT), в
котором код .NET можно скомпилировать непосредственно в WebAssembly.
Компиляция AOT позволяет повысить производительность среды выполнения за
счет увеличения размера приложения. Повторная компоновка среды выполнения
.NET WebAssembly обрезает неиспользуемый код среды выполнения и тем самым
повышает скорость загрузки. Дополнительные сведения см. в разделах Компиляция
AOT и повторная компоновка.
Сохранение предварительно отрисованного состояния
Blazor поддерживает сохранение состояния на предварительно отображенной
странице, чтобы не нужно было повторно создавать состояние при полной
загрузке приложения. Дополнительные сведения см. в статье Компоненты Razor
для предварительной визуализации и интеграции ASP.NET Core.
Границы ошибок
Границы ошибок предоставляют удобный подход к обработке исключений на
уровне пользовательского интерфейса. Дополнительные сведения см. в статье
Обработка ошибок в приложениях Blazor ASP.NET Core.
Поддержка SVG
Элемент <foreignObject>
поддерживается для отображения внутри SVG
произвольного HTML-кода. Дополнительные сведения см. в статье Компоненты
Razor ASP.NET Core.
Поддержка Blazor Server для передачи массива байтов
во взаимодействии с JS
Blazor поддерживает оптимизированное взаимодействие с JS через массивы
байтов, что позволяет избежать кодирования и декодирования массивов байтов в
Base64. Дополнительные сведения см. в следующих ресурсах:
Вызов функций JavaScript из методов .NET в ASP.NET Core Blazor
Вызов методов .NET из функций JavaScript в ASP.NET Core Blazor
Усовершенствования строк запросов
Улучшена поддержка работы со строками запросов. Дополнительные сведения см.
в статье Маршрутизация ASP.NET Core Blazor и навигация.
Привязка для выбора нескольких элементов
Привязка поддерживает выбор нескольких параметров с элементами <input> .
Дополнительные сведения см. в следующих ресурсах:
Привязка к данным Blazor в ASP.NET Core
Формы и компоненты ввода в Blazor для ASP.NET Core
Управление содержимым элемента <head>
Компоненты Razor могут изменять содержимое элемента HTML <head> страницы, в
том числе задавать заголовок страницы (элемент <title> ) и изменять метаданные
(элементы <meta> ). Дополнительные сведения см. в разделе Управление
содержимым <head> в приложениях Blazor ASP.NET Core.
Создание компонентов Angular и React
Создавать компоненты JavaScript, характерные для платформы, можно из
компонентов Razor для веб-платформ, таких как Angular или React.
Дополнительные сведения см. в статье Компоненты Razor ASP.NET Core.
Отрисовка компонентов из JavaScript
Динамическая отрисовка компонентов Razor из JavaScript для существующих
приложений JavaScript. Дополнительные сведения см. в статье Компоненты Razor
ASP.NET Core.
Пользовательские элементы
Доступна экспериментальная поддержка для создания пользовательских
элементов, использующих стандартные интерфейсы HTML. Дополнительные
сведения см. в статье Компоненты Razor ASP.NET Core.
Выведение универсальных типов компонентов из
компонентов-предков
Компонент-предок может каскадировать параметр типа по имени и отображать
потомки с помощью нового атрибута [CascadingTypeParameter] . Дополнительные
сведения см. в статье Компоненты Razor ASP.NET Core.
Динамически отображаемые компоненты
Используйте новый встроенный компонент DynamicComponent для отображения
компонентов по типу. Дополнительные сведения см. в разделе Динамически
отображаемые компоненты Razor ASP.NET Core.
Улучшенная доступность Blazor
Используйте новый компонент FocusOnNavigate , чтобы установить фокус
пользовательского интерфейса на элемент на основе селектора CSS после
перехода с одной страницы на другую. Дополнительные сведения см. в статье
Маршрутизация ASP.NET Core Blazor и навигация.
Поддержка аргументов пользовательских событий
Blazor поддерживает аргументы пользовательских событий, которые позволяют
передавать произвольные данные в обработчики событий .NET с
пользовательскими событиями. Дополнительные сведения см. в статье Обработка
событий Blazor в ASP.NET Core.
Необходимые параметры
Примените новый атрибут [EditorRequired] , чтобы указать обязательный параметр
компонента. Дополнительные сведения см. в статье Компоненты Razor ASP.NET
Core.
Совместное размещение файлов JavaScript со
страницами, представлениями и компонентами
Размещайте совместно файлы JavaScript для страниц, представлений и
компонентов Razor — это удобный способ организации скриптов в приложении.
Дополнительные сведения см. в разделе Взаимодействие JavaScript приложения
ASP.NET Core (взаимодействие JS).
Инициализаторы JavaScript
Инициализаторы JavaScript выполняют логику до и после загрузки приложения
Blazor. Дополнительные сведения см. в разделе Взаимодействие JavaScript
приложения ASP.NET Core (взаимодействие JS).
Потоковая передача при взаимодействии с JavaScript
Blazor теперь поддерживает потоковую передачу данных между .NET и JavaScript.
Дополнительные сведения см. в следующих ресурсах:
Потоковая передача из .NET в JavaScript
Потоковая передача из JavaScript в .NET
Ограничения универсального типа
Теперь поддерживаются параметры универсального типа. Дополнительные
сведения см. в статье Компоненты Razor ASP.NET Core.
Макет развертывания WebAssembly
Используйте макет развертывания, чтобы включить загрузку приложений Blazor
WebAssembly в ограниченных средах безопасности. Дополнительные сведения см.
в разделе Макет развертывания для приложений Blazor WebAssembly ASP.NET Core.
Новые статьи о Blazor
В дополнение к функциям Blazor, описанным в предыдущих разделах, доступны
новые статьи о Blazor по следующим темам:
Загрузки файлов Blazor ASP.NET Core: узнайте, как скачать файл, используя
собственное взаимодействие byte[] для потоковой передачи, чтобы
обеспечить эффективную передачу данных клиенту.
Работа с изображениями в Blazor ASP.NET Core: узнайте, как работать с
изображениями в приложениях Blazor, включая способ потоковой передачи
данных изображения и предварительный просмотр изображения.
Создание приложений Blazor Hybrid с
помощью .NET MAUI, WPF и Windows Forms
Используйте Blazor Hybrid для объединения настольных и мобильных собственных
клиентских платформ с .NET и Blazor:
.NET Multi-platform App UI (.NET MAUI) представляет собой кроссплатформенную платформу для создания собственных мобильных и
классических приложений с помощью C# и XAML.
Приложения Blazor Hybrid можно создавать с помощью платформ Windows
Presentation Foundation (WPF) и Windows Forms.
) Важно!
Платформа Blazor Hybrid находится на этапе предварительной версии и не
должна использоваться в рабочих приложениях до финального выпуска.
Дополнительные сведения см. в следующих ресурсах:
Ознакомьтесь с документацией по ASP.NET Core Blazor Hybrid
Что такое .NET MAUI?
Блог Microsoft .NET (категория: ".NET MAUI")
Kestrel
HTTP/3
в настоящее время находится в разработке, поэтому подлежит
изменению. Поддержка HTTP/3 в ASP.NET Core не выпущена — это
предварительная версия функции, включенная в .NET 6.
Kestrel теперь поддерживает HTTP/3. Дополнительные сведения см. в разделе
Использование протокола HTTP/3 с веб-сервером Kestrel ASP.NET Core и запись в
блоге Поддержка протокола HTTP/3 в .NET 6
.
Новые категории ведения журнала Kestrel для
выборочного ведения журнала
До этого изменения включение подробного ведения журнала для Kestrel было
чрезмерно дорогостоящим, так как у всего Kestrel был общий доступ к имени
категории ведения журнала Microsoft.AspNetCore.Server.Kestrel .
Microsoft.AspNetCore.Server.Kestrel по-прежнему можно использовать, но
следующие новые подкатегории обеспечивают более полный контроль ведения
журнала:
Microsoft.AspNetCore.Server.Kestrel (текущая категория): ApplicationError ,
ConnectionHeadResponseBodyWrite , ApplicationNeverCompleted , RequestBodyStart ,
RequestBodyDone , RequestBodyNotEntirelyRead , RequestBodyDrainTimedOut ,
ResponseMinimumDataRateNotSatisfied , InvalidResponseHeaderRemoved ,
HeartbeatSlow .
Microsoft.AspNetCore.Server.Kestrel.BadRequests : ConnectionBadRequest ,
RequestProcessingError , RequestBodyMinimumDataRateNotSatisfied .
Microsoft.AspNetCore.Server.Kestrel.Connections : ConnectionAccepted ,
ConnectionStart , ConnectionStop , ConnectionPause , ConnectionResume ,
ConnectionKeepAlive , ConnectionRejected , ConnectionDisconnect ,
NotAllConnectionsClosedGracefully , NotAllConnectionsAborted ,
ApplicationAbortedConnection .
Microsoft.AspNetCore.Server.Kestrel.Http2 : Http2ConnectionError ,
Http2ConnectionClosing , Http2ConnectionClosed , Http2StreamError ,
Http2StreamResetAbort , HPackDecodingError , HPackEncodingError ,
Http2FrameReceived , Http2FrameSending , Http2MaxConcurrentStreamsReached .
Microsoft.AspNetCore.Server.Kestrel.Http3 : Http3ConnectionError ,
Http3ConnectionClosing , Http3ConnectionClosed , Http3StreamAbort ,
Http3FrameReceived , Http3FrameSending .
Существующие правила продолжают работать, но теперь можно более
избирательно включать правила. Например, можно значительно сократить
издержки на наблюдения, если включить ведение журнала Debug только для
недопустимых запросов. Для этого используется следующая конфигурация:
XML
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.Kestrel.BadRequests": "Debug"
}
}
Фильтрация журналов применяет правила с наиболее длинным соответствующим
префиксом категории. Дополнительные сведения см. в разделе Применение
правил фильтрации.
Создание события KestrelServerOptions через
EventSource
Источник KestrelEventSource
создает новое событие, содержащее
сериализованный JSON-файл KestrelServerOptions, если он включен с уровнем
детализации EventLevel.LogAlways . Это событие упрощает работу с поведением
сервера при анализе собранных трассировок. Ниже приведен код JSON с
примером полезных данных события:
JSON
{
"AllowSynchronousIO": false,
"AddServerHeader": true,
"AllowAlternateSchemes": false,
"AllowResponseHeaderCompression": true,
"EnableAltSvc": false,
"IsDevCertLoaded": true,
"RequestHeaderEncodingSelector": "default",
"ResponseHeaderEncodingSelector": "default",
"Limits": {
"KeepAliveTimeout": "00:02:10",
"MaxConcurrentConnections": null,
"MaxConcurrentUpgradedConnections": null,
"MaxRequestBodySize": 30000000,
"MaxRequestBufferSize": 1048576,
"MaxRequestHeaderCount": 100,
"MaxRequestHeadersTotalSize": 32768,
"MaxRequestLineSize": 8192,
"MaxResponseBufferSize": 65536,
"MinRequestBodyDataRate": "Bytes per second: 240, Grace Period:
00:00:05",
"MinResponseDataRate": "Bytes per second: 240, Grace Period: 00:00:05",
"RequestHeadersTimeout": "00:00:30",
"Http2": {
"MaxStreamsPerConnection": 100,
"HeaderTableSize": 4096,
"MaxFrameSize": 16384,
"MaxRequestHeaderFieldSize": 16384,
"InitialConnectionWindowSize": 131072,
"InitialStreamWindowSize": 98304,
"KeepAlivePingDelay": "10675199.02:48:05.4775807",
"KeepAlivePingTimeout": "00:00:20"
},
"Http3": {
"HeaderTableSize": 0,
"MaxRequestHeaderFieldSize": 16384
}
},
"ListenOptions": [
{
"Address": "https://127.0.0.1:7030",
"IsTls": true,
"Protocols": "Http1AndHttp2"
},
{
"Address": "https://[::1]:7030",
"IsTls": true,
"Protocols": "Http1AndHttp2"
},
{
"Address": "http://127.0.0.1:5030",
"IsTls": false,
"Protocols": "Http1AndHttp2"
},
{
"Address": "http://[::1]:5030",
"IsTls": false,
"Protocols": "Http1AndHttp2"
}
]
}
Новое событие DiagnosticSource для отклоненных
HTTP-запросов
Kestrel теперь создает новое событие DiagnosticSource для HTTP-запросов,
отклоненных на уровне сервера. До этого изменения возможности наблюдать за
этими отклоненными запросами не было. Новое событие
DiagnosticSource Microsoft.AspNetCore.Server.Kestrel.BadRequest содержит объект
IBadRequestExceptionFeature, который можно использовать для анализа причины
отклонения запроса.
C#
using Microsoft.AspNetCore.Http.Features;
using System.Diagnostics;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var diagnosticSource = app.Services.GetRequiredService<DiagnosticListener>
();
using var badRequestListener = new BadRequestEventListener(diagnosticSource,
(badRequestExceptionFeature) =>
{
app.Logger.LogError(badRequestExceptionFeature.Error, "Bad request
received");
});
app.MapGet("/", () => "Hello world");
app.Run();
class BadRequestEventListener : IObserver<KeyValuePair<string, object>>,
IDisposable
{
private readonly IDisposable _subscription;
private readonly Action<IBadRequestExceptionFeature> _callback;
public BadRequestEventListener(DiagnosticListener diagnosticListener,
Action<IBadRequestExceptionFeature>
callback)
{
_subscription = diagnosticListener.Subscribe(this!, IsEnabled);
_callback = callback;
}
private static readonly Predicate<string> IsEnabled = (provider) =>
provider switch
{
"Microsoft.AspNetCore.Server.Kestrel.BadRequest" => true,
_ => false
};
public void OnNext(KeyValuePair<string, object> pair)
{
if (pair.Value is IFeatureCollection featureCollection)
{
var badRequestFeature =
featureCollection.Get<IBadRequestExceptionFeature>();
if (badRequestFeature is not null)
{
_callback(badRequestFeature);
}
}
}
public void OnError(Exception error) { }
public void OnCompleted() { }
public virtual void Dispose() => _subscription.Dispose();
}
Дополнительные сведения см. в разделе Ведение журнала и диагностика в Kestrel.
Создание ConnectionContext на основе принятого
приема
SocketConnectionContextFactory позволяет создать ConnectionContext на основе
принятого сокета. Это позволяет создавать пользовательские
IConnectionListenerFactory на основе сокетов, не теряя при этом
производительность и организацию пулов в SocketConnection .
См. пример пользовательского IConnectionListenerFactory
, в котором показано,
как использовать SocketConnectionContextFactory .
Kestrel является профилем запуска по умолчанию для
Visual Studio
Профиль запуска по умолчанию для всех новых веб-проектов dotnet — Kestrel.
Запуск Kestrel значительно ускоряется, что повышает удобство при разработке
приложений.
IIS Express по-прежнему можно использовать в качестве профиля запуска для таких
сценариев, как проверка подлинности Windows или общий доступ к портам.
Порты localhost для Kestrel являются случайными
Дополнительные сведения см. в разделе Шаблон генерируемых портов для Kestrel
в этом документе.
Проверка подлинности и авторизация
Серверы проверки подлинности
В версиях .NET от 3 до 5 использовался IdentityServer4
как часть шаблона для
поддержки выдачи маркеров JWT для одностраничных приложений (SPA) и
приложений Blazor. Шаблоны теперь используют сервер Duende Identity .
Если вы расширяете модели удостоверений и обновляете существующие проекты,
необходимо обновить пространства имен в коде с IdentityServer4.IdentityServer
на Duende.IdentityServer и следовать инструкциям по миграции .
Модель лицензирования для Duende Identity Server была изменена на
перекрестную лицензию, из-за чего может потребоваться оплатить лицензию, если
она используется в рабочей среде в коммерческих целях. Дополнительные
сведения см. на странице лицензии Duende .
Отложенное согласование сертификата клиента
Теперь разработчики могут согласиться на использование отложенного
согласования сертификата клиента, указав ClientCertificateMode.DelayCertificate в
HttpsConnectionAdapterOptions. Это работает только с подключениями HTTP/1.1,
так как HTTP/2 запрещает отложенное повторное согласование сертификата.
Вызывающий объект этого API должен поместить в буфер текст запроса, прежде
чем запрашивать сертификат клиента:
C#
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.WebUtilities;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseKestrel(options =>
{
options.ConfigureHttpsDefaults(adapterOptions =>
{
adapterOptions.ClientCertificateMode =
ClientCertificateMode.DelayCertificate;
});
});
var app = builder.Build();
app.Use(async (context, next) =>
{
bool desiredState = GetDesiredState();
// Check if your desired criteria is met
if (desiredState)
{
// Buffer the request body
context.Request.EnableBuffering();
var body = context.Request.Body;
await body.DrainAsync(context.RequestAborted);
body.Position = 0;
// Request client certificate
var cert = await context.Connection.GetClientCertificateAsync();
// Disable buffering on future requests if the client doesn't
provide a cert
}
await next(context);
});
app.MapGet("/", () => "Hello World!");
app.Run();
OnCheckSlidingExpiration событие для контроля
обновления cookie
Скользящий срок действия проверки подлинности Cookie теперь можно настроить
или подавить с помощью нового OnCheckSlidingExpiration. Например, это событие
может использоваться одностраничным приложением, которое должно
периодически выполнять проверку связи с сервером, не влияя на сеанс проверки
подлинности.
Прочее
Горячая перезагрузка
Горячая перезагрузка позволяет быстро вносить обновления в пользовательский
интерфейс и код работающих приложений без потери состояния приложения,
чтобы ускорить разработку и повысить ее продуктивность. Дополнительные
сведения см. в статьях Поддержка Горячей перезагрузки .NET для ASP.NET Core и
Обновление основных возможностей хода выполнения Горячей перезагрузки .NET
и Visual Studio 2022 .
Улучшенные шаблоны одностраничных приложений
(SPA)
Шаблоны проектов ASP.NET Core были обновлены для Angular и React. Теперь в
них используется улучшенный шаблон для одностраничных приложений, которые
обеспечивает большую гибкость и более точно соответствуют шаблонам для
интерфейса современных веб-приложений.
Ранее в шаблоне ASP.NET Core для Angular и React во время разработки
использовалось специализированное ПО промежуточного слоя для запуска
сервера разработки для интерфейсной платформы, а затем запросы прокси от
ASP.NET Core к серверу разработки. Логика запуска сервера разработки была
предназначена для интерфейса командной строки для соответствующей
интерфейсной платформы. Для поддержки дополнительных интерфейсных
платформ, использующих этот шаблон, нужно было добавлять дополнительную
логику в ASP.NET Core.
Обновленные шаблоны ASP.NET Core для Angular и React в .NET 6 меняют
используемую ранее схему и используют преимущества встроенной поддержки
прокси-сервера на серверах разработки большинства современных интерфейсных
платформ. При запуске приложения ASP.NET Core интерфейсный сервер
разработки запускается так же, как и раньше, но сервер разработки настроен на
передачу запросов через прокси-сервер к внутреннему процессу ASP.NET Core. Все
настройки, связанные с настройкой прокси-сервера, являются частью приложения,
а не ASP.NET Core. Настроить проект ASP.NET Core для работы с другими
интерфейсными платформами теперь просто: нужно настроить интерфейсный
сервер разработки для выбранной платформы, чтобы передавать запросы на
внутреннему процессу ASP.NET Core в соответствии с шаблонами Angular и React.
Для кода запуска приложения ASP.NET Core больше не нужна отдельная логика для
одностраничного приложения. Логика для запуска интерфейсного сервера
разработки во время разработки внедряется в приложение во время выполнения с
помощью нового пакета Microsoft.AspNetCore.SpaProxy . Резервная
маршрутизация обрабатывается с помощью маршрутизации конечных точек
вместо ПО промежуточного слоя для одностраничных приложений.
Соответствующие шаблоны все еще можно запускать в виде отдельного проекта в
Visual Studio или с помощью dotnet run из командной строки. При публикации
приложения интерфейсный код в папке ClientApp строится и собирается, как и
раньше, в корне узла приложения ASP.NET Core и предоставляется в виде
статических файлов. Скрипты, содержащиеся в шаблоне, настраивают
интерфейсный сервер разработки для использования протокола HTTPS с помощью
сертификата разработки ASP.NET Core.
Черновая поддержка HTTP/3 в .NET 6
HTTP/3
в настоящее время находится в разработке, поэтому подлежит
изменению. Поддержка HTTP/3 в ASP.NET Core не выпущена — это
предварительная версия функции, включенная в .NET 6.
См. запись блога Поддержка HTTP/3 в .NET 6
.
Аннотации ссылочного типа, допускающие
значения NULL
В частях исходного кода ASP.NET Core 6,0
были применены заметки о
допустимости значений NULL.
Используя новую функцию допустимости значений NULL в C# 8, ASP.NET Core
может обеспечить дополнительную безопасность во время компиляции при
обработке ссылочных типов. Например, защита от исключений ссылок на null . В
проектах, которые выбрали использование заметок о допустимости значений
NULL, могут появиться новые предупреждения времени сборки от API ASP.NET
Core.
Чтобы включить ссылочные типы, допускающие значения NULL, добавьте в файлы
проекта следующее свойство:
XML
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
Дополнительные сведения см. в статье Ссылочные типы, допускающие значение
NULL.
Анализ исходного кода
Были добавлены несколько анализаторов платформы компилятора .NET, которые
проверяют код приложения на наличие таких проблем, как неправильная
конфигурация или порядок ПО промежуточного слоя, конфликты маршрутизации
и т. д. Дополнительные сведения см. в статье Анализ кода в приложениях ASP.NET
Core.
Улучшения шаблонов веб-приложений
Шаблоны веб-приложений:
Используют новую минимальную модель размещения.
Значительно сокращают количество файлов и строк кода, необходимых для
создания приложения. Например, пустое веб-приложение ASP.NET Core
создает один файл C# с четырьмя строками кода и является полноценным
приложением.
Объединяют Startup.cs и Program.cs в один файл Program.cs .
Используют инструкции верхнего уровня для минимизации кода,
необходимого для приложения.
Используют глобальные директивы using, чтобы исключить или
минимизировать числу требуемых строк инструкций using.
Созданные шаблоном порты для Kestrel
Во время создания проекта назначаются случайные порты для использования вебсервером Kestrel. Случайные порты помогают уменьшить конфликт портов, если на
одном компьютере выполняется несколько проектов.
При создании проекта в созданном файле Properties/launchSettings.json задается
случайный порт HTTP между 5000 и 5300 и случайный порт HTTPS между 7000 и
7300. Порты можно изменить в файле Properties/launchSettings.json . Если порт не
указан, Kestrel по умолчанию использует порт HTTP 5000 и HTTPS 5001.
Дополнительные сведения см. в статье KestrelНастройка конечных точек для вебсервера для ASP.NET Core.
Новые параметры ведения журнала по умолчанию
В appsettings.json и appsettings.Development.json были внесены следующие
изменения:
diff
- "Microsoft": "Warning",
- "Microsoft.Hosting.Lifetime": "Information"
+ "Microsoft.AspNetCore": "Warning"
Изменение с "Microsoft": "Warning" на "Microsoft.AspNetCore": "Warning" приводит
к записи всех информационных сообщений из пространства имен Microsoft ,
кроме Microsoft.AspNetCore . Например, Microsoft.EntityFrameworkCore теперь
заносится в журнал на информационном уровне.
Автоматическое добавление ПО промежуточного
слоя страницы исключений разработчика
В среде разработки по умолчанию DeveloperExceptionPageMiddleware добавляется .
Больше не нужно добавлять следующий код в приложения пользовательского вебинтерфейса:
C#
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
Поддержка заголовков запросов в кодировке Latin1 в
HttpSysServer
HttpSysServer теперь поддерживает декодирование заголовков запросов, Latin1
закодированных путем присвоения свойству UseLatin1RequestHeaders в
HttpSysOptions значения true :
C#
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseHttpSys(o => o.UseLatin1RequestHeaders = true);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Журналы модуля ASP.NET Core включают метки
времени и идентификатор процесса
Расширенные журналы диагностики модуля ASP.NET Core (ANCM) для IIS (ANCM)
содержат метки времени и идентификатор процесса , создающего журналы.
Запись меток времени и идентификатора процесса в журналы упрощает
диагностику проблем с перекрывающимися перезапусками процессов в IIS при
выполнении нескольких рабочих процессов IIS.
Журналы теперь похожи на пример, показанный ниже:
Интерфейс командной строки.NET
[2021-07-28T19:23:44.076Z, PID: 11020] [aspnetcorev2.dll] Initializing logs
for 'C:\<path>\aspnetcorev2.dll'. Process Id: 11020. File Version:
16.0.21209.0. Description: IIS ASP.NET Core Module V2. Commit:
96475a2acdf50d7599ba8e96583fa73efbe27912.
[2021-07-28T19:23:44.079Z, PID: 11020] [aspnetcorev2.dll] Resolving hostfxr
parameters for application: '.\InProcessWebSite.exe' arguments: '' path:
'C:\Temp\e86ac4e9ced24bb6bacf1a9415e70753\'
[2021-07-28T19:23:44.080Z, PID: 11020] [aspnetcorev2.dll] Known dotnet.exe
location: ''
Настраиваемый размер буфера для
неиспользованных входящих запросов для IIS
Сервер IIS ранее помещал в буфер только 64 КБ текстов неиспользованных
запросов. Размер буфера 64 КБ привел к ограничению операций чтения этим
максимальным размером, что влияло на производительность при больших
входящих объектах, таких как отправка данных. В .NET 6 размер буфера по
умолчанию измене с 64 КиБ на 1 МиБ, что повышает пропускную способность для
отправок больших объемов данных. В наших тестах отправка 700 МиБ, которая
занимала 9 секунд, теперь занимает всего 2,5 секунды.
Недостатком большего размера буфера является увеличение потребления памяти
для каждого запроса, когда приложение не считывает текст запроса быстро.
Поэтому, помимо изменения размера буфера по умолчанию, теперь появилась
возможность настроить размер буфера для приложений в зависимости от рабочей
нагрузки.
Вспомогательные функции тегов для компонентов
представлений
Рассмотрим компонент представления с необязательным параметром, как
показано в следующем коде:
C#
class MyViewComponent
{
IViewComponentResult Invoke(bool showSomething = false) { ... }
}
В ASP.NET Core 6 вспомогательную функцию тегов можно вызвать без указания
значения для параметра showSomething :
razor
<vc:my />
Шаблон Angular обновлен до версии Angular 12
Шаблон ASP.NET Core 6.0 для Angular теперь использует Angular 12
.
Шаблон React обновлен до React 17
.
Настраиваемое пороговое значение буфера перед
записью на диск в форматировщике выходных
данных Json.NET
Примечание. Рекомендуется использовать форматировщик выходных данных
System.Text.Json, за исключением случаев, когда сериализатор Newtonsoft.Json
требуется для обеспечения совместимости. Сериализатор System.Text.Json
является полностью асинхронным ( async ) и эффективно работает для больших
объемов полезных данных.
Форматировщик выходных данных Newtonsoft.Json по умолчанию помещает в
буфер в памяти ответы размером до 32 КБ перед буферизацией на диске. Это
позволяет избежать выполнения синхронных операций ввода-вывода, которые
могут привести к другим побочным эффектам, таким как нехватка потоков и
взаимоблокировка приложений. Однако если размер ответа превышает 32 КБ, для
операций дискового ввода-вывода требуется значительное время. Теперь
пороговое использование памяти, прежде чем данные помещаются в буфер на
диске, можно настроить с помощью свойства
MvcNewtonsoftJsonOptions.OutputFormatterMemoryBufferThreshold:
C#
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages()
.AddNewtonsoftJson(options =>
{
options.OutputFormatterMemoryBufferThreshold = 48 * 1024;
});
var app = builder.Build();
Дополнительные сведения см. в этом запросе на вытягивание на GitHub
ив
файле NewtonsoftJsonOutputFormatterTest.cs .
Ускорение получения и установки заголовков HTTP
Добавлены новые API для предоставления доступа ко всем распространенным
заголовкам, доступным в Microsoft.Net.Http.Headers.HeaderNames в качестве
свойств (IHeaderDictionary), в результате чего API теперь проще использовать.
Например, встроенное ПО промежуточного слоя в следующем коде получает и
устанавливает заголовки запросов и ответов с помощью новых API:
C#
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Use(async (context, next) =>
{
var hostHeader = context.Request.Headers.Host;
app.Logger.LogInformation("Host header: {host}", hostHeader);
context.Response.Headers.XPoweredBy = "ASP.NET Core 6.0";
await next.Invoke(context);
var dateHeader = context.Response.Headers.Date;
app.Logger.LogInformation("Response date: {date}", dateHeader);
});
app.Run();
Для реализованных заголовков методы доступа получения и установки
реализуются путем перехода непосредственно к полю без поиска. Для
нереализованных заголовков методы доступа могут пропускать первоначальный
поиск по реализованным заголовкам и напрямую выполнять поиск
Dictionary<string, StringValues> . Пропуск поиска ускоряет доступ в обоих
сценариях.
Асинхронная потоковая передача
ASP.NET Core теперь поддерживает асинхронную потоковую передачу из действий
контроллера и ответы от форматировщика JSON. Возврат IAsyncEnumerable от
действия больше не приводит к буферизации содержимого ответа в памяти перед
его отправкой. Отсутствие буферизации помогает сократить использование памяти
при возврате больших наборов данных, которые можно перечислить асинхронно.
Обратите внимание, что Entity Framework Core предоставляет реализации
IAsyncEnumerable для запроса базы данных. Улучшенная поддержка
IAsyncEnumerable в ASP.NET Core в .NET 6 может сделать использование EF Core с
ASP.NET Core более эффективным. Например, следующий код больше не помещает
данные о продуктах в память перед отправкой ответа:
C#
public IActionResult GetMovies()
{
return Ok(_context.Movie);
}
Однако при использовании отложенной загрузки в EF Coreэто новое поведение
может привести к ошибкам из-за параллельного выполнения запроса во время
перечисления данных. Приложения могут вернуться к предыдущему поведению
путем буферизации данных:
C#
public async Task<IActionResult> GetMovies2()
{
return Ok(await _context.Movie.ToListAsync());
}
Дополнительные сведения об этом изменении в поведении см. в соответствующем
объявлении .
ПО промежуточного слоя для ведения журнала HTTP
Ведение журнала HTTP — это новое встроенное ПО промежуточного слоя, в
котором регистрируются сведения об HTTP-запросах и HTTP-ответах, включая
заголовки и весь текст:
C#
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseHttpLogging();
app.MapGet("/", () => "Hello World!");
app.Run();
При переходе к / с помощью кода выше данные записываются в журнал
примерно так, как показано ниже:
Интерфейс командной строки.NET
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
Request:
Protocol: HTTP/2
Method: GET
Scheme: https
PathBase:
Path: /
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,
*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cache-Control: max-age=0
Connection: close
Cookie: [Redacted]
Host: localhost:44372
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Edg/95.0.1020.30
sec-ch-ua: [Redacted]
sec-ch-ua-mobile: [Redacted]
sec-ch-ua-platform: [Redacted]
upgrade-insecure-requests: [Redacted]
sec-fetch-site: [Redacted]
sec-fetch-mode: [Redacted]
sec-fetch-user: [Redacted]
sec-fetch-dest: [Redacted]
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
Response:
StatusCode: 200
Content-Type: text/plain; charset=utf-8
Предыдущие выходные данные были включены в следующий файл
appsettings.Development.json :
JSON
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware":
"Information"
}
}
}
Ведение журнала HTTP предоставляет журналы со следующими сведениями:
информация HTTP-запроса;
общие свойства;
заголовки;
текст;
информация HTTP-ответа.
Чтобы настроить ПО промежуточного слоя для ведения журнала HTTP, укажите
HttpLoggingOptions:
C#
using Microsoft.AspNetCore.HttpLogging;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(logging =>
{
// Customize HTTP logging.
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("My-Request-Header");
logging.ResponseHeaders.Add("My-Response-Header");
logging.MediaTypeOptions.AddText("application/javascript");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
});
var app = builder.Build();
app.UseHttpLogging();
app.MapGet("/", () => "Hello World!");
app.Run();
Функция IConnectionSocketFeature
Функция запроса IConnectionSocketFeature предоставляет доступ к базовому
принимающему сокету, связанному с текущим запросом. Использовать ее можно с
помощью FeatureCollection в HttpContext .
Например, следующее приложение задает свойство LingerState для принимающего
сокета:
C#
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.ConfigureEndpointDefaults(listenOptions =>
listenOptions.Use((connection, next) =>
{
var socketFeature =
connection.Features.Get<IConnectionSocketFeature>();
socketFeature.Socket.LingerState = new LingerOption(true, seconds:
10);
return next();
}));
});
var app = builder.Build();
app.MapGet("/", (Func<string>)(() => "Hello world"));
await app.RunAsync();
Ограничения универсального типа в Razor
При определении параметров универсального типа в Razor с помощью директивы
@typeparam ограничения универсального типа теперь можно указать с помощью
стандартного синтаксиса C#:
Уменьшение размера скриптов SignalR, Blazor Server и
MessagePack
Скрипты SignalR, MessagePack и Blazor Server теперь значительно меньше, что
обеспечивает меньший объем загрузки, меньшее время синтаксического анализа и
компиляции кода JavaScript в браузере и ускоряет запуск. Уменьшение размера:
signalr.js : 70%
blazor.server.js : 45%
Уменьшение размера скриптов стало возможным благодаря Бену Адамсу (Ben
Adams) . Дополнительные сведения об уменьшении размера см. в этом запросе
на вытягивание на GitHub .
Включение сеансов профилирования Redis
Вклад от Габриэля Лукачи (Gabriel Lucaci)
позволяет проводить сеансы
профилирования Redis с помощью
Microsoft.Extensions.Caching.StackExchangeRedis
:
C#
using StackExchange.Redis.Profiling;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddStackExchangeRedisCache(options =>
{
options.ProfilingSession = () => new ProfilingSession();
});
Дополнительные сведения см. в статье Профилирование StackExchange.Redis .
Теневое копирование в IIS
В модуль ASP.NET Core (ANCM) для IIS добавлена экспериментальная функция,
позволяющая обеспечить поддержку теневого копирования сборок приложений. В
настоящее время .NET блокирует двоичные файлы приложения при запуске на
Windows, что делает невозможным замену двоичных файлов во время работы
приложения. Хотя мы по-прежнему рекомендуем использовать автономный файл
приложения, мы понимаем, что существуют определенные сценарии (например,
FTP-развертывания), где это невозможно.
В таких случаях включите теневое копирование, настроив параметры обработчика
модуля ASP.NET Core. В большинстве случаев у приложений ASP.NET Core нет
файла web.config в системе управления версиями, который можно изменить. В
ASP.NET Core web.config обычно создается пакетом SDK. Чтобы приступить к
работе, можно использовать следующий пример web.config :
XML
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- To customize the asp.net core module uncomment and edit the following
section.
For more info see https://go.microsoft.com/fwlink/?linkid=838655 -->
<system.webServer>
<handlers>
<remove name="aspNetCore"/>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2"
resourceType="Unspecified"/>
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%"
stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout">
<handlerSettings>
<handlerSetting name="experimentalEnableShadowCopy" value="true" />
<handlerSetting name="shadowCopyDirectory"
value="../ShadowCopyDirectory/" />
<!-- Only enable handler logging if you encounter issues-->
<!--<handlerSetting name="debugFile" value=".\logs\aspnetcoredebug.log" />-->
<!--<handlerSetting name="debugLevel" value="FILE,TRACE" />-->
</handlerSettings>
</aspNetCore>
</system.webServer>
</configuration>
Теневое копирование в IIS — это экспериментальная функция. Мы не гарантируем,
что она станет частью ASP.NET Core. Оставьте отзыв о теневом копировании IIS в
описании этой проблемы на GitHub .
Дополнительные ресурсы
Примеры кода перенесены на новую минимальную модель размещения в 6.0
Новые возможности .NET 6
Новые возможности в ASP.NET
Core 5.0
Статья • 05.10.2022 • Чтение занимает 13 мин
В этой статье описываются наиболее важные изменения в ASP.NET Core 5.0 со
ссылками на соответствующую документацию.
Улучшения ASP.NET Core MVC и Razor
Привязка модели DateTime в формате UTC
Привязка модели теперь поддерживает привязку строк времени в формате UTC к
DateTime . Если запрос содержит строку времени в формате UTC, привязка модели
привязывает ее к DateTime в формате UTC. Например, следующая строка времени
привязывается к DateTime в формате UTC:
https://example.com/mycontroller/myaction?time=2019-06-14T02%3A30%3A04.0576719Z
Привязка модели и проверка с помощью типов
записей C# 9
Типы записей C# 9 можно использовать с привязкой модели в контроллере MVC
или на странице Razor. Типы записей — это хороший способ моделирования
передаваемых по сети данных.
Например, в следующем контроллере PersonController используется тип записи
Person с привязкой модели и проверкой формы:
C#
public record Person([Required] string Name, [Range(0, 150)] int Age);
public class PersonController
{
public IActionResult Index() => View();
[HttpPost]
public IActionResult Index(Person person)
{
// ...
}
}
Файл Person/Index.cshtml :
CSHTML
@model Person
Name: <input asp-for="Model.Name" />
<span asp-validation-for="Model.Name" />
Age: <input asp-for="Model.Age" />
<span asp-validation-for="Model.Age" />
Усовершенствования DynamicRouteValueTransformer
В ASP.NET Core 3.1 появился DynamicRouteValueTransformer в качестве способа
использования пользовательской конечной точки для динамического выбора
действия контроллера MVC или страницы Razor. Приложения ASP.NET Core 5.0
могут передавать состояние в DynamicRouteValueTransformer и фильтровать
выбранный набор конечных точек.
Прочее
Этот атрибут [Compare] можно применить к свойствам в модели страницы
Razor.
Параметры и свойства, привязанные из текста, считаются обязательными по
умолчанию.
Веб-интерфейс API
Спецификация OpenAPI включена по умолчанию
Спецификация OpenAPI
является отраслевым стандартом для описания API-
интерфейсов HTTP и их интеграции в сложные бизнес-процессы или со
сторонними производителями. OpenAPI широко поддерживается всеми
поставщиками облачных служб и многими реестрами API. В приложениях,
создающих документы OpenAPI из веб-API, представлено множество новых
возможностей, в которых можно использовать эти API-интерфейсы. В условиях
партнерства с издателями проекта Swashbuckle.AspNetCore
с открытым кодом
шаблон API для ASP.NET Core содержит зависимость NuGet от Swashbuckle
.
Swashbuckle — это популярный пакет NuGet с открытым кодом, который
динамически создает документы OpenAPI. Он выполняет это путем самоанализа
через контроллеры API и создания документа OpenAPI во время выполнения или
во время сборки, используя интерфейс командной строки Swashbuckle.
В ASP.NET Core 5.0 шаблоны веб-API по умолчанию поддерживают OpenAPI. Чтобы
отключить OpenAPI:
Из командной строки.
Интерфейс командной строки.NET
dotnet new webapi --no-openapi true
Из Visual Studio: снимите флажок Enable OpenAPI support (Включить
поддержку OpenAPI).
Все файлы .csproj , созданные для проектов веб-API, содержат ссылку на пакет
NuGet Swashbuckle.AspNetCore
.
XML
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
</ItemGroup>
Код, созданный шаблоном, содержит код в Startup.ConfigureServices , который
активирует создание документа OpenAPI:
C#
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApp1", Version =
"v1" });
});
}
Метод Startup.Configure добавляет ПО промежуточного слоя Swashbuckle, которое
активирует:
процесс создания документа;
страницу пользовательского интерфейса Swagger по умолчанию в режиме
разработки.
Созданный шаблоном код не станет по случайности предоставлять описание API
при публикации в рабочей среде.
C#
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json",
"WebApp1 v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Импорт Управления API Azure
Если проекты API для ASP.NET Core включают OpenAPI, Visual Studio 2019
версии 16.8 и более поздних публикаций автоматически предлагают
дополнительный этап в потоке публикации. Разработчики, использующие
Управление API Azure, имеют возможность автоматического импорта APIинтерфейсов в Управление API Azure во время потока публикации:
Улучшенный интерфейс запуска для проектов веб-API
Если OpenAPI включен по умолчанию, интерфейс запуска приложений (F5) для
разработчиков веб-API значительно улучшается. В ASP.NET Core 5.0 шаблон веб-API
предварительно настроен для загрузки страницы пользовательского интерфейса
Swagger. На странице пользовательского интерфейса Swagger содержится
документация, добавленная для опубликованного API, и обеспечивается
тестирование API-интерфейсов одним щелчком мыши.
Blazor
Повышение производительности
В .NET 5 мы внесли значительные улучшения в производительность среды
выполнения Blazor WebAssembly, делая основной акцент на сложной отрисовке
пользовательского интерфейса и сериализации JSON. По результатам наших тестов
производительности в большинстве сценариев скорость Blazor WebAssembly в
.NET 5 в два-три раза выше. Дополнительные сведения см. в блоге ASP.NET об
обновлениях ASP.NET Core в .NET 5 (релиз-кандидат 1) .
Изоляция CSS
Теперь Blazor поддерживает определение стилей CSS, областью действия которых
является данный компонент. Благодаря стилям CSS для отдельных компонентов
проще ориентироваться в стилях в приложении и избежать непредвиденных
побочных эффектов от применения глобальных стилей. Дополнительные сведения
см. в статье Изоляция CSS в ASP.NET Core Blazor.
Новый компонент InputFile
Компонент InputFile позволяет считывать один или несколько файлов, выбранных
пользователем для отправки. Дополнительные сведения см. в статье Отправка
файлов в ASP.NET Core Blazor.
Новые компоненты InputRadio и InputRadioGroup
Blazor содержит встроенные компоненты InputRadio и InputRadioGroup , которые
упрощают привязку данных к группам переключателей со встроенной проверкой.
Дополнительные сведения см. в статье Формы и компоненты ввода в Blazor для
ASP.NET Core.
Виртуализация компонентов
Повысьте воспринимаемую производительность отрисовки компонентов с
помощью встроенной поддержки виртуализации платформы Blazor.
Дополнительные сведения см. в статье Виртуализация компонентов Razor ASP.NET
Core.
Поддержка события ontoggle
События Blazor теперь поддерживают событие DOM ontoggle . Дополнительные
сведения см. в статье Обработка событий Blazor в ASP.NET Core.
Установка фокуса пользовательского интерфейса в
приложениях Blazor
Используйте удобный метод FocusAsync со ссылками на элемент, чтобы установить
фокус пользовательского интерфейса на этот элемент. Дополнительные сведения
см. в статье Обработка событий Blazor в ASP.NET Core.
Настраиваемые атрибуты класса проверки CSS
Настраиваемые атрибуты класса проверки CSS полезны при интеграции с
платформами CSS, такими как Bootstrap. Дополнительные сведения см. в статье
Формы и компоненты ввода в Blazor для ASP.NET Core.
Поддержка IAsyncDisposable
Теперь компоненты Razor поддерживают интерфейс IAsyncDisposable для
асинхронного выпуска выделенных ресурсов.
Изоляция JavaScript и ссылки на объекты
Blazor реализует изоляцию JavaScript в стандартных модулях JavaScript .
Дополнительные сведения см. в статье Вызов функций JavaScript из методов .NET в
ASP.NET Core Blazor.
Поддержка компонентами форм отображаемого
имени
Следующие встроенные компоненты поддерживают отображаемые имена с
помощью параметра DisplayName .
InputDate
InputNumber
InputSelect
Дополнительные сведения см. в статье Формы и компоненты ввода в Blazor для
ASP.NET Core.
Параметры маршрута catch-all
В компонентах поддерживаются параметры маршрута catch-all, которые
захватывают пути в нескольких папках. Дополнительные сведения см. в статье
Маршрутизация ASP.NET Core Blazor и навигация.
Усовершенствования отладки
В ASP.NET Core 5.0 улучшена отладка приложений Blazor WebAssembly. Кроме того,
сейчас отладка поддерживается в Visual Studio для Mac. Дополнительные сведения
см. в статье Отладка в ASP.NET CoreBlazor WebAssembly.
Microsoft Identity версии 2.0 и MSAL версии 2.0
Теперь для безопасности Blazor используется Microsoft Identity версии 2.0
(Microsoft.Identity.Web
и Microsoft.Identity.Web.UI ) и MSAL версии 2.0.
Дополнительные сведения см. в статье о безопасности Blazor и узле Identity.
Защищенное хранилище браузера для Blazor Server
Теперь приложения Blazor Server могут использовать встроенную поддержку для
хранения сведений о состоянии приложения в браузере, который уже защищен от
незаконного изменения благодаря защите данных в ASP.NET Core. Данные могут
храниться либо в локальном хранилище браузера, либо в хранилище сеансов.
Дополнительные сведения. см. в статье Управление состоянием ASP.NET Core
Blazor.
Предварительная отрисовка Blazor WebAssembly
Благодаря улучшенной интеграции компонентов в моделях размещения
приложения Blazor WebAssembly теперь могут предварительно отрисовывать
выходные данные на сервере.
Улучшенная обрезка и компоновка
Blazor WebAssembly выполняет обрезку и компоновку промежуточного языка (IL)
во время сборки, чтобы затем удалить ненужный IL из выходных сборок
приложения. В выпуске ASP.NET Core 5.0 Blazor WebAssembly выполняет
улучшенную обрезку с помощью дополнительных параметров конфигурации.
Дополнительные сведения см. в статьях Настройка средства обрезки для ASP.NET
Core Blazor и Параметры обрезки.
Анализатор совместимости с браузерами
Приложения Blazor WebAssembly предназначены для использования в полной
контактной зоне API .NET, но из-за ограничений песочницы браузера
поддерживаются не все API-интерфейсы .NET. При выполнении в WebAssembly
неподдерживаемые API-интерфейсы вызывают PlatformNotSupportedException.
Анализатор совместимости платформ предупреждает разработчика, когда
приложение использует API-интерфейсы, которые не поддерживаются целевыми
платформами приложения. Дополнительные сведения см. в статье Использование
компонентов в ASP.NET Core из библиотеки классов Razor (RCL).
Сборки с отложенной загрузкой
Производительность запуска приложения Blazor WebAssembly можно повысить,
отложив загрузку некоторых сборок приложений, пока они не потребуются.
Дополнительные сведения см. в статье Настройка средства обрезки для ASP.NET
Core Blazor WebAssembly.
Обновленная поддержка глобализации
Поддержка глобализации доступна для Blazor WebAssembly на основе ICU
(международных компонентов для Юникода). Дополнительные сведения см. в
статье Глобализация и локализация в ASP.NET Core Blazor.
gRPC
В gRPC
внесено много усовершенствований производительности.
Дополнительные сведения см. в статье Улучшения производительности gRPC в
.NET 5 .
Дополнительные сведения о gRPC см. в статье Общие сведения о gRPC на .NET.
SignalR
Фильтры концентраторов SignalR
Фильтры концентраторов SignalR, называемые конвейерами концентраторов в
ASP.NET SignalR, — это возможность, которая позволяет выполнять код до и после
вызова методов концентратора. Выполнение кода до и после вызова методов
концентратора аналогично тому, как ПО промежуточного слоя может выполнять
код до и после HTTP-запроса. Распространенные варианты использования
включают ведение журнала, обработку ошибок и проверку аргументов.
Подробные сведения см. в статье Использование фильтров концентраторов в
ASP.NET Core SignalR.
Параллельные вызовы концентраторов SignalR
ASP.NET Core SignalR теперь может обрабатывать параллельные вызовы
концентраторов. Поведение по умолчанию можно изменить, чтобы разрешить
клиентам одновременно вызывать несколько методов концентратора:
C#
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR(options =>
{
options.MaximumParallelInvocationsPerClient = 5;
});
}
Добавлена поддержка MessagePack в клиент Java для
SignalR
Новый пакет com.microsoft.signalr.messagepack
добавляет поддержку
MessagePack в клиент Java для SignalR. Чтобы использовать протокол
концентратора MessagePack, добавьте .withHubProtocol(new
MessagePackHubProtocol()) в построитель подключений:
Java
HubConnection hubConnection = HubConnectionBuilder.create(
"http://localhost:53353/MyHub")
.withHubProtocol(new MessagePackHubProtocol())
.build();
Kestrel
Перезагружаемые конечные точки с помощью конфигурации. Kestrel может
обнаружить изменения в конфигурации, переданные
KestrelServerOptions.Configure, отменить привязку к имеющимся конечным
точкам и привязать к новым конечным точкам, не требуя перезапуска
приложения, если для параметра reloadOnChange задано значение true . По
умолчанию при использовании ConfigureWebHostDefaults или
CreateDefaultBuilderKestrel привязывается к подразделу конфигурации
"Kestrel"
с включенным параметром reloadOnChange . Приложения должны
передавать reloadOnChange: true при вызове KestrelServerOptions.Configure
вручную для получения перезагружаемых конечных точек.
Улучшения заголовков ответов HTTP/2. Дополнительные сведения см. в
следующем разделе Улучшения в области производительности.
Поддержка дополнительных типов конечных точек в транспортировке
сокетов. В дополнение к новому API, представленному в System.Net.Sockets,
транспортировка сокетов по умолчанию в Kestrel позволяет привязаться как к
имеющимся дескрипторам файлов, так и к сокетам домена Unix. Поддержка
привязки к имеющимся дескрипторам файлов позволяет использовать
имеющуюся интеграцию Systemd , не требуя транспортировки libuv .
Декодирование настраиваемого заголовка в Kestrel. Приложения могут
указать, какие Encoding использовать для интерпретации входящих
заголовков на основе имени заголовка, а не по умолчанию в UTF-8. Задайте
свойство
Microsoft.AspNetCore.Server.Kestrel.KestrelServerOptions.RequestHeaderEncoding
Selector , чтобы указать используемую кодировку:
C#
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
options.RequestHeaderEncodingSelector = encoding =>
{
return encoding switch
{
"Host" => System.Text.Encoding.Latin1,
_ => System.Text.Encoding.UTF8,
};
};
});
webBuilder.UseStartup<Startup>();
});
Параметры для конечной точки Kestrel с помощью
конфигурации
Добавлена поддержка настройки параметров для каждой конечной точки Kestrel с
помощью конфигурации. Конфигурации для каждой конечной точки включают:
используемые протоколы HTTP;
используемые протоколы TLS;
выбранный сертификат;
режим сертификата клиента.
Конфигурация позволяет определить, какой сертификат выбирается на основе
указанного имени сервера. Имя сервера входит в состав расширения указания
имени сервера для протокола TLS, как указано клиентом. Конфигурация Kestrel
также поддерживает префикс подстановочного знака в имени узла.
В следующем примере показано, как задать для каждой конечной точки
использование файла конфигурации:
JSON
{
"Kestrel": {
"Endpoints": {
"EndpointName": {
"Url": "https://*",
"Sni": {
"a.example.org": {
"Protocols": "Http1AndHttp2",
"SslProtocols": [ "Tls11", "Tls12"],
"Certificate": {
"Path": "testCert.pfx",
"Password": "testPassword"
},
"ClientCertificateMode" : "NoCertificate"
},
"*.example.org": {
"Certificate": {
"Path": "testCert2.pfx",
"Password": "testPassword"
}
},
"*": {
// At least one sub-property needs to exist per
// SNI section or it cannot be discovered via
// IConfiguration
"Protocols": "Http1",
}
}
}
}
}
}
Указание имени сервера — это расширение TLS для включения виртуального
домена в состав согласования SSL. Это фактически означает, что виртуальное
доменное имя или имя узла можно использовать для определения конечной точки
сети.
Усовершенствования в области
производительности
HTTP/2
Значительное сокращение распределений в пути к коду HTTP/2.
Поддержка динамического сжатия HPack
заголовков ответов HTTP/2 в
Kestrel. Дополнительные сведения см. в разделе Размер таблицы заголовка и
записи блога HPACK: возможность Silent Killer в HTTP/2 .
Отправка кадров проверки связи HTTP/2. HTTP/2 имеет механизм отправки
кадров проверки связи, чтобы неактивное соединение по-прежнему
работало. Обеспечение работоспособного соединения особенно полезно при
работе с долгосрочными потоками, которые часто находятся в неактивном
состоянии, но только периодически видят действия, например потоки gRPC.
Приложения могут отправить периодические кадры проверки связи в Kestrel,
установив ограничения на KestrelServerOptions:
C#
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
options.Limits.Http2.KeepAlivePingInterval =
TimeSpan.FromSeconds(10);
options.Limits.Http2.KeepAlivePingTimeout =
TimeSpan.FromSeconds(1);
});
webBuilder.UseStartup<Startup>();
});
Контейнеры
До выпуска .NET 5.0 создание и публикация документа Dockerfile для приложения
ASP.NET Core, необходимого для получения всего пакета SDK для .NET Core и
образа ASP.NET Core. В этом выпуске количество извлекаемых байтов образов
пакета SDK сокращено, а байты, полученные для образа ASP.NET Core, как правило,
устраняются. Дополнительные сведения об этом см. в комментарии к проблеме
на GitHub .
Аутентификация и авторизация
Проверка подлинности Azure Active Directory с
помощью Microsoft.Identity.Web
Шаблоны проектов ASP.NET Core теперь интегрируются с Microsoft.Identity.Web для
управления проверкой подлинности с помощью Azure Activity Directory (Azure AD).
Пакет Microsoft.Identity.Web
предоставляет:
Улучшенный интерфейс проверки подлинности с помощью Azure AD.
Более простой способ доступа к ресурсам Azure от имени пользователей,
включая Microsoft Graph. См. пример Microsoft.Identity.Web , который
начинается с базового входа и продолжается через мультитенантность с
использованием API-интерфейсов Azure, Microsoft Graph и защиту
собственных API-интерфейсов. Пакет Microsoft.Identity.Web доступен вместе
с .NET 5.
Разрешение анонимного доступа к конечной точке
Метод расширения AllowAnonymous предоставляет анонимный доступ к конечной
точке:
C#
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
})
.AllowAnonymous();
});
}
Пользовательская обработка сбоев авторизации
Пользовательская обработка сбоев авторизации стала еще проще благодаря
новому интерфейсу IAuthorizationMiddlewareResultHandler , который вызывается
ПО промежуточного слояавторизации. Реализация по умолчанию остается
неизменной, но пользовательский обработчик можно зарегистрировать во
внедрении зависимостей, что позволяет использовать пользовательские HTTP-
ответы в зависимости от причины сбоя авторизации. См. этот пример , в котором
демонстрируется использование IAuthorizationMiddlewareResultHandler .
Авторизация при использовании маршрутизации
конечных точек
Авторизация при использовании маршрутизации конечных точек теперь получает
HttpContext , а не экземпляр конечной точки. Это позволяет ПО промежуточного
слоя авторизации получить доступ к RouteData и другим свойствам HttpContext ,
которые были недоступны через класс Endpoint. Конечную точку можно получить
из контекста, используя context.GetEndpoint.
Управление доступом на основе ролей с помощью
проверки подлинности Kerberos и протокола LDAP в
Linux
См. раздел Проверка подлинности Kerberos и управление доступом на основе
ролей (RBAC).
Усовершенствования API
Методы расширения JSON для HttpRequest и
HttpResponse
Данные JSON можно считывать и записывать из HttpRequest и HttpResponse с
помощью новых методов расширения ReadFromJsonAsync и WriteAsJsonAsync . Эти
методы расширения используют сериализатор System.Text.Json для обработки
данных JSON. Новый метод расширения HasJsonContentType также может
проверять, имеет ли запрос тип содержимого JSON.
Методы расширения JSON можно объединять с маршрутизацией конечных точек
для создания API-интерфейсов JSON в стиле программирования, который мы
называем маршрут к коду. Это новый вариант для разработчиков, которые хотят
создавать базовые API-интерфейсы JSON простым способом. Например, вебприложение, имеющее только несколько конечных точек, может использовать
маршрут к коду, а не все функции MVC ASP.NET Core:
C#
endpoints.MapGet("/weather/{city:alpha}", async context =>
{
var city = (string)context.Request.RouteValues["city"];
var weather = GetFromDatabase(city);
await context.Response.WriteAsJsonAsync(weather);
});
System.Diagnostics.Activity
System.Diagnostics.Activity теперь по умолчанию имеет формат W3C. Благодаря
этому распределенная трассировка в ASP.NET Core поддерживает взаимодействие
с другими платформами по умолчанию.
FromBodyAttribute
FromBodyAttribute теперь поддерживает настройку параметра, который позволяет
считать такие параметры или свойства необязательными:
C#
public IActionResult Post([FromBody(EmptyBodyBehavior =
EmptyBodyBehavior.Allow)]
MyModel model)
{
...
}
Прочие улучшения
Мы начали применять заметки, допускающие значение NULL, к сборкам ASP.NET
Core. Мы планируем добавить заметки к большей части контактной зоны
общедоступного API на платформе .NET 5.
Управление активацией класса Startup
Добавлена дополнительная перегрузка UseStartup, которая разрешает
приложению предоставлять фабричный метод для управления активацией класса
Startup . Управление активацией класса Startup полезно для передачи
дополнительных параметров в Startup , которые инициализируются вместе с
узлом:
C#
public class Program
{
public static async Task Main(string[] args)
{
var logger = CreateLogger();
var host = Host.CreateDefaultBuilder()
.ConfigureWebHost(builder =>
{
builder.UseStartup(context => new Startup(logger));
})
.Build();
await host.RunAsync();
}
}
Автоматическое обновление с помощью dotnet watch
В .NET 5 при запуске dotnet watch в проекте ASP.NET Core запускается браузер по
умолчанию и выполняется автоматическое обновление браузера по мере
внесения изменений в код. Это означает, что вы можете:
Открыть проект ASP.NET Core в текстовом редакторе.
Выполните dotnet watch .
Сосредоточиться на изменениях кода, пока инструментарий обрабатывает
перестроение, перезапуск и перезагрузку приложения.
Форматировщик средства ведения журнала консоли
В библиотеке Microsoft.Extensions.Logging для поставщика журнала консоли
внесено несколько усовершенствований. Теперь разработчики могут реализовать
пользовательский ConsoleFormatter , чтобы полностью контролировать
форматирование и цветовое выделение выходных данных консоли. APIинтерфейсы форматировщика позволяют выполнять обширное форматирование
путем реализации подмножества escape-последовательностей VT-100. VT-100
поддерживается в большинстве современных терминалов. Средство ведения
журнала консоли может анализировать escape-последовательности
неподдерживаемых терминалов, позволяя разработчикам создавать единый
форматировщик для всех терминалов.
Средство ведения журнала консолиJSON
Помимо поддержки пользовательских форматировщиков, мы также добавили
встроенный форматировщик JSON, который выводит структурированные журналы
JSON на консоль. В следующем примере кода показано, как переключиться со
средства ведения журнала по умолчанию на JSON:
C#
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.AddJsonConsole(options =>
{
options.JsonWriterOptions = new JsonWriterOptions()
{ Indented = true };
});
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
Сообщения журнала, выводимые на консоль, имеют формат JSON:
JSON
{
"EventId": 0,
"LogLevel": "Information",
"Category": "Microsoft.Hosting.Lifetime",
"Message": "Now listening on: https://localhost:5001",
"State": {
"Message": "Now listening on: https://localhost:5001",
"address": "https://localhost:5001",
"{OriginalFormat}": "Now listening on: {address}"
}
}
Новые возможности в ASP.NET
Core 3.1
Статья • 28.01.2023 • Чтение занимает 2 мин
В этой статье описываются наиболее важные изменения в ASP.NET Core 3.1 со
ссылками на соответствующую документацию.
Поддержка разделяемых классов для
компонентов Razor
Компоненты Razor теперь создаются как разделяемые классы. Код компонента
Razor можно написать как файл кода программной части, определенный как
разделяемый класс, вместо того чтобы определять весь код компонента в одном
файле. Дополнительные сведения см. в разделе Поддержка разделяемых классов.
Вспомогательная функция тега компонента и
передача параметров в компоненты
верхнего уровня
В Blazor с ASP.NET Core 3.0 компоненты отрисовывались как страницы и
представления с помощью вспомогательной функции HTML
( Html.RenderComponentAsync ). В ASP.NET Core 3.1 компонент отрисовывается из
страницы или представления с помощью новой вспомогательной функции тега
компонента:
CSHTML
<component type="typeof(Counter)" render-mode="ServerPrerendered" />
Вспомогательная функция HTML по-прежнему поддерживается в ASP.NET Core 3.1,
но рекомендуется использовать вспомогательную функцию тега компонента.
Приложения Blazor Server теперь могут передавать параметры в компоненты
верхнего уровня во время первоначальной отрисовки. Ранее параметры можно
было передавать в компонент верхнего уровня только с помощью
RenderMode.Static. В этом выпуске поддерживается как RenderMode.Server, так и
RenderMode.ServerPrerendered. Все указанные значения параметров сериализуются
в формат JSON и включаются в исходный ответ.
Например, компонент Counter может предварительно отрисовываться со
значением приращения ( IncrementAmount ):
CSHTML
<component type="typeof(Counter)" render-mode="ServerPrerendered"
param-IncrementAmount="10" />
Дополнительные сведения см. в разделе Интеграция компонентов в Razor Pages и
приложения MVC.
Поддержка общих очередей в HTTP.sys
HTTP.sys поддерживает создание анонимных очередей запросов. В ASP.NET
Core 3.1 добавлена возможность создания именованной очереди запросов
HTTP.sys или присоединения к существующей очереди. Создание именованной
очереди запросов HTTP.sys или присоединение к ней обеспечивает сценарии, в
которых процесс контроллера HTTP.Sys, которому принадлежит очередь, не
зависит от процесса прослушивателя. Такая независимость позволяет сохранять
существующие соединения и находящиеся в очереди запросы при перезапуске
процесса прослушивателя:
C#
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
// ...
webBuilder.UseHttpSys(options =>
{
options.RequestQueueName = "MyExistingQueue";
options.RequestQueueMode = RequestQueueMode.CreateOrAttach;
});
});
Критические изменения для файлов cookie
SameSite
Поведение файлов cookie SameSite изменилось в соответствии с предстоящими
изменениями в браузерах. Это может повлиять на сценарии проверки
подлинности, такие как AzureAd, OpenIdConnect или WsFederation. Дополнительные
сведения см. в статье Работа с файлами cookie SameSite в ASP.NET Core.
Запрет выполнения действий по умолчанию
для событий в приложениях Blazor
Используйте атрибут директивы @on{EVENT}:preventDefault , чтобы предотвратить
выполнение действия по умолчанию для события. В следующем примере
запрещается действие по умолчанию, отображающее символ клавиши в текстовом
поле:
razor
<input value="@_count" @onkeypress="KeyHandler" @onkeypress:preventDefault
/>
Дополнительные сведения см. в разделе Запрет действий по умолчанию.
Остановка распространения событий в
приложениях Blazor
Используйте атрибут директивы @on{EVENT}:stopPropagation , чтобы остановить
распространение событий. В следующем примере установка флажка блокирует
передачу событий щелчка мышью из дочернего элемента <div> в родительский
элемент <div> .
razor
<input @bind="_stopPropagation" type="checkbox" />
<div @onclick="OnSelectParentDiv">
<div @onclick="OnSelectChildDiv"
@onclick:stopPropagation="_stopPropagation">
...
</div>
</div>
@code {
private bool _stopPropagation = false;
}
Дополнительные сведения см. в разделе Отключение распространения событий.
Подробные сведения об ошибках во время
разработки приложений Blazor
Если во время разработки приложение Blazor работает неправильно, подробные
сведения об ошибках в приложении могут помочь в устранении неполадок. При
возникновении ошибки в приложении Blazor в нижней части экрана отображается
золотистая полоска.
Во время разработки из этой полоски можно перейти в консоль браузера, где
можно просмотреть исключение.
В рабочей среде эта полоска уведомляет пользователя о том, что произошла
ошибка, и рекомендует обновить содержимое окна браузера.
Дополнительные сведения см. в статье Обработка ошибок в приложениях Blazor
ASP.NET Core.
Новые возможности в ASP.NET
Core 3.0
Статья • 28.01.2023 • Чтение занимает 14 мин
В этой статье описываются наиболее важные изменения в ASP.NET Core 3.0 со
ссылками на соответствующую документацию.
Blazor
Blazor — это новая платформа в ASP.NET Core, предназначенная для создания
интерактивного веб-интерфейса на стороне клиента с использованием .NET. Она
воплощает следующие возможности:
создание многофункциональных интерактивных пользовательских
интерфейсов на C# вместо JavaScript;
совместное использование серверной и клиентской логик приложений,
написанных с помощью .NET;
отображение пользовательского интерфейса в виде HTML-страницы с CSS для
широкой поддержки браузеров, в том числе для мобильных устройств.
Поддерживаемые сценарии платформы Blazor:
повторно используемые компоненты пользовательского интерфейса
(компоненты Razor);
маршрутизация на стороне клиента;
макеты компонентов;
поддержка внедрения зависимостей;
Формы и проверка
Предоставление компонентов Razor в библиотеках классов Razor
Взаимодействие с JavaScript
Дополнительные сведения см. в статье об ASP.NET Blazor.
Blazor Server
Blazor отделяет логику отображения компонентов от того, как применяются
обновления пользовательского интерфейса. Blazor Server предоставляет поддержку
размещения компонентов Razor на сервере в приложении ASP.NET Core.
Обновления пользовательского интерфейса передаются через подключение
SignalR. Blazor Server поддерживается только в ASP.NET Core 3.0.
Blazor WebAssembly (предварительная версия)
Приложения Blazor можно также запускать напрямую в браузере с
использованием среды выполнения .NET на основе WebAssembly. Платформа
Blazor WebAssembly доступна в режиме предварительной версии и не
поддерживается в ASP.NET Core 3.0. Blazor WebAssembly будет поддерживаться в
будущем выпуске ASP.NET Core.
составные части компонента Razor.
Приложения Blazor создаются на основе компонентов. Компоненты — это
автономные блоки пользовательского интерфейса, такие как страница, диалоговое
окно или форма. Это обычные классы .NET, определяющие логику отрисовки
пользовательского интерфейса и обработчики событий на стороне клиента.
Многофункциональные интерактивные веб-приложения можно создавать без
JavaScript.
Компоненты в Blazor обычно создаются с использованием синтаксиса Razor,
естественного сочетания HTML и C#. Компоненты Razor похожи на Razor Pages и
представления MVC тем, что они используют Razor. В отличие от страниц и
представлений, которые созданы на базе модели "запрос и ответ", компоненты
используются исключительно для обработки компоновки пользовательского
интерфейса.
gRPC
gRPC :
это популярная высокопроизводительная платформа RPC (удаленный вызов
процедур).
Для разработки API используется подход, при котором сначала создается
контракт.
Использует современные технологии, такие как:
HTTP/2 для транспортировки;
буферы протоколов в качестве языка описания интерфейса;
формат двоичной сериализации.
Предоставляет следующие возможности:
Проверка подлинности
двунаправленная потоковая передача и управление потоком;
отмена и время ожидания.
К функциям gRPC в ASP.NET Core 3.0 относятся:
Grpc.AspNetCore.
Платформа ASP.NET Core для размещения служб gRPC.
gRPC в ASP.NET Core поддерживает интеграцию со стандартными
возможностями ASP.NET Core, такими как ведение журнала, внедрение
зависимостей, проверка подлинности и авторизация.
Grpc.Net.Client.
Клиент gRPC для .NET Core, созданный на основе знакомого
клиента HttpClient .
Grpc.Net.ClientFactory.
Интеграция клиента gRPC с HttpClientFactory .
Дополнительные сведения см. в статье Общие сведения о gRPC на .NET.
SignalR
Инструкции по миграции см. в разделе об обновлении кода SignalR. Теперь SignalR
использует System.Text.Json для сериализации и десериализации сообщений JS.
Инструкции по восстановлению сериализатора на основе Newtonsoft.Json см. в
разделе Switch to Newtonsoft.Json (Переключение на Newtonsoft.JSON).
В клиентах JavaScript и .NET для SignalR добавлена поддержка автоматического
повторного подключения. По умолчанию клиент пытается немедленно заново
подключиться и повторить попытку через 2, 10 и 30 секунд при необходимости.
Если клиент успешно повторно подключается, он получает новый идентификатор
подключения. Автоматическое повторное подключение необходимо явно
выбирать:
JavaScript
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chathub")
.withAutomaticReconnect()
.build();
Интервалы повторного подключения можно указать, передав массив длительности
в миллисекундах:
JavaScript
.withAutomaticReconnect([0, 3000, 5000, 10000, 15000, 30000])
//.withAutomaticReconnect([0, 2000, 10000, 30000]) The default intervals.
Пользовательскую реализацию можно передать для полного управления
интервалами повторного подключения.
Если повторное подключение не удается выполнить после последнего интервала
повторного подключения:
клиент считает, что подключение находится в автономном режиме;
клиент прекращает попытки повторного подключения.
Во время повторных попыток подключения обновите пользовательский интерфейс
приложения, чтобы уведомить пользователя о попытке повторного подключения.
Чтобы обеспечить возможность отправлять отзывы о пользовательском
интерфейсе при прерывании подключения, в API клиента SignalR добавлены
следующие обработчики событий:
onreconnecting : дает разработчикам возможность отключить
пользовательский интерфейс или сообщить пользователям о том, что
приложение находится в автономном режиме.
onreconnected . Предоставляет разработчикам возможность обновлять
пользовательский интерфейс после повторного установления соединения.
Следующий код использует onreconnecting для обновления пользовательского
интерфейса при попытке подключения:
JavaScript
connection.onreconnecting((error) => {
const status = `Connection lost due to error "${error}". Reconnecting.`;
document.getElementById("messageInput").disabled = true;
document.getElementById("sendButton").disabled = true;
document.getElementById("connectionStatus").innerText = status;
});
Следующий код использует onreconnected для обновления пользовательского
интерфейса при подключении:
JavaScript
connection.onreconnected((connectionId) => {
const status = `Connection reestablished. Connected.`;
document.getElementById("messageInput").disabled = false;
document.getElementById("sendButton").disabled = false;
document.getElementById("connectionStatus").innerText = status;
});
SignalR 3.0 и более поздних версий предоставляется пользовательский ресурс для
обработчиков авторизации, когда методу концентратора требуется авторизация.
Ресурс является экземпляром HubInvocationContext . HubInvocationContext включает:
HubCallerContext
Имя вызываемого метода концентратора.
Аргументы для метода концентратора.
Рассмотрим следующий пример приложения чата, разрешающего вход нескольких
организаций с помощью Azure Active Directory. Любой пользователь с учетной
записью Майкрософт может войти в чат, но запрещать пользователям принимать
участие в обсуждениях или просматривать журналы чата пользователей могут
только члены владеющей организации. Приложение может ограничивать
определенные функции для определенных пользователей.
C#
public class DomainRestrictedRequirement :
AuthorizationHandler<DomainRestrictedRequirement, HubInvocationContext>,
IAuthorizationRequirement
{
protected override Task
HandleRequirementAsync(AuthorizationHandlerContext context,
DomainRestrictedRequirement requirement,
HubInvocationContext resource)
{
if (context.User?.Identity?.Name == null)
{
return Task.CompletedTask;
}
if (IsUserAllowedToDoThis(resource.HubMethodName,
context.User.Identity.Name))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
private bool IsUserAllowedToDoThis(string hubMethodName, string
currentUsername)
{
if (hubMethodName.Equals("banUser",
StringComparison.OrdinalIgnoreCase))
{
return currentUsername.Equals("bob42@jabbr.net",
StringComparison.OrdinalIgnoreCase);
}
return currentUsername.EndsWith("@jabbr.net",
StringComparison.OrdinalIgnoreCase));
}
}
В приведенном выше коде DomainRestrictedRequirement служит пользовательским
IAuthorizationRequirement . Так как параметр ресурса HubInvocationContext
передается, внутренняя логика может выполнять следующее:
проверять контекст, в котором вызывается концентратор;
принимать решения о разрешении пользователю выполнять отдельные
методы концентратора.
Отдельные методы концентратора можно пометить именем политики, которую
проверяет код во время выполнения. Когда клиенты пытаются вызвать отдельные
методы концентратора, обработчик DomainRestrictedRequirement запускается и
управляет доступом к методам. В зависимости от того, как
DomainRestrictedRequirement управляет доступом:
все вошедшие в систему пользователи могут вызывать метод SendMessage ;
просматривать журналы пользователей могут только пользователи,
выполнившие вход с помощью адреса электронной почты @jabbr.net ;
запретить пользователям участвовать в обсуждениях могут только члены
bob42@jabbr.net .
C#
[Authorize]
public class ChatHub : Hub
{
public void SendMessage(string message)
{
}
[Authorize("DomainRestricted")]
public void BanUser(string username)
{
}
[Authorize("DomainRestricted")]
public void ViewUserHistory(string username)
{
}
}
Для создания политики DomainRestricted может потребоваться сделать следующее:
добавить новую политику в Startup.cs ;
предоставить настраиваемое требование DomainRestrictedRequirement в
качестве параметра;
зарегистрировать DomainRestricted с помощью ПО промежуточного слоя
авторизации.
C#
services
.AddAuthorization(options =>
{
options.AddPolicy("DomainRestricted", policy =>
{
policy.Requirements.Add(new DomainRestrictedRequirement());
});
});
Концентраторы SignalR используют маршрутизацию конечных точек. Подключение
концентратора SignalR было ранее выполнено явным образом:
C#
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("hubs/chat");
});
В предыдущей версии разработчикам требовалось подключать контроллеры,
страницы Razor и концентраторы в различных местах. В результате явного
подключения возникает последовательность практически идентичных сегментов
маршрутизации:
C#
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("hubs/chat");
});
app.UseRouting(routes =>
{
routes.MapRazorPages();
});
Концентраторы SignalR 3.0 можно маршрутизировать через конечные точки. При
такой маршрутизации, как правило, можно настроить всю маршрутизацию в
UseRouting .
C#
app.UseRouting(routes =>
{
routes.MapRazorPages();
routes.MapHub<ChatHub>("hubs/chat");
});
С SignalR ASP.NET Core 3.0 добавлены новые возможности.
потоковая передача между клиентом и сервером. При потоковой передаче между
клиентом и сервером методы на стороне сервера могут принимать экземпляры
IAsyncEnumerable<T> или ChannelReader<T> . В следующем примере C# метод
UploadStream в концентраторе получит поток строк от клиента:
C#
public async Task UploadStream(IAsyncEnumerable<string> stream)
{
await foreach (var item in stream)
{
// process content
}
}
Клиентские приложения .NET могут передавать экземпляр IAsyncEnumerable<T> или
ChannelReader<T> в качестве аргумента stream для метода концентратора
UploadStream , приведенного выше.
После завершения цикла for и завершения работы локальной функции
отправляется завершение потока:
C#
async IAsyncEnumerable<string> clientStreamData()
{
for (var i = 0; i < 5; i++)
{
var data = await FetchSomeData();
yield return data;
}
}
await connection.SendAsync("UploadStream", clientStreamData());
Клиентские приложения JavaScript используют SignalR Subject (или субъект RxJS )
для аргумента stream в приведенном выше методе UploadStream Hub.
JavaScript
let subject = new signalR.Subject();
await connection.send("StartStream", "MyAsciiArtStream", subject);
Код JavaScript может использовать метод subject.next для обработки строк по
мере их записи и готовности к отправке на сервер.
JavaScript
subject.next("example");
subject.complete();
С помощью кода, подобного двум предыдущим фрагментам, можно создать
потоковую передачу в реальном времени.
Новая сериализация JSON
ASP.NET Core 3.0 теперь по умолчанию использует System.Text.Json для
сериализации JSON:
асинхронно считывает и записывает JSON;
оптимизирован для текста UTF-8;
предоставляет более высокую производительность, чем Newtonsoft.Json .
Сведения о добавлении Json.NET в ASP.NET Core 3.0 см. в разделе Добавление
поддержки формата JSON на основе Newtonsoft.Json.
Новые директивы Razor
Следующий список содержит новые директивы Razor.
@attribute: Директива @attribute применяет этот атрибут к классу созданной
страницы или представления. Например, @attribute [Authorize] .
@implements: Директива @implements реализует интерфейс для созданного
класса. Например, @implements IDisposable .
IdentityServer4 поддерживает проверку
подлинности и авторизацию для веб-API и
одностраничных приложений
ASP.NET Core 3.0 обеспечивает проверку подлинности в одностраничных
приложениях с помощью поддержки авторизации веб-API. ASP.NET Core Identity
для проверки подлинности и хранения данных пользователей объединяется с
IdentityServer4
для реализации OpenID Connect.
IdentityServer4 — это платформа OpenID Connect и OAuth 2.0 для ASP.NET Core 3.0.
Она обеспечивает следующие функции безопасности:
Проверка подлинности как услуга (AaaS)
Единый вход (SSO) для нескольких типов приложений
Контроль доступа для API
Шлюз федерации
Дополнительные сведения см. в документации по IdentityServer4
или статье
Проверка подлинности и авторизация для одностраничных приложений.
Проверка подлинности Kerberos и проверка
подлинности с помощью сертификата
Для проверки подлинности с помощью сертификата требуется:
настройка сервера для принятия сертификатов;
добавление ПО промежуточного слоя для проверки подлинности в
Startup.Configure ;
добавление службы проверки подлинности с помощью сертификатов в
Startup.ConfigureServices .
C#
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate();
// Other service configuration removed.
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
// Other app configuration removed.
}
Параметры проверки подлинности с помощью сертификата включают следующие
возможности:
принятие самозаверяющих сертификатов;
проверка отзыва сертификатов;
проверка наличия в предложенном сертификате правильных флагов
использования.
Субъект-пользователь по умолчанию создается на основе свойств сертификата.
Субъект-пользователь содержит событие, которое позволяет дополнить или
заменить субъект. Дополнительные сведения см. в статье Настройка проверки
подлинности по сертификату в ASP.NET Core.
Проверка подлинности Windows теперь предусмотрена для Linux и macOS. В
предыдущих версиях аутентификация Windows была доступна только для IIS и
HTTP.sys. В ASP.NET Core 3.0 Kestrel может использовать Negotiate, Kerberos и NTLM
в Windows, Linux и macOS для узлов, присоединенных к домену Windows.
Поддержка в Kestrel этих схем проверки подлинности реализована в пакете
Microsoft.AspNetCore.Authentication.Negotiate NuGet . Как и в других службах
проверки подлинности, настройте приложение проверки подлинности во всей
организации, а затем настройте службу:
C#
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
// Other service configuration removed.
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
// Other app configuration removed.
}
Требования к узлу:
На узлах Windows имена субъектов-служб должны быть добавлены в учетную
запись пользователя, где размещается приложение.
Компьютеры Linux и macOS должны быть присоединены к домену.
Имена субъектов-служб необходимо создать для веб-процесса.
Файлы Keytab необходимо создать и настроить на компьютере узла.
Дополнительные сведения см. в статье Настройка проверки подлинности Windows
в ASP.NET Core.
Изменения шаблонов
В шаблонах веб-интерфейса (Razor Pages, MVC с контроллером и представлениями)
удалены следующие элементы.
Пользовательского интерфейса согласия на файлы cookie больше нет.
Сведения о том, как включить функцию согласия для файлов cookie в
приложении, созданном на основе шаблона ASP.NET Core 3.0, см. в статье
Поддержка Общего регламента по защите данных (GDPR) в ASP.NET Core.
Для ссылки на скрипты и связанные статические ресурсы теперь используются
локальные файлы, а не CDN. Дополнительные сведения см. в статье В версии
3.0 для ссылки на скрипты и связанные статические ресурсы теперь
используются локальные файлы, а не CDN в зависимости от текущей среды
(dotnet/AspNetCore.Docs № 14350) .
Шаблон Angular обновлен для использования Angular 8.
По умолчанию для шаблона библиотеки классов Razor (RCL) используется
разработка компонентов Razor. Новый параметр шаблона в Visual Studio
обеспечивает поддержку шаблонов для страниц и представлений. При создании
RCL на основе шаблона в командной оболочке передайте параметр --supportpages-and-views ( dotnet new razorclasslib --support-pages-and-views ).
Универсальный узел
Шаблоны ASP.NET Core 3.0 используют универсальный узел .NET в ASP.NET Core. В
предыдущих версиях использовался WebHostBuilder. Использование
универсального узла .NET Core (HostBuilder) обеспечивает лучшую интеграцию
приложений ASP.NET Core с другими серверными сценариями, не зависящими от
Интернета. Дополнительные сведения см. в разделе HostBuilder replaces
WebHostBuilder (HostBuilder заменяет WebHostBuilder).
Конфигурация узла
До выхода ASP.NET Core 3.0 переменные среды с префиксом ASPNETCORE_ были
загружены для конфигурации веб-узла. В 3.0 AddEnvironmentVariables используется
для загрузки переменных среды с префиксом DOTNET_ для конфигурации узла с
помощью CreateDefaultBuilder .
Изменения во внедрении через конструктор Startup
Универсальный узел поддерживает только следующие типы для внедрения через
конструктор Startup :
IHostEnvironment
IWebHostEnvironment
IConfiguration
Все службы по-прежнему можно непосредственно внедрять в метод
Startup.Configure в качестве аргументов. Дополнительные сведения см. в статье
Generic Host restricts Startup constructor injection
(Универсальный узел
ограничивает внедрение через конструктор Startup) (№ 353).
Kestrel
В конфигурацию Kestrel добавлена возможность миграции на универсальный
узел. В версии 3.0 ConfigureWebHostDefaults настраивается в построителе вебузлов, предоставляемом Kestrel.
Адаптеры подключений удалены из Kestrel и заменены ПО промежуточного
слоя подключения, которое похоже на ПО промежуточного слоя HTTP в
конвейере ASP.NET Core, но для подключений более низкого уровня.
Транспортный уровень Kestrel предоставляется как открытый интерфейс в
Connections.Abstractions .
Неоднозначность заголовков и конечных строк устранена путем
перемещения конечных заголовков в новую коллекцию.
Из-за таких интерфейсов API с синхронными операциями ввода-вывода, как
HttpRequest.Body.Read , часто возникает нехватка потоков, что приводит к
сбоям приложений. В 3.0 AllowSynchronousIO отключен по умолчанию.
Дополнительные сведения см. в статье Миграция с ASP.NET Core 2.2 на 3.0.
HTTP/2 включено по умолчанию.
В Kestrel для конечных точек HTTPS по умолчанию включен протокол HTTP/2.
Поддержка HTTP/2 для IIS или HTTP.sys включена, если поддерживается
операционной системой.
EventCounters по запросу
EventSource размещения, Microsoft.AspNetCore.Hosting , выдает следующие новые
типы EventCounter, связанные с входящими запросами:
requests-per-second
total-requests
current-requests
failed-requests
Маршрутизация конечных точек
Маршрутизация конечных точек, позволяющая платформам (например, MVC)
хорошо работать с ПО промежуточного слоя, усовершенствована:
порядок ПО промежуточного слоя и конечных точек можно настроить в
конвейере обработки запросов Startup.Configure ;
конечные точки и ПО промежуточного слоя хорошо используются с другими
технологиями на основе ASP.NET Core, такими как проверки
работоспособности;
конечные точки могут реализовывать политику, например CORS или
авторизацию, в ПО промежуточного слоя и MVC;
фильтры и атрибуты можно разместить в методах контроллеров.
Дополнительные сведения см. в статье Маршрутизация в ASP.NET Core.
Проверки работоспособности
Для проверок работоспособности используется маршрутизация конечных точек с
универсальным узлом. В Startup.Configure вызовите MapHealthChecks для
построителя конечной точки с URL-адресом конечной точки или относительным
путем:
C#
app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/health");
});
Конечные точки проверки работоспособности могут:
указать один или несколько разрешенных узлов или портов;
требовать авторизацию;
требовать CORS.
Дополнительные сведения см. в следующих статьях:
Миграция с ASP.NET Core 2.2.x на 3.0
Проверки работоспособности в ASP.NET Core
Каналы в HttpContext
Теперь можно читать текст запроса и писать текст ответа с помощью API
System.IO.Pipelines. Свойство HttpRequest.BodyReader предоставляет класс
PipeReader, который можно использовать для чтения текста запроса. Свойство
HttpResponse.BodyWriter предоставляет класс PipeWriter, который можно
использовать для записи текста ответа. HttpRequest.BodyReader — это аналог потока
HttpRequest.Body . HttpResponse.BodyWriter — это аналог потока HttpResponse.Body .
Улучшены сообщения об ошибках в IIS
Теперь при ошибках запуска во время размещения приложений ASP.NET Core в IIS
предоставляются более полные диагностические данные. Сведения об этих
ошибках передаются в журнал событий Windows с трассировками стека везде, где
это применимо. Кроме того, все предупреждения, ошибки и необработанные
исключения записываются в журнал событий Windows.
Служба рабочих ролей и пакет SDK для
рабочей роли
В .NET Core 3.0 появился новый шаблон приложения службы рабочих ролей. Этот
шаблон может служить отправной точкой для написания длительных приложений
служб в .NET Core.
Дополнительные сведения можно найти в разделе
.NET Core Workers as Windows Services
(Рабочие роли .NET Core в качестве
служб Windows)
Фоновые задачи с размещенными службами в ASP.NET Core
Размещение ASP.NET Core в службе Windows
Улучшения ПО промежуточного слоя
перенаправления заголовков
В предыдущих версиях ASP.NET Core вызвать UseHsts и UseHttpsRedirection было
проблематично при развертывании в Azure Linux или за любым обратным проксисервером, отличным от IIS. Исправление для предыдущих версий описано в
разделе Переадресация схемы для Linux и обратных прокси-серверов не IIS.
Этот сценарий исправлен в ASP.NET Core 3.0. Узел включает ПО промежуточного
слоя перенаправления заголовков, если переменной среды
ASPNETCORE_FORWARDEDHEADERS_ENABLED присвоено значение true . В образах
контейнера для ASPNETCORE_FORWARDEDHEADERS_ENABLED задано значение true .
Улучшения производительности
В ASP.NET Core 3.0 реализованы многочисленные улучшения, сокращающие
использование памяти и повышающие пропускную способность:
сокращение использования памяти при использовании встроенного
контейнера внедрения зависимостей для служб с заданной областью;
сокращение количества распределений на платформе, включая сценарии ПО
промежуточного слоя и маршрутизацию;
сокращение использования памяти для подключений WebSocket;
сокращение использования памяти и улучшение пропускной способности для
подключений по протоколу HTTPS;
новый оптимизированный и полностью асинхронный сериализатор JSON;
сокращение использования памяти и улучшения пропускной способности при
анализе формы.
ASP.NET Core 3.0 работает только в .NET
Core 3.0
Начиная с ASP.NET Core 3.0, .NET Framework больше не является поддерживаемой
целевой платформой. Проекты, предназначенные для .NET Framework, можно
полноценно использовать с помощью выпуска LTS .NET Core 2.1 . Большинство
пакетов, связанных с ASP.NET Core 2.1.x, будут поддерживаться неограниченно
после истечения трехлетнего периода LTS для .NET Core 2.1.
Сведения о миграции см. в статье Перенос кода в .NET Core из .NET Framework.
Использование общей платформы .NET Core
Для общей платформы ASP.NET Core 3.0, содержащейся в метапакете
Microsoft.AspNetCore.app, больше не требуется явный элемент <PackageReference />
в файле проекта. При использовании пакета SDK Microsoft.NET.Sdk.Web в файле
проекта автоматически создается ссылка на общую платформу:
XML
<Project Sdk="Microsoft.NET.Sdk.Web">
Сборки, удаленные из общей платформы
ASP.NET Core
Наиболее важные сборки, удаленные из общей платформы ASP.NET Core 3.0:
Newtonsoft.Json
(Json.NET). Сведения о добавлении Json.NET в ASP.NET
Core 3.0 см. в разделе Добавление поддержки формата JSON на основе
Newtonsoft.Json. В ASP.NET Core 3.0 внедрен System.Text.Json для чтения
формата JSON и записи в него. Дополнительные сведения см. в разделе Новая
сериализация JSON в этом документе.
Entity Framework Core
Полный список сборок, удаленных из общей платформы, см. в разделе Assemblies
being removed from Microsoft.AspNetCore.App 3.0
(Сборки, удаленные из
Microsoft.AspNetCore.App 3.0). Дополнительные сведения о мотивации для этого
изменения см. в статье Breaking changes to Microsoft.AspNetCore.App in 3.0
(Критические изменения в Microsoft.AspNetCore.App 3.0) и записи блога A first look
at changes coming in ASP.NET Core 3.0
ASP.NET Core 3.0).
(Первое знакомство с изменениями в
Новые возможности ASP.NET Core 2.2
Статья • 28.01.2023 • Чтение занимает 4 мин
В этой статье описываются наиболее важные изменения в ASP.NET Core 2.2 со
ссылками на соответствующую документацию.
Соглашения и анализаторы OpenAPI
OpenAPI (ранее Swagger) — это не зависящая от языка спецификация для описания
REST API. В экосистеме OpenAPI представлены средства для обнаружения,
тестирования и создания кода клиента в соответствии со спецификацией.
Поддержка создания и визуализации документов OpenAPI в ASP.NET Core MVC
обеспечивается за счет управляемых сообществом проектов, таких как NSwag
и
Swashbuckle.AspNetCore . В ASP.NET Core 2.2 представлены улучшенные средства
и возможности среды выполнения для создания документов OpenAPI.
Дополнительные сведения см. в следующих ресурсах:
Использование анализаторов веб-API
Использование соглашений веб-API
ASP.NET Core 2.2.0, предварительная версия 1. Соглашения и анализаторы
OpenAPI
Поддержка сведений о проблеме
В ASP.NET Core 2.1 появился объект ProblemDetails , основанный на спецификации
RFC 7807
и предназначенный для передачи сведений об ошибке в составе HTTP-
ответа. В версии 2.2 ProblemDetails является стандартным ответом для кодов
ошибок клиента в контроллерах с атрибутами ApiControllerAttribute .
IActionResult , возвращающий код состояния ошибки клиента (4xx), теперь
возвращает текст ProblemDetails . В результат также включается идентификатор
корреляции, который может использоваться для сопоставления ошибки с
помощью журналов запросов. В отношении ошибок клиентов ProducesResponseType
по умолчанию использует тип ответа ProblemDetails . Это описывается в выходных
данных Open API (Swagger), создаваемых с помощью NSwag или
Swashbuckle.AspNetCore.
Маршрутизация конечных точек
В ASP.NET Core 2.2 используется новая система маршрутизации конечных точек,
обеспечивающая оптимизированную диспетчеризацию запросов. Среди
изменений представлены новые члены для создания ссылок API и
преобразователи параметров маршрута.
Дополнительные сведения см. в следующих ресурсах:
Маршрутизация конечных точек в версии 2.2
Преобразователи параметров маршрута
(см. раздел Маршрутизация)
Различия между IRouter и маршрутизацией на основе конечных точек
Проверки работоспособности
Новая служба проверки работоспособности упрощает использование ASP.NET Core
в средах с обязательной проверкой работоспособности, таких как Kubernetes.
Служба проверки работоспособности включает ПО промежуточного слоя и набор
библиотек, которые определяют абстракцию IHealthCheck и службу.
Проверки работоспособности используются подсистемой балансировки нагрузки
или оркестратором контейнеров для быстрого определения того, насколько
эффективно система отвечает на запросы. Оркестратор контейнеров может
реагировать на неуспешную проверку работоспособности, остановив
последовательное развертывание или перезапустив контейнер. Подсистема
балансировки нагрузки может реагировать на результаты проверки
работоспособности путем перенаправления трафика от неисправного экземпляра
службы.
Проверки работоспособности предоставляются приложением в качестве конечной
точки HTTP, используемой системами мониторинга. Проверки работоспособности
можно настроить для различных сценариев мониторинга в реальном времени и
систем мониторинга. Проверки работоспособности интегрируются с проектом
BeatPulse . Это значительно упрощает добавление проверок в десятки
распространенных систем и зависимых компонентов.
Дополнительные сведения см. в статье Проверки работоспособности в ASP.NET
Core.
HTTP/2 в Kestrel
В ASP.NET Core 2.2 добавлена поддержка HTTP/2.
HTTP/2 является основной редакцией HTTP-протокола. В число важных функций
HTTP/2 входят следующие:
Поддержка сжатия заголовка.
Полностью мультиплексные потоки через одно соединение.
Версия HTTP/2 сохраняет семантику HTTP (например, методы и заголовки HTTP),
однако она заметно отличается от HTTP/1.x в отношении механизмов
кадрирования и передачи данных между клиентом и сервером.
В связи с этим изменением механизмов кадрирования серверам и клиентам
необходимо согласовывать используемую версию протокола. Согласование
протоколов на уровне приложений (Application-Layer Protocol Negotiation, ALPN) —
это расширение TLS, с помощью которого сервер и клиент могут согласовать
версию протокола в рамках процесса подтверждения TLS. Даже при наличии у
сервера и клиента сведений о версии протокола все основные браузеры
поддерживают ALPN как единственное средство для установления соединения по
протоколу HTTP/2.
Дополнительные сведения см. в статье Поддержка HTTP/2.
Конфигурация Kestrel
В предыдущих версиях ASP.NET Core настройка параметров Kestrel осуществлялась
путем вызова UseKestrel . В версии 2.2 для настройки параметров Kestrel
вызывается метод ConfigureKestrel в построителе узла. Это изменение позволяет
устранить проблему с порядком регистрации IServer для внутрипроцессного
размещения. Дополнительные сведения см. в следующих ресурсах:
Устранение конфликта UseIIS
Настройка параметров сервера Kestrel путем настройки Kestrel
Внутрипроцессное размещение в службах
IIS
В более ранних версиях ASP.NET Core службы IIS выступают в качестве обратного
прокси-сервера. В версии 2.2 модуль ASP.NET Core может загружать CoreCLR и
размещать приложение в рабочем процессе IIS (w3wp.exe). Внутрипроцессное
размещение позволяет оптимизировать производительность и диагностику при
работе со службами IIS.
Дополнительные сведения см. в статье Внутрипроцессное размещение для служб
IIS.
Клиент Java SignalR
В ASP.NET Core 2.2 представлен клиент Java для SignalR. Этот клиент поддерживает
подключение к серверу SignalR ASP.NET Core из кода Java, в том числе из
приложений Android.
Дополнительные сведения см. в статье о клиенте Java для SignalR ASP.NET Core.
Усовершенствования CORS
В предыдущих версия ASP.NET Core ПО промежуточного слоя CORS обеспечивало
отправку заголовков Accept , Accept-Language , Content-Language и Origin
независимо от значений, настроенных в CorsPolicy.Headers . В версии 2.2
соблюдение политики ПО промежуточного слоя CORS возможно только в том
случае, если отправляемые в Access-Control-Request-Headers заголовки точно
соответствуют заголовкам, указанным в WithHeaders .
Дополнительные сведения см. в статье ПО промежуточного слоя CORS.
Сжатие ответов
ASP.NET Core 2.2 поддерживает сжатие ответов с использованием формата сжатия
Brotli .
Дополнительные сведения см. в статье Поддержка сжатия Brotli для сжатия ответов
в ПО промежуточного слоя.
Шаблоны проектов
Шаблоны веб-проектов ASP.NET Core обновлены до Bootstrap 4
и Angular 6
.
Новое оформление выглядит проще и позволяет более эффективно отображать
важные структуры приложения.
Производительность проверок
Модель MVC имеет расширяемую гибкую систему проверки, позволяющую на
основе запросов определять, какие средства проверки применяются к конкретной
модели. Эта возможность отлично подходит для разработки сложных поставщиков
служб проверки. Тем не менее в большинстве распространенных случаев
приложение использует только встроенные средства проверки и не нуждается в
таких дополнительных возможностях. К встроенным средствам проверки относятся
заметки к данным, такие как [Required] и [StringLength], а также IValidatableObject .
В ASP.NET Core 2.2 модель MVC может обойти проверку в случаях, когда
определяется, что для указанного графа модели проверка не требуется. Пропуск
проверки приводит к существенным улучшениям для моделей, в которых
отсутствуют или не могут использоваться средства проверки. К ним относятся такие
объекты, как коллекции примитивов (например, byte[] , string[] ,
Dictionary<string, string> ) или сложные графы объектов с небольшим
количеством средств проверки.
Производительность клиента HTTP
В ASP.NET Core 2.2 была повышена производительность SocketsHttpHandler за счет
уменьшения числа конфликтов, связанных с блокировкой пула подключений. Была
увеличена пропускная способность приложений, которые выполняют множество
исходящих запросов HTTP, таких как некоторые архитектуры микрослужб. Под
нагрузкой пропускная способность HttpClient может повышаться до 60 % в Linux и
до 20 % в Windows.
Дополнительные сведения см. в статье, посвященной запросам на вытягивание,
благодаря которым удалось добиться этого улучшения
.
Дополнительные сведения
Полный список изменений см. в статье Заметки о выпуске ASP.NET Core 2.2 .
Новые возможности ASP.NET Core 2.1
Статья • 28.01.2023 • Чтение занимает 5 мин
В этой статье описываются наиболее важные изменения в ASP.NET Core 2.1 со
ссылками на соответствующую документацию.
SignalR
SignalR переписан для ASP.NET Core 2.1.
В ASP.NET Core SignalR внесен ряд усовершенствований:
Упрощенная модель горизонтального масштабирования.
Новый клиент JavaScript без зависимости jQuery.
Новый компактный двоичный протокол на базе MessagePack.
Поддержка пользовательских протоколов.
Новая модель потокового ответа.
Поддержка клиентов на базе только протокола WebSocket.
Дополнительные сведения см. в статье об ASP.NET SignalR.
Библиотеки класса Razor
В ASP.NET Core 2.1 проще создать и включить пользовательский интерфейс на
основе Razor в библиотеку, а затем использовать его сразу в нескольких проектах.
Новый пакет SDK для Razor позволяет создавать файлы Razor в проекте библиотеки
классов, который можно поместить в пакет NuGet. Представления и страницы в
библиотеках обнаруживаются автоматически и могут переопределяться
приложением. Благодаря интеграции компиляции Razor в сборку:
Время запуска приложения значительно сократилось.
Быстрые обновления для представлений и страниц Razor во время
выполнения по-прежнему доступны в рамках рабочего процесса
последовательной разработки.
Дополнительные сведения см. в разделе Создание многоразового
пользовательского интерфейса с помощью проекта библиотеки классов Razor.
Библиотека пользовательского интерфейса
Identity и формирование шаблонов
ASP.NET Core 2.1 предоставляет ASP.NET Core Identity как библиотеку классов Razor.
Приложения, включающие Identity, могут применить новый шаблон Identity для
выборочного добавления исходного кода из библиотеки классов Identity (RCL) для
Razor. Вы можете создать исходный код, чтобы изменить код и тем самым
изменить поведение. Например, вы можете указать шаблону создать код,
используемый при регистрации. Созданный код имеет приоритет над тем же
кодом в RCL для Identity.
Приложения, которые не включают проверку подлинности, могут применить
шаблон Identity, чтобы добавить пакет RCL для Identity. Вы можете выбрать, какой
код Identity будет создан.
Дополнительные сведения см. в разделе Шаблоны Identity в проектах ASP.NET Core.
HTTPS
Учитывая повышенное внимание к безопасности и конфиденциальности, важно
использовать HTTPS для веб-приложений. В Интернете применение HTTPS все
чаще становится обязательным. Сайты, не использующие HTTPS, считаются
небезопасными. Браузеры (Chromium, Mozilla) требуют использования вебкомпонентов в контексте безопасности. Общий регламент по защите данных
предписывает использовать HTTPS для защиты конфиденциальности
пользователей. В то время как использование HTTPS в рабочей среде становится
обязательным, применение HTTPS при разработке помогает предотвратить
проблемы с развертыванием (например, небезопасные ссылки). ASP.NET Core 2.1
включает ряд усовершенствований, упрощающих использование HTTPS в среде
разработки и настройку HTTPS в рабочей среде. Дополнительные сведения см. в
разделе Обязательное использование HTTPS.
По умолчанию включено
Чтобы вам было проще разрабатывать безопасные веб-сайты, протокол HTTPS
теперь включен по умолчанию. Начиная с версии 2.1, Kestrel ожидает передачи
данных по адресу https://localhost:5001 , если присутствует локальный сертификат
разработки. Сертификат разработки создается, когда:
Вы впервые запускаете пакет SDK для .NET Core.
Вы создаете его вручную с помощью нового средства dev-certs .
Запустите dotnet dev-certs https --trust , чтобы установить доверие для
сертификата.
Перенаправление и принудительное применение
HTTPS
Веб-приложения обычно используют оба протокола — HTTP и HTTPS, но
перенаправляют весь трафик HTTP на HTTPS. В версии 2.1 появилось специальное
ПО промежуточного слоя, которое интеллектуально перенаправляет трафик на
HTTPS, учитывая конфигурацию или порты связанного сервера.
Использование протокола HTTPS также можно гарантировать с помощью
протокола HTTP Strict Transport Security (HSTS). HSTS указывает браузерам всегда
переходить на сайт через HTTPS. В ASP.NET Core 2.1 добавлено ПО
промежуточного слоя HSTS, которое поддерживает параметры для максимального
возраста, дочерних доменов и списка предварительной загрузки HSTS.
Конфигурация для рабочей среды
В рабочей среде необходимо явно настроить HTTPS. В версии 2.1 добавлена схема
конфигурации по умолчанию для настройки HTTPS для Kestrel. Можно настроить
приложения, чтобы они использовали:
Несколько конечных точек, включая URL-адреса. Дополнительные сведения
см. в статье Реализация веб-сервера Kestrel: конфигурация конечных точек.
Сертификат для использования HTTPS из файла на диске или из хранилища
сертификатов.
Общий регламент по защите данных
ASP.NET Core предоставляет API-интерфейсы и шаблоны, которые помогают
соответствовать требованиям Общего регламента по защите данных в ЕС .
Дополнительные сведения см. в разделе Поддержка общего регламента по защите
данных в ASP.NET Core. В примере приложения
показано, как использовать и
тестировать большинство точек расширения для общего регламента по защите
данных и API-интерфейсов, добавленных в шаблоны ASP.NET Core 2.1.
Интеграционные тесты
Добавлен новый пакет, который оптимизирует создание и выполнение тестов.
Пакет Microsoft.AspNetCore.Mvc.Testing
выполняет следующие задачи:
Копирует файл зависимостей (*.deps) из протестированного приложения в
папку bin тестового проекта.
Задает корневую папку содержимого в корневой папке проекта тестируемого
приложения, чтобы можно было найти статические файлы и страницы или
представления при выполнении тестов.
Предоставляет класс WebApplicationFactory<TEntryPoint> для оптимизации
начальной загрузки тестируемого приложения на TestServer.
В следующем тесте используется xUnit
для проверки того, что страница индексов
загружается с кодом состояния успешного выполнения и правильным заголовком
Content-Type:
C#
public class BasicTests
: IClassFixture<WebApplicationFactory<RazorPagesProject.Startup>>
{
private readonly HttpClient _client;
public BasicTests(WebApplicationFactory<RazorPagesProject.Startup>
factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task GetHomePage()
{
// Act
var response = await _client.GetAsync("/");
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
}
Дополнительные сведения см. в разделе Интеграционные тесты.
[ApiController], ActionResult<T>
В ASP.NET Core 2.1 добавлены новые соглашения программирования, с которыми
проще создавать чистые и выразительные веб-API. ActionResult<T> — это новый
тип, который разрешает приложениям возвращать либо тип ответа, либо любой
другой результат действия (аналогично IActionResult), при этом по-прежнему
указывая тип ответа. Также был добавлен атрибут [ApiController] , с помощью
которого можно принять соглашения и поведения для веб-API.
Дополнительные сведения см. в разделе Сборка веб-API с использованием ASP.NET
Core.
IHttpClientFactory
ASP.NET Core 2.1 содержит новую службу IHttpClientFactory , которая упрощает
настройку и использование экземпляров HttpClient в приложениях. В HttpClient
уже существует концепция делегирования обработчиков, которые можно связать
друг с другом для исходящих HTTP-запросов. Фабрика:
Упрощает регистрацию экземпляров HttpClient каждого именованного
клиента.
Реализует обработчик Polly, который позволяет использовать политики Polly
для повтора, размыкателя цепи и т. д.
Дополнительные сведения см. в разделе Инициирование HTTP-запросов.
Конфигурация транспорта Kestrel Libuv.
После выпуска ASP.NET Core 2.1 транспорт Kestrel по умолчанию основан не на
Libuv, а на управляемых сокетах. Дополнительные сведения см. в статье Реализация
веб-сервера Kestrel: конфигурация транспорта Libuv.
Построитель универсальных узлов
Добавлен построитель универсальных узлов ( HostBuilder ). Построитель можно
использовать для приложений, которые не обрабатывают HTTP-запросы (обмен
сообщениями, фоновые задачи и т. д.).
Дополнительные сведения см. в разделе Универсальный узел .NET.
Обновленные шаблоны SPA
Обновлены шаблоны одностраничных приложений для Angular, React и React с
Redux. Теперь можно использовать стандартные структуры проектов и создавать
системы для каждой платформы.
Шаблон Angular основан на Angular CLI, а шаблоны React основаны на create-reactapp.
Дополнительные сведения можно найти в разделе
Использование Angular с ASP.NET Core
Использование React с ASP.NET Core
Использование шаблона проекта React и Redux с ASP.NET Core
Поиск в Razor Pages активов Razor
В версии 2.1 Razor Pages ищет ресурсы Razor (например, макеты и частично
выполненные строки) в следующих каталогах в указанном порядке.
1. Текущая папка Pages.
2. /Pages/Shared/
3. /Views/Shared/
Razor Pages в области
Razor Pages теперь поддерживает области. Чтобы увидеть пример областей,
создайте новое веб-приложение Razor Pages с отдельными учетными записями
пользователей. Веб-приложение Razor Pages с отдельными учетными записями
пользователей включает /Areas/Identity/Pages.
Совместимая версия MVC
Метод SetCompatibilityVersion позволяет приложению принимать или отклонять
потенциально критические изменения в поведении, появившиеся в ASP.NET Core
MVC 2.1 или более поздних версий.
Дополнительные сведения см. в статье Совместимая версия для ASP.NET Core MVC.
Миграция с 2.0 на 2.1
См. раздел Миграция с ASP.NET Core 2.0 на 2.1.
Дополнительные сведения
Полный список изменений см. в статье Заметки о выпуске ASP.NET Core 2.1 .
Новые возможности ASP.NET Core 2.0
Статья • 10.01.2023 • Чтение занимает 5 мин
В этой статье описываются наиболее важные изменения в ASP.NET Core 2.0 со
ссылками на соответствующую документацию.
Razor Pages
Razor Pages — это новая функция платформы MVC ASP.NET Core, которая делает
создание кодов сценариев для страниц проще и эффективнее.
Дополнительные сведения см. в следующей вводной статье и учебнике.
Введение в Razor Pages
Начало работы с Razor Pages
Метапакет ASP.NET Core
Новый метапакет ASP.NET Core включает все пакеты, выпущенные и
поддерживаемые командами ASP.NET Core и Entity Framework Core, а также
внутренние и сторонние зависимости. Вам больше не придется выбирать
отдельный пакет компонентов ASP.NET Core. Все компоненты входят в пакет
Microsoft.AspNetCore.All . Шаблоны по умолчанию используют именно этот пакет.
Дополнительные сведения см. в статье Метапакет Microsoft.AspNetCore.All для
ASP.NET Core 2.0.
Хранилище среды выполнения
Приложения, использующие метапакет Microsoft.AspNetCore.All , автоматически
получают все преимущества нового хранилища среды выполнения .NET Core.
Хранилище содержит все ресурсы среды выполнения, необходимые для запуска
приложений ASP.NET Core 2.0. При использовании метапакета
Microsoft.AspNetCore.All приложение не развертывает никакие ресурсы из
указанных по ссылке пакетов NuGet ASP.NET Core, так как эти пакеты уже
присутствуют в целевой системе. Кроме того, для сокращения времени запуска
приложения ресурсы в хранилище среды выполнения подвергаются
предварительной компиляции.
Дополнительные сведения см. в статье Хранилище среды выполнения.
.NET Standard 2.0
Пакеты ASP.NET 2.0 предназначены для .NET Standard 2.0. На эти пакеты могут
ссылаться другие библиотеки .NET Standard 2.0; кроме того, они могут выполняться
в реализациях .NET, совместимых с .NET Standard 2.0, включая .NET Core 2.0 и .NET
Framework 4.6.1.
Метапакет Microsoft.AspNetCore.All работает только с .NET Core 2.0, так как
предназначен для использования с хранилищем среды выполнения .NET Core 2.0.
Изменения в конфигурации
В ASP.NET Core 2.0 экземпляр IConfiguration добавляется в контейнер служб по
умолчанию. IConfiguration в контейнере служб упрощает для приложений задачу
получения значений конфигурации из контейнера.
Сведения о состоянии плановой документации см. в статье о проблемах GitHub .
Изменения в ведении журналов
В ASP.NET 2.0 Core ведение журнала по умолчанию включено в систему внедрения
зависимостей. Добавить поставщиков и настроить фильтрацию можно в файле
Program.cs , а не файле Startup.cs . А ILoggerFactory по умолчанию поддерживает
такой способ фильтрации, который позволяет использовать один гибкий подход и
для перекрестной фильтрации по поставщикам, и для фильтрации по отдельному
поставщику.
Дополнительные сведения см. в статье Введение в ведение журналов.
Изменения в проверке подлинности
Новая модель проверки подлинности облегчает настройку проверки подлинности
для приложения с использованием внедрения зависимостей.
Доступны новые шаблоны для настройки проверки подлинности в вебприложениях и веб-API с использованием Azure AD B2C
.
Сведения о состоянии плановой документации см. в статье о проблемах GitHub .
Обновление Identity
Мы упростили сборку защищенных веб-API с использованием Identity в
ASP.NET Core 2.0. Теперь маркеры доступа для обращения к веб-API можно
получить с помощью библиотеки проверки подлинности Microsoft (MSAL) .
Дополнительные сведения об изменениях в проверке подлинности в версии 2.0 см.
в следующих ресурсах.
Подтверждение учетной записи и восстановление пароля в ASP.NET Core
Включение создания QR-кодов для приложений проверки подлинности в
ASP.NET Core
Миграция проверки подлинности и в ASP.NET Core 2.0
Шаблоны SPA
Доступны шаблоны проектов одностраничных приложений (Single Page Application,
SPA) Angular, Aurelia, Knockout.js, React.js и React.js с Redux. Шаблон Angular
обновлен до Angular 4. Шаблоны Angular и React доступны по умолчанию.
Сведения о получении других шаблонов см. в разделе Создание проекта SPA.
Сведения о сборке SPA в ASP.NET Core см. в статье Создание одностраничных
приложений в ASP.NET Core с помощью служб JavaScript.
Улучшения Kestrel
В веб-сервере Kestrel реализованы новые функции, необходимые серверу с
выходом в Интернет. Добавлено несколько параметров конфигурации
ограничений для сервера в новое свойство Limits класса KestrelServerOptions . Вы
можете добавлять следующие ограничения:
максимальное число клиентских подключений;
максимальный размер текста запроса;
минимальная скорость передачи данных в тексте запроса.
Дополнительные сведения см. в статье Реализация веб-сервера в ASP.NET Core.
WebListener переименован в HTTP.sys
Пакеты Microsoft.AspNetCore.Server.WebListener и Microsoft.Net.Http.Server
объединены в новый пакет Microsoft.AspNetCore.Server.HttpSys . Соответственно
обновлены и пространства имен.
Дополнительные сведения см. в статье Реализация веб-сервера HTTP.sys в ASP.NET
Core.
Расширенная поддержка заголовков HTTP
Теперь при использовании MVC для передачи FileStreamResult или
FileContentResult можно указывать дату ETag или LastModified для передаваемого
содержимого. Для возвращаемого содержимого эти значения можно задать,
используя следующий код:
C#
var data = Encoding.UTF8.GetBytes("This is a sample text from a binary
array");
var entityTag = new EntityTagHeaderValue("\"MyCalculatedEtagValue\"");
return File(data, "text/plain", "downloadName.txt", lastModified:
DateTime.UtcNow.AddSeconds(-5), entityTag: entityTag);
Файл, возвращаемый вашим посетителям, будет иметь соответствующие заголовки
HTTP для значений ETag и LastModified .
Если посетители вашего приложение запросят содержимое с заголовком типа
"Запрос диапазона", ASP.NET Core распознает и обработает этот заголовок.
Запрошенное содержимое может доставляться частично, в случае чего ASP.NET
Core соответствующим образом пропустит и вернет только запрошенный набор
байтов. При этом прописывать в методах специальные обработчики для адаптации
или реализации этой функции не требуется, все будет сделано автоматически.
Запуск внешнего размещения и Application
Insights
Среды внешнего размещения теперь внедряют зависимости дополнительных
пакетов и выполняют код во время запуска приложения; при этом приложению не
нужно явно принимать зависимость или вызывать какие-либо методы. Эту
функцию можно использовать для того, чтобы выделить в определенных средах
какие-то уникальные для них компоненты без предварительной настройки самого
приложения.
В ASP.NET Core 2.0 эта функция используется для автоматического включения
диагностики Application Insights при отладке в Visual Studio и (после включения)
при запуске службы приложений Azure. В связи с этим шаблоны проектов больше
не добавляют пакеты и код Application Insights по умолчанию.
Сведения о состоянии плановой документации см. в статье о проблемах GitHub .
Автоматическое использование маркеров
защиты от подделки
ASP.NET Core всегда помогает в создании HTML-кода для содержимого, но в новой
версии сделан еще один шаг к предотвращению атак с подделкой межсайтовых
запросов (XSRF). Теперь ASP.NET Core будет выдавать маркеры защиты от подделки
по умолчанию и проверять их при отправке форм и выполнении страниц без
дополнительной конфигурации.
Дополнительные сведения см. на странице Предотвращение атак с
использованием подделки межсайтовых запросов (XSRF/CSRF) в ASP.NET Core.
Автоматическая предварительная
компиляция
При публикации по умолчанию включается предварительная компиляция
представления Razor, что сокращает размер выходных данных публикации и время
запуска приложения.
Дополнительные сведения см. в статье Компиляция и предварительная
компиляция представлений в ASP.NET Core.
Поддержка C# 7.1 в Razor
Для работы с новым компилятором Roslyn был обновлен и обработчик
представлений Razor. Добавлена поддержка таких функций C# 7.1 как выражения
по умолчанию, выводимые имена кортежей и сопоставление шаблонов с
универсальными шаблонами. Чтобы использовать C# 7.1 в проекте, добавьте в
файл проекта следующее свойство и перезагрузите решение.
XML
<LangVersion>latest</LangVersion>
Сведения о состоянии компонентов C# 7.1 см. в статье о репозитории Roslyn
GitHub .
Изменения в другой документации к
версии 2.0
Профили публикации Visual Studio для развертывания приложений ASP.NET
Core
Управление ключами
Настройка проверки подлинности Facebook
Настройка проверки подлинности Twitter
Настройка проверки подлинности Google
Настройка проверки подлинности учетной записи Майкрософт
Руководство по миграции
Инструкции по миграции приложений с ASP.NET Core 1.x в ASP.NET 2.0 см. в
следующих ресурсах.
Миграция с ASP.NET Core 1.x на ASP.NET Core 2.0
Миграция проверки подлинности и в ASP.NET Core 2.0
Дополнительные сведения
Полный список изменений см. в статье Заметки о выпуске ASP.NET Core 2.0 .
Чтобы отслеживать ход работы и планы команды разработчиков ASP.NET Core,
смотрите выпуски ASP.NET Community Standup .
Новые возможности ASP.NET Core 1.1
Статья • 28.01.2023 • Чтение занимает 2 мин
В состав ASP.NET Core 1.1 входят следующие новые функции и компоненты:
ПО промежуточного слоя для переопределения URL-адресов
ПО промежуточного слоя для кэширования ответов
Просмотр компонентов как вспомогательных функций тегов
ПО промежуточного слоя в качестве фильтров MVC
Поставщик TempData на основе файлов Cookie
Поставщик ведения журнала службы приложений Azure
Поставщик конфигурации Azure Key Vault
Репозитории ключей защиты данных для хранилищ Azure и Redis
Сервер WebListener для Windows
Поддержка WebSocket
Выбор между версиями ASP.NET Core 1.0 и 1.1
ASP.NET Core 1.1 имеет более широкий набор возможностей, чем ASP.NET Core 1.0.
Как правило, мы рекомендуем использовать последнюю версию.
Дополнительные сведения
Заметки о выпуске ASP.NET Core 1.1.0
Чтобы отслеживать ход работы и планы команды разработчиков ASP.NET
Core, смотрите выпуски ASP.NET Community Standup .
Выбор пользовательского вебинтерфейса ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 7 мин
ASP.NET Core — это полноценная платформа пользовательского интерфейса.
Выберите, какие функциональные возможности следует объединить, чтобы
соответствовать потребностям пользовательского веб-интерфейса приложения.
Преимущества и недостатки
пользовательских интерфейсов, которые
отрисовываются сервером и клиентом
Существует три общих подхода к созданию современного пользовательского вебинтерфейса в ASP.NET Core.
Приложения, которые преобразовывают для просмотра пользовательский
интерфейс с сервера.
Приложения, которые преобразовывают для просмотра пользовательский
интерфейс на клиенте в браузере.
Гибридные приложения, использующие преимущества методов отрисовки
серверных и клиентских пользовательских интерфейсов. Например,
большинство пользовательских веб-интерфейсов преобразовываются для
просмотра на сервере, а компоненты, отображаемые клиентом, добавляются
по мере необходимости.
Существует ряд преимуществ и недостатков, которые следует учитывать при
отрисовке пользовательского интерфейса на сервере или на клиенте.
Пользовательский интерфейс, отображаемый
сервером
Приложение пользовательского веб-интерфейса, которое преобразовывается для
просмотра на сервере, в ответ на запрос браузера динамически создает HTML и
CSS код страницы на сервере. Страница поступает на клиент уже готовая для
просмотра.
Преимущества:
Требования клиента минимальны, так как сервер создает логику и страницы:
Отлично подходит для низкоуровневых устройств и подключений с низкой
пропускной способностью.
Предоставляет широкий спектр версий браузеров на клиенте.
Быстрая загрузка начальной страницы.
Минимальное количество или отсутствие элементов JavaScript для
представления клиенту.
Гибкий доступ к защищенным ресурсам сервера:
Доступ к базе данных.
Доступ к секретам, таким как значения для вызовов API к службе
хранилища Azure.
Преимущества статического анализа сайта, такие как оптимизация для
поисковых систем.
Примеры распространенных сценариев приложения пользовательского вебинтерфейса, отображаемого сервером:
Динамические сайты, например те, которые предоставляют
персонализированные страницы, данные и формы.
Отображение данных только для чтения, например списков транзакций.
Отображение статических страниц блога.
Общедоступная система управления содержимым.
Недостатки:
Оплата за вычисления и использование памяти выставляется на сервере, а не
на каждом клиенте.
Для взаимодействия с пользователем требуется выполнить круговой путь к
серверу, чтобы создать обновления пользовательского интерфейса.
Пользовательский интерфейс, отображаемый
клиентом
Приложение, отображаемое клиентом, динамически преобразовывает для
просмотра пользовательский веб-интерфейс на клиенте и при необходимости
непосредственно обновляет модель DOM браузера.
Преимущества:
Обеспечивает практически мгновенные многочисленные интерактивные
возможности без необходимости выполнения кругового пути к серверу.
Обработка событий пользовательского интерфейса и логика выполняются
локально на устройстве пользователя с минимальной задержкой.
Поддержка добавочных обновлений с обеспечением сохранения частично
заполненных форм или документов, не требуя при этом от пользователя
нажимать кнопку для отправки формы.
Может быть настроен для работы в отключенном режиме. Обновления
клиентской модели с возможностью синхронизировать ее с сервером при
восстановлении подключения.
Снижение нагрузки и сокращение затрат на сервер. Рабочая нагрузка
переносится на клиент. Многие приложения, отображаемые клиентом, могут
размещаться так же, как и статические веб-сайты.
Использование преимуществ возможностей устройства пользователя.
Примеры пользовательского веб-интерфейса, отображаемого клиентом:
Интерактивная панель мониторинга.
Приложение с функцией перетаскивания.
Быстродействующее социальное приложение для совместной работы.
Недостатки:
Код логики необходимо скачать и выполнить на клиенте, что увеличивает
время начальной загрузки.
Требования клиента могут исключить пользователя, который использует
низкоуровневые устройства, более старые версии браузера или подключения
с низкой пропускной способностью.
Выбор решения пользовательского
интерфейса ASP.NET Core, отображаемого
сервером
В следующем разделе подробно описываются доступные модули
пользовательского веб-интерфейса ASP.NET Core, отображаемые сервером, и
приведены ссылки для начала работы. Razor Pages в ASP.NET Core и ASP.NET Core
MVC — это серверные платформы для создания веб-приложений с помощью .NET.
Razor Pages в ASP.NET Core
Razor Pages — это модель на основе страниц. Задачи, связанные с
пользовательским интерфейсом и бизнес-логикой, хранятся отдельно, но на одной
странице. Razor Pages — рекомендуемый способ создания приложений на основе
страниц или форм для разработчиков, которые еще не работали с ASP.NET Core.
Приступить к работе с Razor Pages намного проще, чем с ASP.NET Core MVC.
Преимущества Razor Pages в дополнение к преимуществам отрисовки сервера:
Быстрое создание и обновление пользовательского интерфейса. Код
страницы хранится на странице. Задачи, связанные с пользовательским
интерфейсом и бизнес-логикой, хранятся отдельно.
Возможность выполнить проверку и масштабирование больших приложений.
Более простой способ упорядочения страниц ASP.NET Core, чем при
использовании ASP.NET MVC:
Логику и модели представлений можно хранить вместе в их пространстве
имен и каталоге.
Группы связанных страниц могут храниться вместе в собственном
пространстве имен и каталоге.
Чтобы приступить к работе с первым приложением ASP.NET Core Razor Pages, см.
Учебник. Начало работы с Razor Pages в ASP.NET Core. Полный обзор ASP.NET Core
Razor Pages, его архитектуры и преимуществ см. в статье Введение в Razor Pages в
ASP.NET Core.
ASP.NET Core MVC
ASP.NET MVC отрисовывает пользовательский интерфейс на сервере и использует
шаблон архитектуры "Модель — представление — контроллер" (MVC). Шаблон
MVC разделяет приложение на три основных группы компонентов: модели,
представления и контроллеры. Запросы пользователей направляются в
контроллер. Контроллер отвечает за работу с моделью для выполнения действий
пользователей и получения результатов запросов. Контроллер выбирает
представление для отображения пользователю со всеми необходимыми данными
модели. Поддержка Razor Pages осуществляется на базе ASP.NET Core MVC.
Преимущества MVC в дополнение к преимуществам отрисовки сервера:
Основывается на масштабируемой и продуманной модели для создания
крупных веб-приложений.
Четкое разделение задач для максимальной гибкости.
Разделение обязанностей на основе шаблона "Модель — представление —
контроллер" гарантирует, что бизнес-модель можно легко развивать, не
затрагивая при этом реализацию возможностей более низкого уровня.
Сведения о начале работы с ASP.NET Core MVC см. в статье Начало работы с
ASP.NET Core MVC. Общие сведения об архитектуре и преимуществах ASP.NET Core
MVC см. в статье Общие сведения ASP.NET Core MVC.
Blazor Server
Платформа Blazor предназначена для создания интерактивного веб-интерфейса на
стороне клиента с использованием Blazor и предоставляет следующие
возможности:
создание многофункциональных интерактивных пользовательских
интерфейсов на C# вместо JavaScript ;
совместное использование серверной и клиентской логик приложений,
написанных с помощью .NET;
отображение пользовательского интерфейса в виде HTML-страницы с CSS для
широкой поддержки браузеров, в том числе для мобильных устройств.
интеграция с современными платформами размещения, такими как Docker.
создание гибридных классических и мобильных приложений с помощью .NET
и Blazor;
Использование .NET для разработки веб-приложений на стороне клиента
предоставляет следующие преимущества:
создавайте код на C#, а не на JavaScript.
возможность использовать существующую экосистему .NET с библиотеками
.NET;
сохранение единой логики приложений для сервера и клиента;
высокая производительность, надежность и безопасность платформы .NET;
Сохраняйте продуктивность в Windows, Linux или macOS с помощью среды
разработки, такой как Visual Studio
или Visual Studio Code .
создавайте приложения на основе распространенных языков, платформ и
инструментов, которые отличаются стабильностью, широким набором
функций и простотой в использовании.
Blazor Serverобеспечивает поддержку размещения пользовательского интерфейса,
отображаемого сервером, в приложении ASP.NET Core. Обновления
пользовательского интерфейса клиента обрабатываются через SignalR
подключение. Среда выполнения остается на сервере и обрабатывает выполнение
кода C# приложения.
Дополнительные сведения см. в статьях о моделях размещения ASP.NET Core Blazor
и ASP.NET Core Blazor. Модель размещения, отрисоченная Blazor клиентом, описана
Blazor WebAssembly в разделе далее в этой статье.
Выбор решения ASP.NET Core,
отображаемого клиентом
В следующем разделе кратко описываются доступные модули пользовательского
веб-интерфейса ASP.NET Core, отображаемые клиентом, и приведены ссылки для
начала работы.
Blazor WebAssembly
Blazor WebAssembly — это платформа одностраничных приложений (SPA) для
создания интерактивных клиентских веб-приложений с общими характеристиками
Blazor Server , описанными в разделе ранее в этой статье.
Выполнение кода .NET в веб-браузерах становится возможным благодаря
технологии WebAssembly
(сокращенно
). WebAssembly — это компактный
формат байт-кода, оптимизированный для быстрой загрузки и максимального
быстродействия. WebAssembly — это открытый веб-стандарт, который
поддерживается в веб-браузерах без подключаемых модулей. Blazor WebAssembly
работает во всех современных веб-браузерах, включая браузеры мобильных
устройств.
При сборке Blazor WebAssembly и запуске приложения:
Файлы кода C# и файлы Razor компилируются в сборки .NET.
Сборки и среда выполнения .NET загружаются в браузер.
Blazor WebAssembly осуществляет начальную загрузку среды выполнения .NET
и настраивает ее для загрузки сборок для приложения. Среда Blazor
WebAssembly выполнения использует взаимодействие JavaScript для
обработки операций с моделью DOM
и вызовов API браузера.
Дополнительные сведения см. в статьях о моделях размещения ASP.NET Core Blazor
и ASP.NET Core Blazor. Модель размещения, отрисоченная Blazor на сервере,
описана Blazor Server в разделе ранее в этой статье.
Одностраничное приложение (SPA) в ASP.NET Core с
платформами JavaScript, такими как Angular и React
Создайте клиентскую логику для приложений ASP.NET Core с помощью таких
популярных платформ JavaScript, как Angular
и React
. Приложение ASP.NET
Core предоставляет шаблоны проектов для Angular и React. Его также можно
использовать с другими платформами JavaScript.
Преимущества SPA в ASP.NET Core SPA с платформами JavaScript в дополнение к
приведенным выше преимуществам отрисовки клиента:
Среда выполнения JavaScript уже предоставлена вместе с браузером.
Большое сообщество и продуманная экосистема.
Создание клиентской логики для приложений ASP.NET Core с помощью таких
популярных платформ JS, как Angular и React.
Недостатки:
Требуются дополнительные языки программирования, платформы и средства.
Сложно обмениваться кодом, поэтому некоторая логика может
дублироваться.
Чтобы начать работу, см. следующие статьи.
Использование Angular с ASP.NET Core
Использование React с ASP.NET Core
Выбор гибридного решения: ASP.NET Core
MVC или Razor Pages в сочетании с Blazor
MVC, Razor Pages и Blazor являются частью платформы ASP.NET Core и разработаны
для совместного использования. Компоненты Razor можно интегрировать в
приложения Razor Pages и MVC в размещенном решении Blazor WebAssembly или
Blazor Server. Одновременно с отрисовкой страницы или представления можно
выполнять предварительную обработку компонентов.
Преимущества MVC или Razor Pages в сочетании с Blazor, которые дополняют
обычные преимуществам MVC и Razor Pages:
предварительная отрисовка выполняет компоненты Razor на сервере и
отрисовывает их в виде представления или страницы, что улучшает
восприятие скорости загрузки приложения.
Добавление интерактивности в существующие представления (страницы) с
помощью вспомогательной функции тега компонента.
Чтобы начать работу с ASP.NET Core MVC или Razor Pages в сочетании с Blazor,
воспользуйтесь статьей Компоненты Razor для предварительной визуализации и
интеграции ASP.NET Core.
Дальнейшие действия
Дополнительные сведения см. в разделе:
ASP.NET Core
Модели размещения ASP.NET Core
Компоненты Razor для предварительной визуализации и интеграции
ASP.NET Core
Сравнение служб gRPC с API-интерфейсами HTTP
Учебник. Создание веб-приложения
Razor Pages с помощью ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 2 мин
В этой серии руководств приводятся основные сведения о создании вебприложения Razor Pages.
Дополнительные сведения, предназначенные для разработчиков, которые
знакомы с контроллерами и представлениями, см. в статье Введение в Razor Pages
в ASP.NET Core.
Если вы новичок в разработке на ASP.NET Core и не знаете, какое решение для
пользовательского веб-интерфейса подойдет вам, см. статью Выбор
пользовательского интерфейса ASP.NET Core.
В серию входят следующие руководства:
1. Создание веб-приложения Razor Pages
2. Добавление модели в приложение Razor Pages
3. Формирование шаблона (создание) страницы Razor Pages
4. Работа с базой данных
5. Обновление Razor Pages
6. Добавление поиска
7. Добавление нового поля
8. Добавление проверки
В итоге вы получите приложение, которое позволяет отображать сведения о базе
данных фильмов и управлять ею.
Учебник. Начало работы с Razor Pages
в ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 21 мин
Автор: Рик Андерсон
(Rick Anderson)
В этом первом руководстве серии приводятся основные сведения о вебприложении Razor Pages в ASP.NET Core.
Дополнительные сведения, предназначенные для разработчиков, которые
знакомы с контроллерами и представлениями, см. в статье Введение в Razor Pages.
Общие сведения см. в разделе Entity Framework Core для начинающих .
Если вы новичок в разработке на ASP.NET Core и не знаете, какое решение для
пользовательского веб-интерфейса подойдет вам, см. статью Выбор
пользовательского интерфейса ASP.NET Core.
В конце этого руководства у Razor вас будет веб-приложение Pages, которое
управляет базой данных фильмов.
Предварительные требования
Visual Studio
Visual Studio 2022
с рабочей нагрузкой ASP.NET и веб-разработка.
Создание веб-приложения Razor Pages
Visual Studio
Запустите Visual Studio и щелкните Создать проект
В диалоговом окне Создание проекта выберите ASP.NET Core Вебприложение>Далее.
В диалоговом окне Настроить новый проект введите RazorPagesMovie в
поле Имя проекта. Важно присвоить проекту имя RazorPagesMovie,
включая сопоставление регистра букв, чтобы пространство имени
соответствовало при копировании и вставке примера кода.
Выберите Далее.
В диалоговом окне Дополнительные сведения выполните следующие
действия.
Выберите .NET 7.0.
Убедитесь, что флажок Не использовать операторы верхнего уровня
снят.
Нажмите кнопку создания.
Создается следующий начальный проект:
Альтернативные подходы к созданию проекта см. в статье Создание проекта в
Visual Studio.
Запуск приложения
Visual Studio
Выберите RazorPagesMovie в Обозревателе решений и нажмите клавиши
CTRL + F5, чтобы выполнить запуск без отладчика.
Visual Studio отображает следующее диалоговое окно, если проект еще не
настроен для использования SSL:
Выберите Да, чтобы сделать SSL-сертификат IIS Express доверенным.
Отобразится следующее диалоговое окно.
Выберите Да, если согласны доверять сертификату разработки.
Сведения о доверии к браузеру Firefox см. в разделе Ошибка сертификата
браузера Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
Visual Studio:
Запускает приложение, которое запускает сервер Kestrel.
Запускает браузер по умолчанию по адресу https://localhost:<port> ,
который отображает пользовательский интерфейс приложений. <port> —
случайный порт, назначенный при создании приложения.
Анализ файлов проекта
В разделах ниже приведен обзор основных папок и файлов проекта, с которыми
вы будете работать в последующих учебниках.
Папка Pages
Содержит страницы Razor и вспомогательные файлы. Каждая страница Razor — это
пара файлов.
Файл .cshtml с разметкой HTML и кодом C# использует синтаксис Razor.
Файл .cshtml.cs с кодом C#, который обрабатывает события страницы.
Имена вспомогательных файлов начинаются с символа подчеркивания. Например,
файл _Layout.cshtml настраивает элементы пользовательского интерфейса, общие
для всех страниц. _Layout.cshtml настраивает меню навигации в верхней части
страницы и уведомление об авторских правах в нижней части страницы.
Подробные сведения см. в статье Макет в ASP.NET Core.
Папка wwwroot
Содержит статические ресурсы, такие как HTML-файлы, файлы JavaScript и CSSфайлы. Подробные сведения см. в статье Статические файлы в ASP.NET Core.
appsettings.json
Содержит данные конфигурации, например строки подключения. Дополнительные
сведения см. в разделе Конфигурация в ASP.NET Core.
Program.cs
Содержит следующий код:
C#
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for
production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Следующие строки кода в этом файле создают WebApplicationBuilder с
предварительно настроенными значениями по умолчанию, добавляют Razor
поддержку Pages в контейнер внедрения зависимостей (DI) и создают приложение:
C#
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
Страница со сведениями об исключении для разработчика включена по
умолчанию и содержит полезную информацию об исключениях. Рабочие
приложения не следует запускать в режиме разработки, поскольку на странице со
сведениями об исключении для разработчика может находиться
конфиденциальная информация.
В следующем коде для конечной точки устанавливаются исключения /Error и
включается протокол HSTS, если приложение не запущено в режиме разработки:
C#
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for
production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
Например, приведенный выше код выполняется, когда приложение находится в
рабочем или тестовом режиме. Дополнительные сведения см. в статье
Использование нескольких сред в ASP.NET Core.
Следующий код включает различное ПО промежуточного слоя:
app.UseHttpsRedirection(); : перенаправляет все запросы HTTP на HTTPS.
app.UseStaticFiles(); : обеспечивает обслуживание таких статических файлов,
как HTML, CSS, изображения и JavaScript. Подробные сведения см. в статье
Статические файлы в ASP.NET Core.
app.UseRouting(); : добавляет соответствие маршрута в конвейер ПО
промежуточного слоя. Дополнительные сведения см. в статье Маршрутизация
в ASP.NET Core.
app.MapRazorPages(); : настраивает маршрутизацию конечных точек для Razor
Pages.
app.UseAuthorization(); : разрешает пользователю доступ к защищенным
ресурсам. Это приложение не использует авторизацию, поэтому эту строку
можно удалить.
app.Run(); : запускает приложение.
Устранение неполадок с
завершенным примером
Если вы столкнулись с проблемой, которую не можете решить, сравните свой код с
кодом готового проекта. Просмотрите или скачайте завершенный проект
(порядок загрузки).
Дальнейшие действия
Далее: Добавление модели
Часть 2. Добавление модели в
приложение Razor Pages в
ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 42 мин
В этом учебнике добавляются классы для управления фильмами в базе данных.
Классы моделей приложения используют Entity Framework Core (EF Core) для
работы с базой данных. EF Core — это объектно-реляционный сопоставителя
(O/RM), упрощающий доступ к данным. Сначала вы записываете классы модели и
EF Core создаете базу данных.
Классы модели называются классами POCO (из "Plain-Old CLR Objects"), так как они
не имеют зависимости EF Coreот . Эти классы определяют свойства данных,
которые хранятся в базе данных.
Добавление модели данных
Visual Studio
1. В Обозревателе решений щелкните правой кнопкой мыши проект
RazorPagesMovie и выберите Добавить>New Folder (Новая папка).
Назовите папку Models .
2. Щелкните правой кнопкой мыши папку Models . Выберите
Добавить>Класс. Присвойте классу имя Movie.
3. Добавьте в класс Movie следующие свойства:
C#
using System.ComponentModel.DataAnnotations;
namespace RazorPagesMovie.Models;
public class Movie
{
public int Id { get; set; }
public string? Title { get; set; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
public decimal Price { get; set; }
}
Класс Movie содержит:
Поле ID является обязательным для первичного ключа базы данных.
Атрибут [DataType], указывающий тип данных в свойстве ReleaseDate . С
этим атрибутом:
Пользователю не требуется вводить сведения о времени в поле даты.
Отображается только дата, а не время.
Знак вопроса после string указывает, что свойство допускает значение
NULL. Дополнительные сведения см. в статье Ссылочные типы,
допускающие значение NULL.
DataAnnotations рассматриваются в следующем руководстве.
Выполните сборку проекта, чтобы убедиться в отсутствии ошибок компиляции.
Создание модели фильма
В этом разделе создается модель фильма. То есть средство формирования
шаблонов создает страницы для операций создания, чтения, обновления и
удаления для модели фильма.
Visual Studio
1. Создайте папку Pages/Movies.
a. Щелкните правой кнопкой мыши папку Pages и выберите
Добавить>New Folder (Новая папка).
b. Назовите папку Movies.
2. Щелкните правой кнопкой мыши папку Pages/Movies и выберите
Добавить>New Scaffolded Item (Создать шаблонный элемент).
3. В диалоговом окне Добавление шаблона щелкните Razor Pages на
основе Entity Framework (CRUD)>Добавить.
4. Заполните поля в диалоговом окне Добавление Razor Pages на основе
Entity Framework (CRUD) :
a. В раскрывающемся списке Класс модели выберите Фильм
(RazorPagesMovie.Models) .
b. В строке Класс контекста данных щелкните знак плюса ( + ).
i. В диалоговом окне Добавление контекста данных будет
сгенерировано имя класса
RazorPagesMovie.Data.RazorPagesMovieContext .
c. Выберите Добавить.
Файл appsettings.json обновляется с указанием строки подключения,
используемой для подключения к локальной базе данных.
Созданные и обновленные файлы
В процессе формирования шаблонов создаются указанные ниже файлы.
Pages/Movies: Create, Delete, Details, Edit и Index.
Data/RazorPagesMovieContext.cs
В следующем учебнике приводится описание созданных файлов.
В процессе формирования шаблонов в файл Program.cs добавляется следующий
выделенный фрагмент кода.
Visual Studio
C#
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this
for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Изменения в файле Program.cs описываются далее в этом учебнике.
Создание начальной схемы базы данных с
помощью функции миграции EF
Функция миграции в Entity Framework Core предоставляет следующие
возможности:
Создание начальной схемы базы данных.
Поэтапное обновление схемы базы данных, чтобы она соответствовала
модели данных приложения. Существующие данные в базе данных
сохраняются.
Visual Studio
В этом разделе окно Консоль диспетчера пакетов (PMC) используется для
выполнения следующих действий:
Добавления первоначальной миграции.
Обновления базы данных с помощью первоначальной миграции.
1. В меню Инструменты выберите Диспетчер пакетов NuGet>Консоль
диспетчера пакетов.
2. В PMC введите следующие команды:
PowerShell
Add-Migration InitialCreate
Update-Database
Предыдущие команды:
Установите последнюю версию инструментов Entity Framework Core после
удаления любой предыдущей версии, если она существует.
Выполните команду , migrations чтобы создать код, который создает
исходную схему базы данных.
Появится следующее предупреждение, которое будет устранено на следующем
шаге:
"Для десятичного столбца Price в типе сущности Movie не указан тип. Это
приведет к тому, что значения будут усекаться без вмешательства
пользователя, если они не помещаются в значения точности и масштаба по
умолчанию. С помощью метода HasColumnType() явно укажите тип столбца
SQL Server, который может вместить все значения".
Команда migrations формирует код для создания схемы исходной базы данных.
Схема создается на основе модели, указанной в DbContext . Аргумент InitialCreate
используется для присвоения имен миграциям. Можно использовать любое имя,
однако по соглашению выбирается имя, которое описывает миграцию.
Команда update выполняет метод Up в миграциях, которые не были применены. В
этом случае update выполняет метод Up в файле Migrations/<timestamp>_InitialCreate.cs , который создает базу данных.
Проверка контекста, зарегистрированного с помощью
внедрения зависимостей
ASP.NET Core поддерживает внедрение зависимостей. Службы, такие как EF Core
контекст базы данных, регистрируются с помощью внедрения зависимостей во
время запуска приложения. Затем компоненты, которые используют эти службы
(например, Razor Pages), предоставляются через параметры конструктора. Код
конструктора, который получает экземпляр контекста базы данных, приведен далее
в этом руководстве.
Средство формирования шаблонов автоматически создает контекст базы данных и
регистрирует его с использованием контейнера внедрения зависимостей. Средство
формирования шаблонов добавляет следующий выделенный фрагмент кода в
файл Program.cs .
Visual Studio
C#
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this
for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Контекст данных RazorPagesMovieContext :
Получается из Microsoft.EntityFrameworkCore.DbContext.
Указывает сущности, которые включаются в модель данных.
EF Core Координирует функции, такие как создание, чтение, обновление и
удаление, для Movie модели.
C#
using
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.EntityFrameworkCore;
RazorPagesMovie.Models;
namespace RazorPagesMovie.Data
{
public class RazorPagesMovieContext : DbContext
{
public RazorPagesMovieContext
(DbContextOptions<RazorPagesMovieContext> options)
: base(options)
{
}
public DbSet<RazorPagesMovie.Models.Movie> Movie { get; set; } =
default!;
}
}
Представленный выше код создает свойство DbSet<Movie> для набора сущностей.
В терминологии Entity Framework набор сущностей обычно соответствует таблице
базы данных. Сущность соответствует строке в таблице.
Имя строки подключения передается в контекст путем вызова метода для объекта
DbContextOptions. При локальной разработке система конфигурации считывает
строку подключения из файла appsettings.json .
Тестирование приложения
1. Запустите приложение и добавьте /Movies к URL-адресу в браузере
( http://localhost:port/movies ).
Если вы получаете следующую ошибку:
Консоль
SqlException: Cannot open database "RazorPagesMovieContext-GUID"
requested by the login. The login failed.
Login failed for user 'User-name'.
Вы пропустили шаг миграции.
2. Протестируйте ссылку Create New (Создать).
7 Примечание
В поле Price нельзя вводить десятичные запятые. Чтобы обеспечить
поддержку проверки jQuery
для других языков, кроме английского,
используйте вместо десятичной точки запятую (,), а для отображения
данных в форматах для других языков, кроме английского, выполните
глобализацию приложения. Инструкции по глобализации см. на сайте
GitHub
.
3. Протестируйте ссылки Изменить, Сведения и Удалить.
В следующем учебнике рассматриваются файлы, созданные с помощью
формирования шаблонов.
Устранение неполадок с
завершенным примером
Если вы столкнулись с проблемой, которую не можете решить, сравните свой код с
кодом готового проекта. Просмотрите или скачайте завершенный проект
(порядок загрузки).
Дополнительные ресурсы
Предыдущая статья. Начало работы
Следующая статья. Сформированные страницы Razor Pages
Часть 3. Razor Pages, созданные путем
формирования шаблонов, в
ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 22 мин
Автор: Рик Андерсон
(Rick Anderson)
Этот учебник описывает Razor Pages, созданные путем формирования шаблонов в
предыдущем учебнике.
Страницы Create, Delete, Details и Edit
Проверьте модель страницы Pages/Movies/Index.cshtml.cs :
C#
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Pages.Movies;
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
public IList<Movie> Movie { get;set; }
= default!;
public async Task OnGetAsync()
{
if (_context.Movie != null)
{
Movie = await _context.Movie.ToListAsync();
}
}
}
Razor Pages являются производными от класса PageModel. Как правило, класс,
производный от PageModel , называется PageNameModel . Например, страница Index
называется IndexModel .
Используя внедрение зависимостей, конструктор добавляет на страницу
RazorPagesMovieContext :
C#
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
Дополнительные сведения об асинхронном программировании с использованием
Entity Framework см. в разделе Асинхронный код.
При выполнении GET запроса на страницу OnGetAsync метод возвращает на
страницу список фильмов Razor . В Razor Page для инициализации состояния
страницы вызывается OnGetAsync или OnGet . В этом случае OnGetAsync возвращает
список фильмов для отображения.
Когда OnGet возвращает void или OnGetAsync возвращает Task , оператор return не
используется. Например, обратитесь к Privacy Page:
C#
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesMovie.Pages
{
public class PrivacyModel : PageModel
{
private readonly ILogger<PrivacyModel> _logger;
public PrivacyModel(ILogger<PrivacyModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}
Если возвращаемый тип — IActionResult или Task<IActionResult> , необходимо
предоставить оператор return. Например, метод Pages/Movies/Create.cshtml.cs
OnPostAsync :
C#
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Проверьте страницу Pages/Movies/Index.cshtml Razor:
CSHTML
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model
</th>
<th>
@Html.DisplayNameFor(model
</th>
<th>
@Html.DisplayNameFor(model
</th>
<th>
@Html.DisplayNameFor(model
</th>
<th></th>
=> model.Movie[0].Title)
=> model.Movie[0].ReleaseDate)
=> model.Movie[0].Genre)
=> model.Movie[0].Price)
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movie) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.Id">Details</a>
|
<a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Razor может выполнять переход с HTML на C# или на разметку Razor. Если за
символом @ следует зарезервированное ключевое слово Razor, он переходит на
разметку Razor, а если нет, то на C#.
директиву @page
Директива Razor @page преобразует файл в действие MVC, а значит, он может
обрабатывать запросы. @page должна быть первой директивой Razor на странице.
@page и @model являются примерами перехода на разметку, относящуюся к Razor.
Дополнительные сведения см. в статье Синтаксис Razor.
директиву @model
CSHTML
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
Директива @model определяет тип модели, передаваемой на страницу Razor. В
приведенном выше примере строка @model делает класс, производный от
PageModel , доступным для Razor Page. Модель используется на странице во
вспомогательных методах HTML @Html.DisplayNameFor и @Html.DisplayFor .
Проверьте лямбда-выражение, которое используется в следующем
вспомогательном методе HTML:
CSHTML
@Html.DisplayNameFor(model => model.Movie[0].Title)
Вспомогательный метод HTML DisplayNameFor проверяет свойство Title ,
указанное в лямбда-выражении, и определяет отображаемое имя. Лямбдавыражение проверяется, а не вычисляется. Это означает, что в случае, если model ,
model.Movie или model.Movie[0] имеют значение null или пусты, права доступа не
нарушаются. При вычислении лямбда-выражения, например с помощью
@Html.DisplayFor(modelItem => item.Title) , вычисляются значения для свойств
модели.
Страница макета
Выберите ссылки в меню (RazorPagesMovie, Home и Privacy). Меню на каждой
странице имеют одинаковый макет. Макет меню реализован в файле
Pages/Shared/_Layout.cshtml .
Откройте файл Pages/Shared/_Layout.cshtml и проверьте его.
Шаблоны макета позволяют сделать следующее для макета контейнера HTML:
указать его в одном расположении;
применить его на нескольких страницах сайта.
Найдите строку @RenderBody() . RenderBody — это заполнитель для отображения всех
представлений определенных страниц, упакованных в страницу макета. Например,
щелкните ссылку Privacy, и представление Pages/Privacy.cshtml отобразится в
методе RenderBody .
ViewData и макет
Рассмотрим следующую разметку из файла Pages/Movies/Index.cshtml .
CSHTML
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
Выделенная выше разметка представляет собой пример перехода Razor на C#.
Символы { и } ограничивают блок кода C#.
Базовый класс PageModel содержит свойство словаря ViewData . Оно позволяет
передать данные в представление. Объекты добавляются в словарь ViewData с
помощью шаблона ключ — значение. В приведенном выше примере в словарь
ViewData добавляется свойство Title .
Свойство Title используется в файле Pages/Shared/_Layout.cshtml . Ниже показаны
первые несколько строк файла _Layout.cshtml .
CSHTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - RazorPagesMovie</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"
/>
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"
/>
<link rel="stylesheet" href="~/RazorPagesMovie.styles.css" asp-appendversion="true" />
Строка @*Markup removed for brevity.*@ представляет собой комментарий Razor. В
отличие от комментариев HTML <!-- --> комментарии Razor не отправляются
клиенту. Дополнительные сведения см. в веб-документации MDN. Начало работы с
HTML .
Обновление макета
1. Измените элемент <title> в файле Pages/Shared/_Layout.cshtml так, чтобы
вместо Movie отображалось RazorPagesMovie.
CSHTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initialscale=1.0" />
<title>@ViewData["Title"] - Movie</title>
2. Найдите следующий элемент привязки в файле Pages/Shared/_Layout.cshtml .
CSHTML
<a class="navbar-brand" asp-area="" asppage="/Index">RazorPagesMovie</a>
3. Измените указанный выше элемент на следующую разметку.
CSHTML
<a class="navbar-brand" asp-page="/Movies/Index">RpMovie</a>
Указанный выше элемент привязки является вспомогательной функцией тега.
В данном случае он является вспомогательной функцией тега привязки.
Атрибут вспомогательной функции тега asp-page="/Movies/Index" и его
значение создают ссылку на страницу Razor /Movies/Index . Атрибут asp-area
имеет пустое значение, поэтому эта область не используется в ссылке.
Дополнительные сведения см. в статье Области.
4. Сохраните изменения и протестируйте приложение, выбрав ссылку RpMovie.
Если у вас возникли проблемы, ознакомьтесь с файлом _Layout.cshtml
в
GitHub.
5. Проверьте ссылки Home, RpMovie, Create, Edit и Delete. Каждая страница
задает заголовок, который отображается на вкладке браузера. При
добавлении страницы в избранное заголовок используется в закладках.
7 Примечание
В поле Price нельзя вводить десятичные запятые. Чтобы обеспечить
поддержку проверки jQuery
для других языков, кроме английского,
используйте вместо десятичной точки запятую (","), а для отображения данных
в форматах для других языков, кроме английского, выполните действия,
необходимые для глобализации приложения. Инструкции по добавлению
десятичной запятой см. в вопросе № 4076 на сайте GitHub .
Свойство Layout задается в файле Pages/_ViewStart.cshtml :
CSHTML
@{
Layout = "_Layout";
}
Представленный выше код задает файл разметки Pages/Shared/_Layout.cshtml для
всех файлов Razor в папке Pages. Дополнительные сведения см. в статье о макете.
Страничная модель Create
Изучите страничную модель Pages/Movies/Create.cshtml.cs :
C#
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;
namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext
_context;
public CreateModel(RazorPagesMovie.Data.RazorPagesMovieContext
context)
{
_context = context;
}
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
public Movie Movie { get; set; } = default!;
// To protect from overposting attacks, see
https://aka.ms/RazorPagesCRUD
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid || _context.Movie == null || Movie ==
null)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
Метод OnGet инициализирует все состояния, необходимые для страницы. Страница
Create не содержит никаких состояний для инициализации, поэтому возвращается
Page . Далее в этом руководстве показан пример инициализации состояния OnGet .
Метод Page создает объект PageResult , который формирует страницу
Create.cshtml .
Для указания согласия на привязку модели в свойстве Movie используется атрибут
[BindProperty]. Когда форма Create публикует свои значения, среда выполнения
ASP.NET Core связывает переданные значения с моделью Movie .
Метод OnPostAsync выполняется, когда страница публикует данные формы:
C#
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Movie.Add(Movie);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Если в модели есть ошибки, форма отображается снова вместе со всеми
опубликованными данными этой формы. Большинство ошибок в модели может
быть перехвачено на стороне клиента до публикации формы. Пример ошибки в
модели — это публикация значения для поля даты, которое нельзя конвертировать
в дату. Проверка на стороне клиента и проверка модели обсуждаются подробнее
далее в этом учебнике.
Если нет ошибок модели:
Данные сохранены.
Браузер перенаправляется на страницу Index.
Страница Razor создания
Проверьте файл страницы Pages/Movies/Create.cshtml Razor:
CSHTML
@page
@model RazorPagesMovie.Pages.Movies.CreateModel
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="Movie.ReleaseDate" class="control-label">
</label>
<input asp-for="Movie.ReleaseDate" class="form-control" />
<span asp-validation-for="Movie.ReleaseDate" class="textdanger"></span>
</div>
<div class="form-group">
<label asp-for="Movie.Genre" class="control-label"></label>
<input asp-for="Movie.Genre" class="form-control" />
<span asp-validation-for="Movie.Genre" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="Movie.Price" class="control-label"></label>
<input asp-for="Movie.Price" class="form-control" />
<span asp-validation-for="Movie.Price" class="text-danger">
</span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary"
/>
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Visual Studio
Visual Studio выделяет следующие теги полужирным шрифтом, который
используется для вспомогательных функций тегов.
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
Элемент <form method="post"> представляет собой вспомогательную функцию тега
Form. Вспомогательная функция тега Form автоматически включает маркер защиты
от подделки.
Ядро формирования шаблонов создает разметку Razor для каждого поля в модели
(кроме ID) следующего вида:
CSHTML
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
Вспомогательные функции тегов Validation ( <div asp-validation-summary и <span
asp-validation-for ) отображают ошибки проверки. Более подробно проверка
рассматривается далее в этой серии статей.
Вспомогательная функция тега Label ( <label asp-for="Movie.Title" class="controllabel"></label> ) создает подпись к метке и атрибут [for] для свойства Title .
Вспомогательная функция тега Input ( <input asp-for="Movie.Title" class="formcontrol"> ) использует атрибуты DataAnnotations и создает HTML-атрибуты,
необходимые для проверки jQuery на стороне клиента.
Дополнительные сведения о вспомогательных функциях тегов, таких как <form
method="post"> , см. в статье Вспомогательные функции тегов в ASP.NET Core.
Дополнительные ресурсы
Предыдущая статья. Добавление модели
Следующая статья. Работа с базой данных
Часть 4 серии руководств по Razor
Pages
Статья • 28.01.2023 • Чтение занимает 18 мин
Джо Одетт
Объект RazorPagesMovieContext обрабатывает задачу подключения к базе данных и
сопоставления объектов Movie с записями базы данных. Контекст базы данных
регистрируется с помощью контейнера внедрения зависимостей в файле :
Visual Studio
C#
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesMovie.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));
var app = builder.Build();
Система конфигурации ASP.NET Core считывает ключ . Для разработки на
локальном уровне конфигурация получает строку подключения из файла
appsettings.json .
Visual Studio
Созданная строка подключения выглядит аналогично следующему коду JSON.
JSON
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"RazorPagesMovieContext": "Server=
(localdb)\\mssqllocaldb;Database=RazorPagesMovieContextbc;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
Если приложение развертывается на тестовом или рабочем сервере, можно задать
строку подключения к тестовому или рабочему серверу базы данных с помощью
переменной среды. Дополнительные сведения см. в разделе Конфигурация.
Visual Studio
SQL Server Express LocalDB
LocalDB — это упрощенная версия ядра СУБД SQL Server Express,
предназначенная для разработки программ. LocalDB запускается по запросу в
пользовательском режиме, поэтому настройки не слишком сложны. По
умолчанию база данных LocalDB создает файлы *.mdf в каталоге C:\Users\
<user>\ .
1. В меню Вид откройте обозреватель объектов SQL Server (SSOX).
2. Щелкните правой кнопкой мыши таблицу Movie и выберите пункт Movie :
Обратите внимание на значок с изображением ключа рядом с ID . По
умолчанию EF создает свойство с именем ID для первичного ключа.
3. Щелкните правой кнопкой мыши таблицу Movie и выберите пункт Movie :
Заполнение базы данных
Создайте класс SeedData в папке SeedData со следующим кодом:
C#
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;
namespace RazorPagesMovie.Models;
public static class SeedData
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new RazorPagesMovieContext(
serviceProvider.GetRequiredService<
DbContextOptions<RazorPagesMovieContext>>()))
{
if (context == null || context.Movie == null)
{
throw new ArgumentNullException("Null
RazorPagesMovieContext");
}
// Look for any movies.
if (context.Movie.Any())
{
return;
// DB has been seeded
}
context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-2-12"),
Genre = "Romantic Comedy",
Price = 7.99M
},
new Movie
{
Title = "Ghostbusters ",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
},
new Movie
{
Title = "Ghostbusters 2",
ReleaseDate = DateTime.Parse("1986-2-23"),
Genre = "Comedy",
Price = 9.99M
},
new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western",
Price = 3.99M
}
);
context.SaveChanges();
}
}
}
Если в базе данных есть фильмы, возвращается инициализатор заполнения и
фильмы не добавляются.
C#
if (context.Movie.Any())
{
return;
}
Добавление инициализатора заполнения
Обновите файл Program.cs , используя следующий выделенный код:
Visual Studio
C#
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;
using RazorPagesMovie.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddDbContext<RazorPagesMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("RazorPag
esMovieContext") ?? throw new InvalidOperationException("Connection
string 'RazorPagesMovieContext' not found.")));
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
SeedData.Initialize(services);
}
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
В приведенном выше коде файл Program.cs изменен, чтобы сделать следующее:
Получение экземпляра контекста базы данных из контейнера внедрения
зависимостей (DI).
Вызовите метод seedData.Initialize , передав ему экземпляр контекста базы
данных.
Высвобождение контекста после завершения работы метода заполнения.
Оператор using гарантирует удаление контекста.
Если Update-Database не выполнялось, возникает следующее исключение:
SqlException: Cannot open database "RazorPagesMovieContext-" requested by the
login. The login failed. Login failed for user 'user name'.
Тестирование приложения
Удалите все записи в базе данных для запуска метода seed. Остановите и запустите
приложение, чтобы начать заполнение базы данных. Если база данных не
заполнена, установите точку останова в if (context.Movie.Any()) и пошагово
выполните код.
В приложении отображаются заполненные данные.
Дополнительные ресурсы
Предыдущая статья. Сформированные страницы
Далее: Обновление страниц
Pages
Часть 5. Изменение созданных
страниц в приложении ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 13 мин
Приложение для работы с фильмами подготовлено, но представление данных
далеко от идеала. Вместо ReleaseDate должно стоять Дата выпуска, то есть два
слова.
Обновление модели
Обновите файл Models/Movie.cs , используя следующий выделенный код:
C#
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models;
public class Movie
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; } = string.Empty;
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}
В предыдущем коде:
Заметка к данным [Column(TypeName = "decimal(18, 2)")] позволяет Entity
Framework Core корректно сопоставить Price с валютой в базе данных.
Дополнительные сведения см. в разделе Типы данных.
Атрибут [Display] указывает на отображаемое имя поля. В приведенном выше
коде Release Date вместо ReleaseDate .
Атрибут [DataType] указывает тип данных ( Date ). Сведения о времени,
хранящиеся в поле, не отображаются.
Пространство имен DataAnnotations будет рассмотрено в следующем руководстве.
Перейдите к Pages/Movies и наведите указатель мыши на ссылку Edit (Изменение),
чтобы просмотреть целевой URL-адрес.
Ссылки Edit, Details и Delete создаются вспомогательной функцией тегов привязки
в файле Pages/Movies/Index.cshtml .
CSHTML
@foreach (var item in Model.Movie) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.ID">Details</a>
|
<a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Вспомогательные функции тегов позволяют серверному коду участвовать в
создании и отображении HTML-элементов в файлах Razor.
В приведенном выше коде Вспомогательная функция привязки тегов динамически
создает значение атрибута HTML href на основе Razor Page (маршрут является
относительным), атрибут asp-page и идентификатор маршрута ( asp-route-id ).
Дополнительные сведения см. в разделе Формирование URL-адресов для страниц.
Для проверки созданной разметки используйте в браузере параметр Просмотреть
исходный код. Ниже показана часть созданного кода HTML:
HTML
<td>
<a href="/Movies/Edit?id=1">Edit</a> |
<a href="/Movies/Details?id=1">Details</a> |
<a href="/Movies/Delete?id=1">Delete</a>
</td>
Динамически создаваемые ссылки передают идентификатор фильма со строкой
запроса . Например, ?id=1 в https://localhost:5001/Movies/Details?id=1 .
Добавление шаблона маршрута
Обновите страницы Razor Pages Edit, Details и Delete так, чтобы использовался
шаблон маршрута {id:int} . Измените директиву страницы для каждой из этих
страниц c @page на @page "{id:int}" . Запустите приложение и просмотрите
исходный код.
Созданный код HTML добавляет идентификатор в путь URL-адреса:
HTML
<td>
<a href="/Movies/Edit/1">Edit</a> |
<a href="/Movies/Details/1">Details</a> |
<a href="/Movies/Delete/1">Delete</a>
</td>
Запрос на страницу с шаблоном {id:int} маршрута, который не содержит целое
число, возвращает ошибку HTTP 404 (не найдено). Например,
https://localhost:5001/Movies/Details возвращает ошибку 404. Чтобы сделать
идентификатор необязательным, добавьте ? к ограничению маршрута:
CSHTML
@page "{id:int?}"
Чтобы проверить поведение @page "{id:int?}" :
1. Задайте директиву страницы в Pages/Movies/Details.cshtml как @page "
{id:int?}" .
2. Установите точку останова в public async Task<IActionResult> OnGetAsync(int?
id) , в Pages/Movies/Details.cshtml.cs .
3. Перейдите к https://localhost:5001/Movies/Details/ .
Из-за директивы @page "{id:int}" точка останова не достигается. Механизм
маршрутизации возвращает ошибку HTTP 404. При использовании @page "
{id:int?}" метод OnGetAsync возвращает NotFound (HTTP 404):
C#
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Movie = await _context.Movie.FirstOrDefaultAsync(m => m.ID == id);
if (Movie == null)
{
return NotFound();
}
return Page();
}
Проверка обработки исключений нежесткой
блокировки
Проверьте метод OnPostAsync в файле Pages/Movies/Edit.cshtml.cs .
C#
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool MovieExists(int id)
{
return _context.Movie.Any(e => e.Id == id);
}
Предыдущий код обнаруживает исключения параллелизма, когда один клиент
удаляет фильм, а другой вносит изменения в фильм.
Чтобы протестировать блок catch , выполните указанные ниже действия.
1. Задайте точку останова в catch (DbUpdateConcurrencyException) .
2. Выберите команду Изменить у фильма, внесите изменения, но не вводите
Сохранить.
3. В другом окне браузера щелкните ссылку Delete для этого же фильма, а затем
удалите его.
4. В первом окне браузера опубликуйте изменения для фильма.
Коду в рабочей среде может потребоваться обнаружение конфликтов
параллелизма. Дополнительные сведения см. в статье Обработка конфликтов
параллелизма.
Проверка публикации и привязки
Просмотрите файл Pages/Movies/Edit.cshtml.cs .
C#
public class EditModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext _context;
public EditModel(RazorPagesMovie.Data.RazorPagesMovieContext context)
{
_context = context;
}
[BindProperty]
public Movie Movie { get; set; } = default!;
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null || _context.Movie == null)
{
return NotFound();
}
var movie =
await _context.Movie.FirstOrDefaultAsync(m => m.Id ==
id);
if (movie == null)
{
return NotFound();
}
Movie = movie;
return Page();
}
// To protect from overposting attacks, enable the specific properties
you want to bind to.
// For more details, see https://aka.ms/RazorPagesCRUD.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Movie).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(Movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
private bool MovieExists(int id)
{
return _context.Movie.Any(e => e.Id == id);
}
При выполнении HTTP-запроса GET к странице Movies/Edit, например
https://localhost:5001/Movies/Edit/3 , происходит следующее:
Метод OnGetAsync извлекает запись фильма из базы данных и возвращает
метод Page .
Метод Page отображает страницу Pages/Movies/Edit.cshtml Razor. Файл
Pages/Movies/Edit.cshtml содержит директиву модели @model
RazorPagesMovie.Pages.Movies.EditModel , которая делает модель фильма
доступной на странице.
Отображается форма Edit со значениями из записи фильма.
При публикации страницы Movies/Edit происходит следующее:
Значения формы на странице привязываются к свойству Movie . Атрибут
[BindProperty] обеспечивает привязку модели.
C#
[BindProperty]
public Movie Movie { get; set; }
При наличии ошибок в состоянии модели, например ReleaseDate невозможно
преобразовать в дату, форма отображается повторно с предоставленными
значениями.
Если ошибки модели отсутствуют, данные фильма сохраняются.
Методы HTTP GET на страницах Razor Index, Create и Delete работают аналогично.
Метод HTTP POST OnPostAsync на странице Razor Create работает аналогично
методу OnPostAsync на странице Razor Edit.
Дополнительные ресурсы
Предыдущая статья. Работа с базой данных
Следующая статья. Добавление поиска
Часть 6. Добавление поиска в Razor
Pages в ASP.NET Core
Статья • 10.01.2023 • Чтение занимает 8 мин
Автор: Рик Андерсон
(Rick Anderson)
В следующих разделах добавляется поиск фильмов по жанру или имени.
Добавьте выделенный ниже код в Pages/Movies/Index.cshtml.cs :
C#
using
using
using
using
using
using
using
using
Microsoft.AspNetCore.Mvc;
Microsoft.AspNetCore.Mvc.RazorPages;
Microsoft.AspNetCore.Mvc.Rendering;
Microsoft.EntityFrameworkCore;
RazorPagesMovie.Models;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
namespace RazorPagesMovie.Pages.Movies
{
public class IndexModel : PageModel
{
private readonly RazorPagesMovie.Data.RazorPagesMovieContext
_context;
public IndexModel(RazorPagesMovie.Data.RazorPagesMovieContext
context)
{
_context = context;
}
public IList<Movie> Movie { get;set; } = default!;
[BindProperty(SupportsGet = true)]
public string ? SearchString { get; set; }
public SelectList ? Genres { get; set; }
[BindProperty(SupportsGet = true)]
public string ? MovieGenre { get; set; }
В предыдущем коде:
SearchString : содержит текст, который пользователи вводят в поле поиска.
SearchString также имеет атрибут [BindProperty]. [BindProperty] связывает
значения из формы и строки запроса с тем же именем, что и у свойства.
[BindProperty(SupportsGet = true)] требуется для привязки в запросах
HTTP GET.
Genres : содержит список жанров. Genres дает пользователю возможность
выбрать жанр в списке. Для SelectList требуется using
Microsoft.AspNetCore.Mvc.Rendering; .
MovieGenre : содержит конкретный жанр, выбранный пользователем.
Например, "Боевик".
Genres и MovieGenre рассматриваются позднее в этом учебнике.
2 Предупреждение
В целях безопасности вам следует задать привязку данных запроса GET к
свойствам страничной модели. Проверьте введенные данные пользователя,
прежде чем сопоставлять их со свойствами. Привязка GET полезна при
обращении к сценариям, использующим строку запроса или значения
маршрутов.
Чтобы привязать свойство к запросам GET , задайте для свойства SupportsGet
атрибута [BindProperty] значение true :
C#
[BindProperty(SupportsGet = true)]
Дополнительные сведения: ASP.NET Core Community Standup: Bind on GET
discussion (YouTube) .
Обновите метод OnGetAsync страницы Index, добавив следующий код:
C#
public async Task OnGetAsync()
{
var movies = from m in _context.Movie
select m;
if (!string.IsNullOrEmpty(SearchString))
{
movies = movies.Where(s => s.Title.Contains(SearchString));
}
Movie = await movies.ToListAsync();
}
В первой строке метода OnGetAsync создается запрос LINQ для выбора фильмов:
C#
// using System.Linq;
var movies = from m in _context.Movie
select m;
Этот запрос только определяется в этой точке и не выполняется для базы данных.
Если свойство SearchString не равно NULL и не пусто, запрос фильмов изменяется
для фильтрации по строке поиска:
C#
if (!string.IsNullOrEmpty(SearchString))
{
movies = movies.Where(s => s.Title.Contains(SearchString));
}
Код s => s.Title.Contains() представляет собой лямбда-выражение. Лямбдавыражения используются в запросах LINQ на основе методов в качестве
аргументов стандартных методов операторов запроса, таких как метод Where или
Contains . Запросы LINQ не выполняются, если они определяются или изменяются
путем вызова метода, например Where , Contains или OrderBy . Вместо этого
выполнение запроса откладывается. Вычисление выражения откладывается до тех
пор, пока не будет выполнена итерация его реализованного значения или не будет
вызван метод ToListAsync . Дополнительные сведения см. в разделе Выполнение
запроса.
7 Примечание
Метод Contains выполняется в базе данных, а не в коде C#. Регистр символов
запроса учитывается в зависимости от параметров базы данных и сортировки.
В SQL Server метод Contains сопоставляется с SQL LIKE, в котором регистр
символов не учитывается. SQLite с параметрами сортировки по умолчанию —
это смесь параметров с учетом регистра и параметров, которые НЕ учитывают
регистр, в зависимости от запроса. Сведения о том, как выполнять запросы
SQLite, которые не учитывают регистр, см. в следующих статьях:
Эта проблема в GitHub
Эта проблема в GitHub
Параметры сортировки и учет регистра
Перейдите на страницу Movies и добавьте строку запроса, например ?
searchString=Ghost , к URL-адресу. Например, https://localhost:5001/Movies?
searchString=Ghost . Отображаются отфильтрованные фильмы.
Если на страницу Index добавлен следующий шаблон маршрута, строку поиска
можно передать в виде сегмента URL-адреса. Например,
https://localhost:5001/Movies/Ghost .
CSHTML
@page "{searchString?}"
Предыдущее ограничение маршрута разрешало поиск названия в виде данных
маршрута (сегмент URL-адреса) вместо значения строки запроса. Символ ? в "
{searchString?}" означает, что этот параметр является необязательным.
Среда выполнения ASP.NET Core использует привязку модели, чтобы присвоить
значение свойства SearchString по строке запроса ( ?searchString=Ghost ) или
данным маршрута ( https://localhost:5001/Movies/Ghost ). Привязка модели не
учитывает регистр символов.
Однако пользователи не могут изменять URL-адрес для поиска фильма. На этом
шаге добавляется пользовательский интерфейс для поиска фильмов. Если было
добавлено ограничение маршрута "{searchString?}" , удалите его.
Откройте файл Pages/Movies/Index.cshtml и добавьте разметку, которая выделена в
следующем коде:
CSHTML
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<form>
<p>
Title: <input type="text" asp-for="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
@*Markup removed for brevity.*@
Тег HTML <form> использует следующие вспомогательные функции тегов:
вспомогательная функция тега форм. При отправке формы строка фильтра
отправляется на страницу Pages/Movies/Index в строке запроса.
Вспомогательная функция тега Input
Сохраните изменения и проверьте работу фильтра.
Поиск по жанру
Обновите метод OnGetAsync страницы Index, добавив следующий код:
C#
public async Task OnGetAsync()
{
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
var movies = from m in _context.Movie
select m;
if (!string.IsNullOrEmpty(SearchString))
{
movies = movies.Where(s => s.Title.Contains(SearchString));
}
if (!string.IsNullOrEmpty(MovieGenre))
{
movies = movies.Where(x => x.Genre == MovieGenre);
}
Genres = new SelectList(await genreQuery.Distinct().ToListAsync());
Movie = await movies.ToListAsync();
}
Следующий код определяет запрос LINQ, который извлекает все жанры из базы
данных.
C#
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
Список жанров SelectList создается путем проецирования отдельных жанров.
C#
Genres = new SelectList(await genreQuery.Distinct().ToListAsync());
Добавление поиска по жанру на страницу Razor
Обновите Index.cshtml элемент <form> , как показано в следующей разметке:
CSHTML
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<form>
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
Title: <input type="text" asp-for="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>
Проверьте работу приложения, выполнив поиск по жанру, по названию фильма и
по обоим этим параметрам.
Предыдущая статья. Обновление страниц
Следующая статья. Добавление нового поля
Часть 7. Добавление нового поля на
страницу Razor в ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 20 мин
Автор: Рик Андерсон
(Rick Anderson)
В этом разделе Entity Framework Code First Migrations используется для выполнения
следующих задач:
Добавление нового поля в модель.
Перенос изменений в схеме нового поля в базу данных.
Если вы используете EF Code First для автоматического создания и отслеживания
базы данных, Code First:
добавляет в базу данных таблицу __EFMigrationsHistory, которая позволяет
отслеживать синхронизацию схемы базы данных с классами модели, на
основе которой она была создана.
Если классы модели не синхронизированы с базой данных, выдает
исключение.
Автоматическая проверка синхронизации схемы и модели упрощает поиск
несогласованных проблем в коде базы данных.
Добавление свойства Rating в модель Movie
1. Откройте файл Models/Movie.cs и добавьте свойство Rating :
C#
public class Movie
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; } = string.Empty;
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
public string Rating { get; set; } = string.Empty;
}
2. Отредактируйте Pages/Movies/Index.cshtml и добавьте поле Rating :
CSHTML
@page
@model RazorPagesMovie.Pages.Movies.IndexModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<form>
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
Title: <input type="text" asp-for="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model
</th>
<th>
@Html.DisplayNameFor(model
model.Movie[0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model
</th>
<th>
@Html.DisplayNameFor(model
</th>
<th>
@Html.DisplayNameFor(model
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movie)
{
<tr>
=> model.Movie[0].Title)
=>
=> model.Movie[0].Genre)
=> model.Movie[0].Price)
=> model.Movie[0].Rating)
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rating)
</td>
<td>
<a asp-page="./Edit" asp-routeid="@item.Id">Edit</a> |
<a asp-page="./Details" asp-routeid="@item.Id">Details</a> |
<a asp-page="./Delete" asp-routeid="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
3. Обновите следующие страницы, добавив поле Rating :
Pages/Movies/Create.cshtml
.
Pages/Movies/Delete.cshtml
.
Pages/Movies/Details.cshtml
.
Pages/Movies/Edit.cshtml
.
Для работы приложения необходимо обновить базу данных, включив в нее новое
поле. При запуске приложения без обновления базы данных возникает
SqlException :
SqlException: Invalid column name 'Rating'.
Исключение SqlException связано с тем, что обновленный класс модели Movie
отличается от схемы таблицы Movie в базе данных. В таблице базы данных нет
столбца Rating .
Устранить эту ошибку можно несколькими способами:
1. Можно с помощью Entity Framework автоматически удалить и повторно
создать базу данных на основе новой схемы класса модели. Этот подход
удобен на ранних стадиях цикла разработки. В этом случае развитие модели и
схемы базы данных осуществляется одновременно. Недостатком такого
подхода является потеря существующих данных в базе. В рабочей базе
данных применять этот подход не следует! При разработке приложения часто
выполняется удаление базы данных при изменении схемы, для чего
используется инициализатор для автоматического заполнения базы
тестовыми данными.
2. Можно явно изменить схему существующей базы данных в соответствии с
новыми классами модели. Преимущество подхода в том, что он сохраняет
данные. Внесите это изменение вручную либо путем создания скрипта
изменения для базы данных.
3. Можно обновить схему базы данных с помощью Code First Migrations.
В этом руководстве используется Code First Migrations.
Обновите класс SeedData так, чтобы он предоставлял значение нового столбца.
Ниже приведен пример изменения, которое необходимо реализовать для каждого
блока new Movie .
C#
context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-2-12"),
Genre = "Romantic Comedy",
Price = 7.99M,
Rating = "R"
},
См. готовый файл SeedData.cs .
Постройте решение.
Visual Studio
Добавление миграции для поля Rating
1. В меню Сервис последовательно выберите пункты Диспетчер пакетов
NuGet > Консоль диспетчера пакетов.
2. В PMC введите следующие команды:
PowerShell
Add-Migration Rating
Update-Database
Команда Add-Migration задает следующие инструкции для платформы:
Сравните модель Movie со схемой базы данных Movie .
Создайте код для переноса схемы базы данных в новую модель.
В качестве имени файла переноса используется произвольное имя "Rating".
Рекомендуется присваивать этому файлу понятное имя.
Команда Update-Database указывает платформе, что к базе данных нужно
применить изменения схемы, а также сохранить существующие данные.
Если удалить все записи из базы данных, при инициализации она будет
заполнена начальными значениями и в нее будет включено поле Rating . Это
можно сделать с помощью ссылок удаления в браузере или из обозревателя
объектов SQL Server (SSOX).
Другой вариант — удалить базу данных и использовать миграции для
повторного создания базы данных. Удаление базы данных в SSOX:
1. Выберите базу данных в SSOX.
2. Щелкните базу данных правой кнопкой мыши и выберите Удалить.
3. Выберите Закрыть существующие соединения.
4. Нажмите кнопку ОК.
5. Обновите базу данных в PMC.
PowerShell
Update-Database
Запустите приложение и проверьте возможность создания, редактирования и
отображения фильмов с использованием поля Rating . Если база данных не
заполнена начальными значениями, задайте точку останова в методе
SeedData.Initialize .
Дополнительные ресурсы
Предыдущая статья. Добавление поиска
Следующая статья. Добавление проверки
Часть 8 серии руководств по Razor
Pages
Статья • 28.01.2023 • Чтение занимает 26 мин
Автор: Рик Андерсон
(Rick Anderson)
В этом разделе к модели Movie добавляется логика проверки. Правила проверки
применяются каждый раз, когда пользователь создает или редактирует фильм.
Проверка
Ключевой принцип разработки программного обеспечения называется DRY
(от
английского "Don't Repeat Yourself" — не повторяйся). При разработке Razor Pages
рекомендуется задавать любые функциональные возможности лишь один раз и
затем при необходимости отражать их в рамках всего приложения. Принцип "Не
повторяйся" может помочь:
сократить объем кода в приложении;
снизить вероятность возникновения ошибки в коде и упростить его
тестирование и поддержку.
Ярким примером применения принципа "Не повторяйся" является поддержка
проверки, реализуемая в Razor Pages и на платформе Entity Framework:
Правила проверки декларативно определяются в одном месте, в классе
модели.
Правила применяются по всему приложению.
Добавление правил проверки к модели
фильма
Пространство имен System.ComponentModel.DataAnnotations предоставляет:
Набор встроенных атрибутов проверки, которые декларативно применяются
к классу или свойству.
Атрибуты форматирования (такие как [DataType] ), которые обеспечивают
форматирование и не предназначены для проверки.
Обновите класс Movie , чтобы использовать преимущества встроенных атрибутов
проверки [Required] , [StringLength] , [RegularExpression] и [Range] .
C#
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models;
public class Movie
{
public int Id { get; set; }
[StringLength(60, MinimumLength = 3)]
[Required]
public string Title { get; set; } = string.Empty;
// [Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$")]
[Required]
[StringLength(30)]
public string Genre { get; set; } = string.Empty;
[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
[StringLength(5)]
[Required]
public string Rating { get; set; } = string.Empty;
}
Атрибуты проверки определяют поведение для свойств модели, к которым они
применяются:
Атрибуты [Required] и [MinimumLength] означают, что свойство должно иметь
значение. Пользователь может свободно вводить пробелы для выполнения
этой проверки.
Атрибут [RegularExpression] ограничивает набор допустимых для ввода
символов. В приведенном выше коде в Genre :
должны использоваться только буквы;
первая буква должна быть прописной; Пробелы разрешены, а цифры и
специальные символы — нет.
RegularExpression Rating :
первый символ должен быть прописной буквой;
допускаются специальные символы и цифры, а также последующие
пробелы. значение "PG-13" допустимо для рейтинга, но недопустимо для
Genre ;
атрибут [Range] ограничивает значения указанным диапазоном.
Атрибут [StringLength] может задавать максимальную и, при необходимости,
минимальную длину строкового свойства.
Типы значений (например, decimal , int , float , DateTime ) по своей природе
являются обязательными и не требуют атрибута [Required] .
Указанные выше правила проверки используются для демонстрации, они не
являются оптимальными для рабочей системы. Например, предыдущее
исключение не позволяет ввести модель Movie с двумя символами и не допускает
специальные знаки в Genre .
Наличие правил проверки, которые автоматически применяются ASP.NET Core,
помогает:
сделать приложение более надежным;
сократить возможность сохранения недопустимых данных в базе данных.
Пользовательский интерфейс проверки ошибок в
Razor Pages
Запустите приложение и перейдите в раздел "Pages/Movies" (Страницы/фильмы).
Щелкните ссылку Create New (Создать). Введите в форму какие-либо недопустимые
значения. Если функция проверки jQuery на стороне клиента обнаруживает
ошибку, сведения о ней отображаются в соответствующем сообщении.
7 Примечание
Возможно, вы не сможете вводить десятичные запятые в полях для
десятичных чисел. Чтобы обеспечить поддержку проверки jQuery
для
других языков, кроме английского, используйте вместо десятичной точки
запятую (","), а для отображения данных в форматах для других языков, кроме
английского, выполните действия, необходимые для глобализации вашего
приложения. Инструкции по добавлению десятичной запятой см. в
комментарии GitHub 4076 .
Обратите внимание, что для каждого поля, содержащего недопустимое значение, в
форме автоматически отображается сообщение об ошибке проверки. Эти ошибки
применяются как на стороне клиента (с помощью JavaScript и jQuery), так и на
стороне сервера (если пользователь отключает JavaScript).
Основным преимуществом является то, что на страницах создания или
редактирования не требуется изменять код. Пользовательский интерфейс
проверки активируется после применения к модели атрибутов проверки
достоверности. В Razor Pages, создаваемом в рамках этого руководства, правила
проверки применяются автоматически (для этого к свойствам класса модели Movie
применяются атрибуты проверки). При проверке страницы редактирования
применяются те же правила.
Данные формы передаются на сервер только после того, как будут устранены все
ошибки проверки на стороне клиента. Чтобы убедиться, что данные формы не
отправляются, используйте любой из следующих способов.
Поместите точку останова в метод OnPostAsync . Отправьте форму, выбрав
Создать или Сохранить. Точка останова не достигается ни при каких
обстоятельствах.
Используйте инструмент Fiddler .
Проследите сетевой трафик с помощью инструментов разработчика для
браузера.
Проверка на стороне сервера
Если в браузере отключен JavaScript, форма с ошибками отправляется на сервер.
Реализация проверки на стороне сервера:
1. Отключите JavaScript в браузере. JavaScript можно отключить с помощью
средств разработчика в браузере. Если JavaScript невозможно отключить в
этом браузере, попробуйте использовать другой браузер.
2. Поместите точку останова в метод OnPostAsync страниц создания или
редактирования.
3. Отправьте форму с недопустимыми данными.
4. Проверка недопустимого состояния модели:
C#
if (!ModelState.IsValid)
{
return Page();
}
Иначе отключите проверку на стороне клиента на сервере.
В следующем коде демонстрируется часть страницы Create.cshtml , созданной
ранее в рамках этого руководства. Она используется на страницах создания и
редактирования для выполнения следующих действий:
Отображения начальной формы.
Повторного отображения формы в случае ошибки.
CSHTML
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Movie.Title" class="control-label"></label>
<input asp-for="Movie.Title" class="form-control" />
<span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>
Вспомогательная функция тега Input использует атрибуты DataAnnotations и
создает HTML-атрибуты, необходимые для проверки jQuery на стороне клиента.
Вспомогательная функция тега Validation отображает ошибки проверки.
Дополнительные сведения см. в разделе Проверка.
На страницах создания и редактирования не определены правила проверки.
Правила проверки и строки ошибок указываются только в классе Movie . Они
автоматически применяются к Razor Pages, которые редактируют модель Movie .
Любые необходимые изменения логики проверки осуществляются исключительно
в модели. Проверка применяется согласованно во всем приложении, логика
проверки определяется в одном месте. Такой подход позволяет максимально
оптимизировать код и упростить его поддержку и обновление.
Использование атрибутов DataType
Проверьте класс Movie . В пространстве имен System.ComponentModel.DataAnnotations
в дополнение к набору встроенных атрибутов проверки предоставляются атрибуты
форматирования. Атрибут [DataType] применяется к свойствам ReleaseDate и
Price .
C#
// [Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
Атрибуты [DataType] предоставляют:
Указания для обработчика представлений для форматирования данных.
Атрибуты, например <a> для URL-адресов и <a
href="mailto:EmailAddress.com"> для электронной почты.
Используйте атрибут [RegularExpression] для проверки формата данных. Атрибут
[DataType] позволяет указать тип данных с более точным определением по
сравнению со встроенным типом базы данных. Атрибуты [DataType] не
предназначены для проверки. В примере приложения отображается только дата
без времени.
Перечисление DataType предоставляет множество типов данных, таких как Date ,
Time , PhoneNumber , Currency , EmailAddress и т. д.
Атрибуты [DataType] :
Может включить автоматическое предоставление приложению функций для
конкретных типов. Например, можно создавать ссылку mailto: для
DataType.EmailAddress .
Могут предоставить селектор даты DataType.Date в браузерах,
поддерживающих HTML5.
Создают атрибуты HTML 5 data- , которые используются браузерами с
поддержкой HTML 5.
Не предназначены для проверки.
DataType.Date не задает формат отображаемой даты. По умолчанию поле данных
отображается с использованием форматов, установленных в параметрах
CultureInfo сервера.
Требуются заметки к данным [Column(TypeName = "decimal(18, 2)")] , чтобы Entity
Framework Core корректно сопоставила Price с валютой в базе данных.
Дополнительные сведения см. в разделе Типы данных.
С помощью атрибута [DisplayFormat] можно явно указать формат даты:
C#
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode =
true)]
public DateTime ReleaseDate { get; set; }
Параметр ApplyFormatInEditMode указывает, что при отображении значения для
редактирования будет применяться форматирование. Это поведение может быть
нежелательным для некоторых полей. Например, в полях валюты в
пользовательском интерфейсе редактирования использовать символ денежной
единицы, как правило, не требуется.
Атрибут [DisplayFormat] может использоваться отдельно, однако чаще всего его
рекомендуется применять вместе с атрибутом [DataType] . Атрибут [DataType]
передает семантику данных (в отличие от способа их вывода на экран). Атрибут
[DataType] дает следующие преимущества, недоступные в [DisplayFormat] :
Поддержка функций HTML5 в браузере, например отображение элемента
управления календарем, соответствующего языковому стандарту символа
валюты, ссылок электронной почты и т. д.
По умолчанию формат отображения данных в браузере определяется в
соответствии с установленным языковым стандартом.
Атрибут [DataType] обеспечивает поддержку платформы ASP.NET Core для
выбора соответствующего шаблона поля, применяемого при отображении
данных. При отдельном использовании атрибут DisplayFormat базируется на
строковом шаблоне.
Примечание. Проверка jQuery не работает с атрибутом [Range] и DateTime .
Например, следующий код всегда приводит к возникновению ошибки проверки на
стороне клиента, даже если дата попадает в указанный диапазон:
C#
[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]
Лучшей методикой будет не компилировать модели с фиксированными датами,
поэтому использовать атрибуты [Range] и DateTime следует крайне осторожно.
Используйте конфигурацию для диапазонов дат и других значений, подверженных
частым изменениям, а не указывайте их в коде.
В следующем коде демонстрируется объединение атрибутов в одной строке:
C#
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace RazorPagesMovie.Models;
public class Movie
{
public int Id { get; set; }
[StringLength(60, MinimumLength = 3)]
public string Title { get; set; } = string.Empty;
[Display(Name = "Release Date"), DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$"), Required, StringLength(30)]
public string Genre { get; set; } = string.Empty;
[Range(1, 100), DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$"), StringLength(5)]
public string Rating { get; set; } = string.Empty;
}
Начало работы с Razor Pages и EF Core показывает расширенные EF Core операции
со Razor Страницами.
Применение миграции
DataAnnotation, примененные к классу, изменяют схему. Например, DataAnnotation,
примеренные к полю Title , выполняют следующее:
C#
[StringLength(60, MinimumLength = 3)]
[Required]
public string Title { get; set; } = string.Empty;
ограничивают число символов до 60;
не допускают значение null .
Сейчас таблица Movie имеет следующую схему:
SQL
CREATE TABLE [dbo].[Movie] (
[ID]
INT
[Title]
NVARCHAR (MAX)
[ReleaseDate] DATETIME2 (7)
[Genre]
NVARCHAR (MAX)
[Price]
DECIMAL (18, 2)
[Rating]
NVARCHAR (MAX)
CONSTRAINT [PK_Movie] PRIMARY
);
IDENTITY (1, 1) NOT NULL,
NULL,
NOT NULL,
NULL,
NOT NULL,
NULL,
KEY CLUSTERED ([ID] ASC)
Предыдущие изменения схемы не приводят к созданию исключения EF. Но следует
создать миграцию, чтобы схема соответствовала модели.
Visual Studio
В меню Сервис последовательно выберите пункты Диспетчер пакетов NuGet
> Консоль диспетчера пакетов. В PMC введите следующие команды:
PowerShell
Add-Migration New_DataAnnotations
Update-Database
Update-Database выполняет методы Up класса New_DataAnnotations . Проверьте
метод Up .
C#
public partial class NewDataAnnotations : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Title",
table: "Movie",
type: "nvarchar(60)",
maxLength: 60,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");
migrationBuilder.AlterColumn<string>(
name: "Rating",
table: "Movie",
type: "nvarchar(5)",
maxLength: 5,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");
migrationBuilder.AlterColumn<string>(
name: "Genre",
table: "Movie",
type: "nvarchar(30)",
maxLength: 30,
nullable: false,
oldClrType: typeof(string),
oldType: "nvarchar(max)");
}
Обновленная таблица Movie имеет следующую схему:
SQL
CREATE TABLE [dbo].[Movie] (
[ID]
INT
[Title]
NVARCHAR (60)
[ReleaseDate] DATETIME2 (7)
[Genre]
NVARCHAR (30)
[Price]
DECIMAL (18, 2)
[Rating]
NVARCHAR (5)
CONSTRAINT [PK_Movie] PRIMARY
);
IDENTITY (1, 1) NOT NULL,
NOT NULL,
NOT NULL,
NOT NULL,
NOT NULL,
NOT NULL,
KEY CLUSTERED ([ID] ASC)
Публикация в Azure
Сведения о развертывании в Azure, см. в разделе Учебник: Создание приложения
ASP.NET Core в Azure с подключением к базе данных SQL.
Благодарим вас за изучение общих сведений о страницах Razor. Начало работы с
Razor Pages и EF Core является отличным продолжением работы с этим
руководством.
Дополнительные ресурсы
Вспомогательные функции тегов в формах в ASP.NET Core
Глобализация и локализация в ASP.NET Core
Вспомогательные функции тегов в ASP.NET Core
Создание вспомогательных функций тегов в ASP.NET Core
Предыдущая статья. Добавление нового поля
Начало работы с MVC ASP.NET Core
Статья • 15.11.2022 • Чтение занимает 17 мин
Автор: Рик Андерсон
(Rick Anderson)
В этом руководстве описывается веб-разработка MVC ASP.NET Core с
контроллерами и представлениями. Если вы не знакомы с веб-разработкой
ASP.NET Core, для начала изучите версию этого руководства для Razor Pages. См.
статью Выбор пользовательского интерфейса ASP.NET Core, где сравниваются Razor
Pages, MVC и Blazor для разработки пользовательского интерфейса.
Это первое руководство из серии материалов по веб-разработке MVC ASP.NET
Core с использованием контроллеров и представлений.
Пройдя всю серию, вы создадите приложение, которое управляет базой данных
фильмов и отображает ее. Вы научитесь:
" Создание веб-приложения.
" Добавление модели и формирование шаблона.
" Работа с базой данных.
" Добавление поиска и проверки.
Просмотреть или скачать пример кода
(описание скачивания).
Предварительные требования
Visual Studio
Последняя предварительная версия Visual Studio 2022
с рабочей
нагрузкой ASP.NET и веб-разработка .
Создание веб-приложения
Visual Studio
Запустите Visual Studio и щелкните Создать проект
В диалоговом окне Создание проекта выберите Веб-приложение
ASP.NET Core (Модель — представление — контроллер)>Далее.
В диалоговом окне Настроить новый проект введите MvcMovie в поле
Имя проекта. Важно присвоить проекту имя MvcMovie. Регистр символов
должен соответствовать каждому экземпляру namespace при копировании
кода.
Выберите Далее.
В диалоговом окне Дополнительные сведения выполните следующие
действия.
Выберите .NET 7.0.
Убедитесь, что флажок Не использовать операторы верхнего уровня
снят.
Нажмите кнопку создания.
Дополнительные сведения, включая альтернативные подходы к созданию
проекта, см. в статье Создание проекта в Visual Studio.
В Visual Studio используется шаблон проекта по умолчанию для созданного
проекта MVC. Созданный проект это:
рабочее приложение;
простой начальный проект.
Запуск приложения
Visual Studio
Нажмите клавиши CTRL+F5, чтобы запустить приложение без отладчика.
Visual Studio отображает следующее диалоговое окно, если проект еще не
настроен для использования SSL:
Выберите Да, чтобы сделать SSL-сертификат IIS Express доверенным.
Отобразится следующее диалоговое окно.
Выберите Да, если согласны доверять сертификату разработки.
Сведения о доверии к браузеру Firefox см. в разделе Ошибка сертификата
браузера Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
Visual Studio запускает приложение и открывает браузер по умолчанию.
В адресной строке указывается localhost:<port#> , а не что-либо типа
example.com . Стандартное имя узла для локального компьютера — localhost .
Когда Visual Studio создает веб-проект, для веб-сервера используется
случайный порт.
Запуск приложения без отладки путем нажатия клавиш CTRL+F5 позволяет:
Внесите изменения в код.
Сохраните файл.
Быстро обновить браузер и просмотреть изменения в коде.
Из меню Отладка можно запустить приложение с отладкой или без:
Вы можете выполнить отладку приложения, нажав кнопку HTTPS на панели
инструментов:
Пример приложения приведен на следующем рисунке:
Visual Studio
Справка по Visual Studio
Сведения об отладке кода C# с помощью Visual Studio
Введение в интегрированную среду разработки Visual Studio
В следующем руководстве этой серии вы узнаете о MVC и о том, как начать писать
код.
Далее: Добавление контроллера
Часть 2. Добавление контроллера в
приложение MVC ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 15 мин
Автор: Рик Андерсон
(Rick Anderson)
Модель архитектуры MVC разделяет приложение на три основных компонента:
Model (модель), View (представление) и Controller (контроллер). С помощью
модели MVC можно создавать приложения, которые удобнее тестировать и
обновлять по сравнению с традиционными монолитными приложениями.
Приложения на основе модели MVC содержат следующее:
Модели: классы, представляющие данные в приложении. Классы модели
используют логику проверки, которая позволяет применять бизнес-правила к
этим данным. Как правило, объекты модели извлекают и сохраняют состояние
модели в базе данных. В этом руководстве модель Movie извлекает сведения о
фильмах из базы данных и передает их в представление или обновляет.
Обновленные данные записываются в базу данных.
Представления: компоненты, которые формируют пользовательский
интерфейс приложения. Как правило, в пользовательском интерфейсе
отображаются данные модели.
Контроллеры: классы, которые:
обрабатывают запросы браузера;
получают данные модели;
вызывают шаблоны представления вызовов, которые возвращают ответ.
В приложении MVC представление используется только для отображения
информации. Контроллер обрабатывает и реагирует на ввод и
взаимодействие пользователя. Например, контроллер обрабатывает сегменты URLадреса и значения строки запроса и передает эти значения в модель. Модель
может использовать эти значения для выполнения запросов к базе данных.
Пример.
https://localhost:5001/Home/Privacy : задает контроллер Home и действие
Privacy .
https://localhost:5001/Movies/Edit/5 : это запрос на изменение фильма с ID=5
с помощью контроллера Movies и действия Edit , которые подробно описаны
далее в этом руководстве.
Данные маршрута описаны далее в этом руководстве.
Структура архитектуры MVC разделяет приложение на три основных группы
компонентов: модели, представления и контроллеры. Этот шаблон помогает
реализовать принципы разделения задач: логика пользовательского интерфейса
относится к представлению. Логика ввода относится к контроллеру. Бизнес-логика
размещается в модели. Подобное разделение позволяет эффективно справляться с
трудностями при построении приложения, поскольку оно дает возможность
работать одновременно с одним аспектом реализации, не затрагивая при этом код
других. Например, вы можете изменять код представления независимо от кода
бизнес-логики.
Эти принципы продемонстрированы в этой серии руководств при создании
приложения для работы с фильмами. Проект MVC содержит папки для
контроллеров и представлений.
Добавление контроллера
Visual Studio
В Обозревателе решений щелкните правой кнопкой мыши Контроллеры >
Добавить > Контроллер.
В диалоговом окне Добавление нового шаблонного элемента выберите
Контроллер MVC — пустой>Добавить.
В диалоговом окне Добавление нового элемента — MvcMovie введите
HelloWorldController.cs и щелкните Добавить.
Замените все содержимое Controllers/HelloWorldController.cs следующим кодом:
C#
using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;
namespace MvcMovie.Controllers;
public class HelloWorldController : Controller
{
//
// GET: /HelloWorld/
public string Index()
{
return "This is my default action...";
}
//
// GET: /HelloWorld/Welcome/
public string Welcome()
{
return "This is the Welcome action method...";
}
}
Каждый метод public в контроллере вызывается как конечная точка HTTP. В
приведенном выше примере оба метода возвращают строку. Обратите внимание
на комментарии, предшествующие каждому методу.
Конечная точка HTTP:
Это URL-адрес, который является целевым в веб-приложении, например
https://localhost:5001/HelloWorld .
Она объединяет:
используемый протокол: HTTPS ;
сетевое расположение веб-сервера, включая порт TCP: localhost:5001 ;
целевой универсальный код ресурса (URI): HelloWorld .
В первом комментарии указано, что этот метод HTTP GET
вызывается путем
добавления /HelloWorld/ к базовому URL-адресу.
Во втором комментарии указано, что этот метод HTTP GET
вызывается путем
добавления /HelloWorld/Welcome/ к URL-адресу. Далее в этом руководстве
используется механизм формирования шаблонов для создания методов HTTP POST ,
которые обновляют данные.
Запустите приложение без отладчика.
Добавьте "HelloWorld" к пути в адресной строке. Метод Index возвращает строку.
MVC вызывает классы контроллера (и методы действия в них) в зависимости от
входящего URL-адреса. Логика маршрутизации URL-адресов, используемая
моделью MVC по умолчанию, определяет вызываемый код на основе формата
следующего вида:
/[Controller]/[ActionName]/[Parameters]
Формат маршрутизации задается в файле Program.cs .
C#
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Если при переходе к приложению не указать сегменты URL-адреса, по умолчанию
устанавливаются контроллер Home и метод Index, которые заданы в выделенной
выше строке шаблона. В предшествующих сегментах URL-адресов:
Первый сегмент URL-адреса определяет класс контроллера, который будет
выполняться. Поэтому localhost:5001/HelloWorld сопоставляется с классом
Controller HelloWorld.
Вторая часть сегмента URL-адреса определяет метод действия для класса.
Таким образом, localhost:5001/HelloWorld/Index выполняет метод Index
класса HelloWorldController . Обратите внимание, что в этом случае
достаточно перейти по адресу localhost:5001/HelloWorld , а метод Index
вызывается по умолчанию. Если имя вызываемого метода не указано явно,
для контроллера вызывается метод по умолчанию Index .
В третьей части сегмента URL-адреса ( id ) указываются данные маршрута.
Данные маршрута описаны далее в этом руководстве.
Перейдите по адресу https://localhost:{PORT}/HelloWorld/Welcome . Замените {PORT}
на номер порта.
Метод Welcome запускается и возвращает строку This is the Welcome action
method... . Для этого URL-адреса заданы контроллер HelloWorld и метод действия
Welcome . Часть URL-адреса [Parameters] на данный момент еще не использовалась.
Измените код, чтобы передать сведения о параметрах из URL-адреса в контроллер.
Например, /HelloWorld/Welcome?name=Rick&numtimes=4 .
Измените метод Welcome , включив два параметра, как показано в следующем коде.
C#
// GET: /HelloWorld/Welcome/
// Requires using System.Text.Encodings.Web;
public string Welcome(string name, int numTimes = 1)
{
return HtmlEncoder.Default.Encode($"Hello {name}, NumTimes is:
{numTimes}");
}
Предыдущий код:
Использует функцию необязательного параметра C#, указывая, что параметр
numTimes по умолчанию принимает значение 1, если ему не было передано
значение.
Использует HtmlEncoder.Default.Encode для защиты приложения от
злонамеренного ввода данных (например, JavaScript).
Использует интерполированные строки в $"Hello {name}, NumTimes is:
{numTimes}" .
Запустите приложение и перейдите по адресу https://localhost:
{PORT}/HelloWorld/Welcome?name=Rick&numtimes=4 . Замените {PORT} на номер порта.
Попробуйте использовать разные значения для name и numtimes в URL-адресе.
Система привязки модели MVC автоматически сопоставляет именованные
параметры из строки запроса с параметрами метода. Дополнительные сведения
см. в разделе Привязка модели.
На предыдущем рисунке:
Сегмент URL-адреса Parameters не используется.
Параметры name и numTimes передаются в строку запроса .
Вопросительный знак ( ? ) в приведенном выше URL-адресе используется в
качестве разделителя, после которого указывается строка запроса.
Символом & отделяются пары "поле-значение".
Замените метод Welcome следующим кодом:
C#
public string Welcome(string name, int ID = 1)
{
return HtmlEncoder.Default.Encode($"Hello {name}, ID: {ID}");
}
Запустите приложение и введите следующий URL-адрес: https://localhost:
{PORT}/HelloWorld/Welcome/3?name=Rick
В предыдущем URL-адресе:
Третий сегмент URL-адреса сопоставляется с параметром маршрута id .
Метод Welcome содержит параметр id , который сопоставляется с шаблоном
URL-адреса в методе MapControllerRoute .
Завершающий символ ? указывает на начало строки запроса
.
C#
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
В предшествующем примере:
Третий сегмент URL-адреса сопоставляется с параметром маршрута id .
Метод Welcome содержит параметр id , который сопоставляется с шаблоном
URL-адреса в методе MapControllerRoute .
Завершающий символ ? (в id? ) указывает, что параметр id является
необязательным.
Назад: Приступая к работе
Далее: Добавление представления
Часть 3. Добавление представления в
приложение MVC ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 20 мин
Автор: Рик Андерсон
(Rick Anderson)
В этом разделе вы внесете изменения в класс HelloWorldController для
использования файлов представления Razor. Это позволяет аккуратно
инкапсулировать процесс создания HTML-ответов в клиент.
Шаблоны представлений создаются с помощью Razor. Шаблоны представлений на
основе Razor:
имеют расширение файла .cshtml ;
реализуют удобный способ для создания выходных данных HTML с помощью
C#.
Сейчас метод Index возвращает строку с сообщением в классе контроллера. В
классе HelloWorldController замените метод Index следующим кодом:
C#
public IActionResult Index()
{
return View();
}
Предыдущий код:
вызывает метод View контроллера;
использует шаблон представления для создания HTML-ответа.
Методы контроллера:
называются методами действий; например, метод действия Index в
предыдущем коде;
обычно возвращают IActionResult или класс, производный от ActionResult, а не
тип, например string .
Добавление представления
Visual Studio
Щелкните правой кнопкой мыши папку Views, а затем выберите Добавить >
Новая папка. Назовите папку HelloWorld.
Щелкните правой кнопкой мыши папку Views/HelloWorld, а затем выберите
Добавить > Новый элемент.
В диалоговом окне Добавление нового элемента MvcMovie выполните
следующие действия.
В поле поиска в правом верхнем углу введите view (представление).
Выберите Представление Razor — пустое
В поле Index.cshtml оставьте значение Index.cshtml.
Нажмите Добавить
Замените содержимое файла представления Views/HelloWorld/Index.cshtml Razor
следующим.
CSHTML
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>Hello from our View Template!</p>
Перейдите к https://localhost:{PORT}/HelloWorld :
Метод Index в HelloWorldController выполнил оператор return View(); ,
который указал, что метод должен использовать файл шаблона
представления для отображения ответа в браузере.
Имя файла шаблона представления не указано, MVC по умолчанию
использует файл представления по умолчанию. Если имя файла
представления не указано, возвращается представление по умолчанию. В
этом примере представление по умолчанию имеет то же имя, что и метод
действия Index . Используется шаблон представления
/Views/HelloWorld/Index.cshtml .
На изображении ниже показана строка "Hello from our View Template!",
которая жестко задана в представлении:
Изменение представлений и страниц макета
Выберите ссылки в меню (MvcMovie, Home и Privacy ). Меню на каждой странице
имеют одинаковый макет. Макет меню реализован в файле
Views/Shared/_Layout.cshtml .
Откройте файл Views/Shared/_Layout.cshtml .
Шаблоны макета позволяют:
указать макет контейнера HTML сайта в одном месте;
применять макета контейнера HTML на нескольких страницах сайта.
Найдите строку @RenderBody() . RenderBody — это заполнитель, в котором
отображаются все создаваемые страницы для определенных представлений,
упакованные на странице макета. Например, если щелкнуть ссылку Privacy,
представление Views/Home/Privacy.cshtml отобразится в методе RenderBody .
Изменение заголовка, нижнего колонтитула
и ссылки меню в файле макета
Замените содержимое файла Views/Shared/_Layout.cshtml следующей разметкой:
Изменения выделены:
CSHTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Movie App</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"
/>
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbarlight bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
<a class="navbar-brand" asp-area="" asp-controller="Movies"
asp-action="Index">Movie App</a>
<button class="navbar-toggler" type="button" data-bstoggle="collapse" data-bs-target=".navbar-collapse" ariacontrols="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle
navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex
justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" aspcontroller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" aspcontroller="Home" asp-action="Privacy">Privacy</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
© 2022 - Movie App - <a asp-area="" asp-controller="Home"
asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
Приведенная выше разметка вносит следующие изменения:
замена трех вхождений MvcMovie на Movie App ;
Замена элемента привязки <a class="navbar-brand" asp-area="" aspcontroller="Home" asp-action="Index">MvcMovie</a> на <a class="navbar-brand"
asp-controller="Movies" asp-action="Index">Movie App</a> .
В приведенной выше разметке атрибут вспомогательной функции тега
привязки asp-area="" и значение атрибута были опущены, так как это приложение
не использует области (Areas).
Примечание. Контроллер Movies не был реализован. На этом этапе ссылка Movie
App не работает.
Сохраните изменения и щелкните ссылку Privacy . Обратите внимание, что
заголовок вкладки браузера отображает Privacy Политика — Приложение Movie
вместо Privacy Политика — MvcMovie.
Выберите ссылку Home.
Обратите внимание, что в заголовке и тексте привязки также отображается Movie
App. Внеся одно изменение в шаблон макета, мы изменили заголовок и текст
ссылки на всех страницах сайта.
Просмотрите файл Views/_ViewStart.cshtml .
CSHTML
@{
Layout = "_Layout";
}
Файл Views/_ViewStart.cshtml предоставляет файл Views/Shared/_Layout.cshtml для
каждого представления. Свойство Layout может задавать другое представление
макета или иметь значение null , при котором макет не используется.
Откройте файл представления Views/HelloWorld/Index.cshtml .
Измените заголовок и элемент <h2> , как выделено ниже:
CSHTML
@{
ViewData["Title"] = "Movie List";
}
<h2>My Movie List</h2>
<p>Hello from our View Template!</p>
Заголовок и элемент <h2> немного отличаются, чтобы вы видели, какой именно
фрагмент кода изменяет отображение.
ViewData["Title"] = "Movie List"; в приведенном выше коде присваивает свойству
Title словаря ViewData значение "Movie List". Свойство Title используется в
элементе HTML <title> на странице макета:
CSHTML
<title>@ViewData["Title"] - Movie App</title>
Сохраните изменения и перейдите к https://localhost:{PORT}/HelloWorld .
Обратите внимание, что были изменены следующие элементы:
Заголовок браузера.
Основной заголовок.
Дополнительные заголовки.
Если в браузере изменения не отображаются, это может означать, что содержимое
кэшировано. В этом случае нажмите в браузере клавиши CTRL+F5 для
принудительной загрузки ответа сервера. Заголовок браузера создается с
помощью атрибута ViewData["Title"] , который задается в шаблоне представления
Index.cshtml и дополнительной строки "- Movie App", добавляемой в файл макета.
Содержимое шаблона представления Index.cshtml объединяется с шаблоном
представления Views/Shared/_Layout.cshtml . В браузер отправляется один HTMLответ. Шаблоны макетов позволяют легко вносить изменения, применяемые ко
всем страницам в приложении. Дополнительные сведения см. в разделе Макет.
Небольшой фрагмент данных (сообщение "Hello from our View Template!") все
равно жестко задан в коде. Приложение MVC предоставляет представление, вы
реализуете контроллер, однако модели на данный момент еще нет.
Передача данных из контроллера в
представление
Действия контроллера вызываются в ответ на входящий запрос URL-адреса. Код,
обрабатывающий входящие запросы браузера, добавляется в класс контроллера.
Контроллер извлекает данные из источника данных и определяет тип ответа,
который будет отправлен в браузер. Контроллер может использовать шаблоны
представлений для создания и форматирования ответа HTML, отправляемого
браузеру.
Контроллер отвечает за предоставление данных, необходимых шаблону
представления для отображения ответа.
Шаблоны представлений недолжны:
выполнять бизнес-логику;
непосредственно взаимодействовать с базой данных.
Вместо этого они должны работать только с данными, которые им
предоставляет контроллер. Подобное разделение сфер ответственности позволяет
обеспечить максимальную оптимизацию кода, а также удобство его
очистки;
тестирования;
обслуживания.
Сейчас метод Welcome в классе HelloWorldController принимает параметры name и
ID , после чего выводит значения напрямую в браузер.
Вместо отображения ответа в виде строки настройте контроллер для
использования шаблона представления. Шаблон представления создает
динамический ответ, для получения которого необходимо передать
соответствующие данные из контроллера в представление. Для этого контроллер
может поместить динамические данные (параметры), которые требуются шаблону
представления, в словарь ViewData , к которому впоследствии будет обращаться
этот шаблон за динамическими данными.
В файле HelloWorldController.cs измените метод Welcome так, чтобы добавлять
значения Message и NumTimes в словарь ViewData .
Словарь ViewData представляет собой динамический объект, а это означает, что
можно использовать любой тип. Объект ViewData не имеет определенных свойств,
пока не будет добавлен какой-либо элемент. Система привязки модели MVC
автоматически сопоставляет именованные параметры name и numTimes из строки
запроса с параметрами метода. Полный HelloWorldController :
C#
using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;
namespace MvcMovie.Controllers;
public class HelloWorldController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult Welcome(string name, int numTimes = 1)
{
ViewData["Message"] = "Hello " + name;
ViewData["NumTimes"] = numTimes;
return View();
}
}
Объект словаря ViewData содержит данные, которые будут передаваться в
представление.
Создайте шаблон представления экрана приветствия с именем
Views/HelloWorld/Welcome.cshtml .
В шаблоне представления Welcome.cshtml создайте цикл, который будет
отображать строку "Hello" NumTimes . Замените все содержимое
Views/HelloWorld/Welcome.cshtml следующим:
CSHTML
@{
ViewData["Title"] = "Welcome";
}
<h2>Welcome</h2>
<ul>
@for (int i = 0; i < (int)ViewData["NumTimes"]!; i++)
{
<li>@ViewData["Message"]</li>
}
</ul>
Сохраните изменения и перейдите по следующему URL-адресу:
https://localhost:{PORT}/HelloWorld/Welcome?name=Rick&numtimes=4
Данные извлекаются по URL-адресу и передаются в контроллер с помощью
средства привязки модели MVC. Контроллер упаковывает данные в словарь
ViewData и передает этот объект в представление. Представление отображает
данные в формате HTML в браузере.
В примере выше мы использовали словарь ViewData для передачи данных из
контроллера в представление. Далее в этом руководстве для передачи данных из
контроллера в представление мы будем использовать модель представления.
Подход к передаче данных на основе модели представления является
предпочтительным относительно применения словаря ViewData .
В следующем руководстве создается база данных фильмов.
Назад: Добавление контроллера
Далее: Добавление модели
Часть 4. Добавление модели в
приложение MVC ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 51 мин
Авторы: Рик Андерсон
(Rick Anderson) и Джон П. Смит
(Jon P Smith)
В этом учебнике добавляются классы для управления фильмами в базе данных. Эти
классы представляет уровень модели в приложении MVC.
Эти классы моделей используются с Entity Framework Core (EF Core) для работы с
базой данных. EF Core — это платформа объектно-реляционного сопоставления
(ORM), которая упрощает написание кода доступа к данным.
Созданные классы модели известны как классы POCO (Plain Old CLR Objects —
простые старые объекты CLR). Классы POCO не имеют никакой зависимости EF
Coreот . Эти классы только определяют свойства данных, которые хранятся в базе
данных.
В этом руководстве сначала создаются классы моделей и EF Core создается база
данных.
Добавление класса модели данных
Visual Studio
Щелкните правой кнопкой мыши папку Models и выберите Добавить>Class
(Класс). Задайте файлу имя Movie.cs .
Обновите файл Models/Movie.cs , используя следующий код:
C#
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models;
public class Movie
{
public int Id { get; set; }
public string? Title { get; set; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
public decimal Price { get; set; }
}
Класс Movie содержит поле Id , которое требуется базе данных в качестве
первичного ключа.
Атрибут DataType для ReleaseDate указывает тип данных ( Date ). С этим атрибутом:
Пользователю не требуется вводить сведения о времени в поле даты.
Отображается только дата, а не время.
DataAnnotations рассматриваются в следующем руководстве.
Знак вопроса после string указывает, что свойство допускает значение NULL.
Дополнительные сведения см. в статье Ссылочные типы, допускающие значение
NULL.
Добавление пакетов NuGet
Visual Studio
В меню Сервис последовательно выберите пункты Диспетчер пакетов
NuGet>Консоль диспетчера пакетов (PMC).
В PMC выполните следующие команды:
PowerShell
Install-Package Microsoft.VisualStudio.Web.CodeGeneration.Design
Install-Package Microsoft.EntityFrameworkCore.Design
Install-Package Microsoft.EntityFrameworkCore.SqlServer
Приведенные выше команды добавляют следующие компоненты:
Поставщик EF Core SQL Server. Пакет поставщика устанавливает пакет в EF
Core качестве зависимости.
Программы, используемые пакетами, устанавливаются автоматически на
этапе формирования шаблонов далее в этом учебнике.
Выполните сборку проекта, чтобы проверить его на ошибки компиляции.
Формирования шаблона страниц фильмов
Используйте средство формирования шаблонов, чтобы создать страницы Create ,
Read , Update и Delete (CRUD) для модели фильма.
Visual Studio
В Обозревателе решений щелкните правой кнопкой мыши папку Controllers и
выберите Добавить > New Scaffolded Item (Создать шаблонный элемент).
В диалоговом окне Добавление нового элемента с шаблоном выберите
Контроллер MVC с представлениями, используя команду Добавить Entity
Framework>.
В диалоговом окне добавления контроллера MVC с представлениями с
использованием Entity Framework выполните следующее.
В раскрывающемся списке Класс модели выберите Фильм
(MvcMovie.Models) .
В строке Класс контекста данных щелкните знак плюса (+).
В диалоговом окне Добавление контекста данных создается имя
класса MvcMovie.Data.PagesMovieContext.
Выберите Добавить.
Представления и Имя контроллера: оставьте значения по умолчанию.
Выберите Добавить.
Если отобразится сообщение об ошибке, выберите Добавить еще раз, чтобы
повторить попытку.
При формировании шаблонов выполняются такие действия:
вставка необходимых ссылок на пакеты в файл проекта MvcMovie.csproj ;
регистрация контекста базы данных в файле Program.cs .
добавление строки подключения к базе данных в файл appsettings.json .
При формировании шаблонов выполняется создание таких объектов:
контроллер фильмов Controllers/MoviesController.cs ;
файлы представления Razor для страниц Create, Delete, Details, Edit и Index:
Views/Movies/*.cshtml ;
класс контекста для базы данных: Data/MvcMovieContext.cs .
Автоматическое создание этих файлов и их обновление называется
формированием шаблонов.
Сформированные страницы пока использовать нельзя, так как база данных не
существует. Запуск приложения и выбор ссылки Movie App приводит к
возникновению сообщения об ошибке Не удается открыть базу данных или no
such table: Movie (Такая таблица отсутствует: Movie).
Выполните сборку приложения, чтобы убедиться в отсутствии ошибок.
Первоначальная миграция
Используйте функцию EF CoreМиграции для создания базы данных. Миграции —
это набор средств, с помощью которых можно создавать и обновлять базы данных
в соответствии с моделью данных.
Visual Studio
В меню Сервис последовательно выберите пункты Диспетчер пакетов
NuGet>Консоль диспетчера пакетов.
Введите в консоли диспетчера пакетов (PMC) следующие команды:
PowerShell
Add-Migration InitialCreate
Update-Database
Add-Migration InitialCreate . Создает файл миграции
Migrations/{timestamp}_InitialCreate.cs . Аргумент InitialCreate — это
имя миграции. Можно использовать любое имя, но по соглашению
выбирается имя, которое описывает миграцию. Так как это первая
миграция, созданный класс содержит код для создания схемы базы
данных. Схема базы данных создается на основе модели, указанной в
классе MvcMovieContext .
Update-Database . Обновляет базу данных до последней миграции,
созданной предыдущей командой. Эта команда выполняет метод Up в
файле Migrations/{time-stamp}_InitialCreate.cs , который создает базу
данных.
Команда Update-Database выдает следующее предупреждение:
"Для десятичного столбца Price в типе сущности Movie не указан тип. Это
приведет к тому, что значения будут усекаться без вмешательства
пользователя, если они не помещаются в значения точности и масштаба
по умолчанию. С помощью метода HasColumnType() явно укажите тип
столбца SQL Server, который может вместить все значения".
Игнорируйте предыдущее предупреждение, оно уже исправлено в следующем
учебнике.
Дополнительные сведения о средствах PMC для EF Coreсм. в справочникеEF
Core по инструментам — PMC в Visual Studio.
Тестирование приложения
Запустите приложение и выберите ссылку Movie App.
Если вы получили исключение, похожее на следующее, возможно, вы пропустили
dotnet ef database update команду на шаге миграции:
Visual Studio
Консоль
SqlException: Cannot open database "MvcMovieContext-1" requested by the
login. The login failed.
7 Примечание
В поле Price нельзя вводить десятичные запятые. Чтобы обеспечить
поддержку проверки jQuery
для других языков, кроме английского,
используйте вместо десятичной точки запятую (,), а для отображения данных в
форматах для других языков, кроме английского, выполните глобализацию
приложения. Инструкции по глобализации см. на сайте GitHub
.
Изучение созданного класса контекста базы данных, а
также регистрации
При EF Coreиспользовании доступ к данным осуществляется с помощью модели.
Модель состоит из классов сущностей и объекта контекста, который представляет
сеанс взаимодействия с базой данных. Объект контекста позволяет выполнять
запросы и сохранять данные. Контекст базы данных наследуется от
Microsoft.EntityFrameworkCore.DbContext и определяет сущности, которые
необходимо включить в модель данных.
При формировании шаблонов создается класс контекста базы данных
Data/MvcMovieContext.cs :
C#
using
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.EntityFrameworkCore;
MvcMovie.Models;
namespace MvcMovie.Data
{
public class MvcMovieContext : DbContext
{
public MvcMovieContext (DbContextOptions<MvcMovieContext> options)
: base(options)
{
}
public DbSet<MvcMovie.Models.Movie> Movie { get; set; }
}
}
Приведенный выше код создает свойство DbSet<Movie>, представляющее фильмы
в базе данных.
Внедрение зависимостей
ASP.NET Core поддерживает внедрение зависимостей. Службы, такие как контекст
базы данных, регистрируются с помощью DI в Program.cs . Эти службы
предоставляются компонентам, которые запрашивают их через параметры
конструктора.
В файле Controllers/MoviesController.cs этот конструктор применяет внедрение
зависимостей для внедрения контекста базы данных MvcMovieContext в контроллер.
Контекст базы данных используется в каждом методе создания, чтения, обновления
и удаления
в контроллере.
При формировании шаблонов был создан следующий выделенный код в
Program.cs :
Visual Studio
C#
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));
Система конфигурации ASP.NET Core считывает строку подключения к базе данных
MvcMovieContext.
Проверка созданной строки подключения к базе
данных
При формировании шаблонов в файл appsettings.json была добавлена строка
подключения:
Visual Studio
JSON
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MvcMovieContext": "Server=
(localdb)\\mssqllocaldb;Database=MvcMovieContext7dc5;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
При локальной разработке система конфигурации ASP.NET Core считывает ключ
ConnectionString из файла appsettings.json .
Класс InitialCreate
Изучите файл миграции Migrations/{timestamp}_InitialCreate.cs .
C#
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace MvcMovie.Migrations
{
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Movie",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Title = table.Column<string>(type: "nvarchar(max)",
nullable: true),
ReleaseDate = table.Column<DateTime>(type: "datetime2",
nullable: false),
Genre = table.Column<string>(type: "nvarchar(max)",
nullable: true),
Price = table.Column<decimal>(type: "decimal(18,2)",
nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Movie", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Movie");
}
}
}
В приведенном выше коде:
метод InitialCreate.Up создает таблицу Movie и настраивает Id в качестве
первичного ключа;
метод InitialCreate.Down отменяет изменения схемы, внесенные при
миграции Up .
Внедрение зависимостей в контроллере
Откройте файл Controllers/MoviesController.cs и изучите его конструктор:
C#
public class MoviesController : Controller
{
private readonly MvcMovieContext _context;
public MoviesController(MvcMovieContext context)
{
_context = context;
}
Этот конструктор применяет внедрение зависимостей для внедрения контекста
базы данных ( MvcMovieContext ) в контроллер. Контекст базы данных используется в
каждом методе создания, чтения, обновления и удаления
в контроллере.
Протестируйте страницу создания. Введите и отправьте данные.
Протестируйте страницы редактирования, сведений и удаления.
Строго типизированные модели и директива
@model
Ранее в этом руководстве вы ознакомились с передачей данных или объектов из
контроллера в представление с использованием словаря ViewData . Словарь
ViewData представляет собой динамический объект, который реализует удобный
механизм позднего связывания для передачи информации в представление.
Модель MVC поддерживает передачу строго типизированных объектов модели в
представление. Такой подход обеспечивает проверку кода во время компиляции.
Механизм формирования шаблонов передал строго типизированную модель в
класс MoviesController и представления.
Изучите созданный метод Details в файле Controllers/MoviesController.cs :
C#
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
Параметр id обычно передается в качестве данных маршрута. Например,
https://localhost:5001/movies/details/1 задает:
контроллер к контроллеру movies первый сегмент URL-адреса;
действие для details , второй сегмент URL-адреса;
значение 1 для id , последний сегмент URL-адреса.
Параметр id можно передать с помощью строки запроса, как показано в
следующем примере:
https://localhost:5001/movies/details?id=1
Параметр id определяется как тип, допускающий значение NULL ( int? ), в случае,
если не указано значение id .
Лямбда-выражение передается в метод FirstOrDefaultAsync для выбора записей
фильмов, которые соответствуют данным маршрута или значению строки запроса.
C#
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.Id == id);
Если фильм найден, экземпляр модели Movie передается в представление Details :
C#
return View(movie);
Изучите содержимое файла Views/Movies/Details.cshtml :
CSHTML
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Details";
}
<h1>Details</h1>
<div>
<h4>Movie</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Title)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.ReleaseDate)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.ReleaseDate)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Genre)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Genre)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Price)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>
Оператор @model в начале файла представления задает тип объекта, который будет
ожидаться представлением. При создании контроллера movie был включен
следующий оператор @model :
CSHTML
@model MvcMovie.Models.Movie
Эта директива @model обеспечивает доступ к фильму, который контроллер передал
в представление. Объект Model является строго типизированным. Например, в
представлении Details.cshtml код передает каждое поле фильма во
вспомогательные функции HTML DisplayNameFor и DisplayFor со строго
типизированным объектом Model . Методы Create и Edit и представления также
передают объект модели Movie .
Изучите представление Index.cshtml и метод Index в контроллере Movies. Обратите
внимание на то, как в коде создается объект List при вызове метода View . Код
передает список Movies из метода действия Index в представление:
C#
// GET: Movies
public async Task<IActionResult> Index(string searchString)
{
return _context.Movie != null ?
View(await _context.Movie.ToListAsync()) :
Problem("Entity set 'MvcMovieContext.Movie'
}
is null.");
Код возвращает сведения о проблеме , Movie если свойство контекста данных
имеет значение NULL.
При создании контроллера movies механизм формирования шаблонов включил
следующий оператор @model в начало файла Index.cshtml :
CSHTML
@model IEnumerable<MvcMovie.Models.Movie>
Эта директива @model обеспечивает доступ к списку фильмов, который контроллер
передал в представление с использованием строго типизированного объекта
Model . Например, в представлении Index.cshtml код циклически перебирает
фильмы с использованием оператора foreach для строго типизированного объекта
Model :
CSHTML
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a>
|
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Так как объект Model является строго типизированным (как и объект
IEnumerable<Movie> ), каждый элемент в цикле получает тип Movie . Помимо других
преимуществ, компилятор проверяет типы, используемые в коде.
Дополнительные ресурсы
Entity Framework Core для начинающих
Вспомогательные функции тегов
Глобализация и локализация
Назад: Добавление представления
Далее: Работа с SQL
Часть 5. Работа с базой данных в
приложении MVC ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 12 мин
Авторы: Рик Андерсон
(Rick Anderson) и Джон П. Смит
(Jon P Smith)
Объект MvcMovieContext обрабатывает задачу подключения к базе данных и
сопоставления объектов Movie с записями базы данных. Контекст базы данных
регистрируется с помощью контейнера внедрения зависимостей в файле :
Visual Studio
C#
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));
Система конфигурации ASP.NET Core считывает ключ . Для разработки на
локальном уровне она получает строку подключения из файла
appsettings.json .
JSON
"ConnectionStrings": {
"MvcMovieContext": "Server=
(localdb)\\mssqllocaldb;Database=MvcMovieContext7dc5;Trusted_Connection=True;MultipleActiveResultSets=true"
}
Если приложение развертывается на тестовом или рабочем сервере, можно задать
строку подключения к рабочему SQL Server с помощью переменной среды.
Дополнительные сведения см. в разделе Конфигурация.
Visual Studio
SQL Server Express LocalDB
LocalDB
— это упрощенная версия ядра СУБД SQL Server Express, устанавливаемая
по умолчанию с Visual Studio.
Запускается по требованию с помощью строки подключения.
Используется для разработки программ. Запускается в пользовательском
режиме, поэтому конфигурация не слишком сложная.
По умолчанию создает файлы .mdf в каталоге C:/Users/{user} .
Заполнение базы данных
Создайте класс SeedData в папке SeedData . Замените сгенерированный код
следующим кодом:
C#
using
using
using
using
using
Microsoft.EntityFrameworkCore;
Microsoft.Extensions.DependencyInjection;
MvcMovie.Data;
System;
System.Linq;
namespace MvcMovie.Models;
public static class SeedData
{
public static void Initialize(IServiceProvider serviceProvider)
{
using (var context = new MvcMovieContext(
serviceProvider.GetRequiredService<
DbContextOptions<MvcMovieContext>>()))
{
// Look for any movies.
if (context.Movie.Any())
{
return;
// DB has been seeded
}
context.Movie.AddRange(
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-2-12"),
Genre = "Romantic Comedy",
Price = 7.99M
},
new Movie
{
Title = "Ghostbusters ",
ReleaseDate = DateTime.Parse("1984-3-13"),
Genre = "Comedy",
Price = 8.99M
},
new Movie
{
Title = "Ghostbusters 2",
ReleaseDate = DateTime.Parse("1986-2-23"),
Genre = "Comedy",
Price = 9.99M
},
new Movie
{
Title = "Rio Bravo",
ReleaseDate = DateTime.Parse("1959-4-15"),
Genre = "Western",
Price = 3.99M
}
);
context.SaveChanges();
}
}
}
Если в базе данных есть фильмы, возвращается инициализатор заполнения и
фильмы не добавляются.
C#
if (context.Movie.Any())
{
return; // DB has been seeded.
}
Добавление инициализатора заполнения
Visual Studio
Замените все содержимое Program.cs следующим кодом: Новый код выделен.
C#
using
using
using
using
Microsoft.EntityFrameworkCore;
Microsoft.Extensions.DependencyInjection;
MvcMovie.Data;
MvcMovie.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<MvcMovieContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("MvcMovie
Context")));
// Add services to the container.
builder.Services.AddControllersWithViews();
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
SeedData.Initialize(services);
}
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this
for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Удалите все записи в базе данных. Это можно сделать с помощью ссылок
удаления в браузере или из SSOX.
Тестирование приложения. Приложение должно выполнить инициализацию,
вызывая код в файле Program.cs , чтобы запустить метод заполнения. Чтобы
выполнить инициализацию принудительно, закройте окно командной строки,
открытое Visual Studio, и перезапустите, нажав клавиши CTRL+F5.
В приложении будут отображены данные.
Предыдущая статья — "Добавление модели"
Следующая статья — "Добавление методов и представлений контроллера"
Часть 6. Методы и представления
контроллера в ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 23 мин
Автор: Рик Андерсон
(Rick Anderson)
Все готово для приложения по работе с фильмами, но презентация далеко не
идеальна, например, элемент ReleaseDate должен состоять из двух слов.
Откройте файл Models/Movie.cs и добавьте указанные ниже выделенные строки:
C#
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models;
public class Movie
{
public int Id { get; set; }
public string? Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
}
DataAnnotations описаны в следующем учебнике. Атрибут Display определяет
отображаемое имя поля (в этом случае "Release Date" вместо "ReleaseDate").
Атрибут DataType определяет тип данных (Date), поэтому сведения о времени,
хранящиеся в поле, не отображаются.
Требуются заметки к данным [Column(TypeName = "decimal(18, 2)")] , чтобы Entity
Framework Core корректно сопоставила Price с валютой в базе данных.
Дополнительные сведения см. в разделе Типы данных.
Перейдите к контроллеру Movies . Наведите указатель мыши на ссылку Edit
(Изменить) и удерживайте его на месте, чтобы просмотреть целевой URL-адрес.
Ссылки Edit (Изменить), Details (Сведения) и Delete (Удалить) создаются
вспомогательной функцией тегов привязки Core MVC в файле
Views/Movies/Index.cshtml .
CSHTML
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
Вспомогательные функции тегов позволяют серверному коду участвовать в
создании и отображении HTML-элементов в файлах Razor. В представленном выше
коде AnchorTagHelper динамически создает значение атрибута HTML href на
основании метода действия контроллера и идентификатора маршрута. Для
изучения созданной разметки используйте функцию просмотра исходного кода в
вашем любимом браузере или средства для разработчика. Ниже показана часть
созданного кода HTML:
HTML
<td>
<a href="/Movies/Edit/4"> Edit </a> |
<a href="/Movies/Details/4"> Details </a> |
<a href="/Movies/Delete/4"> Delete </a>
</td>
Формат для routing задан в файле Program.cs :
C#
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
ASP.NET Core преобразует https://localhost:5001/Movies/Edit/4 в запрос метода
действия Edit контроллера Movies с параметром Id , равным 4. (Методы
контроллера также называются методами действия.)
Вспомогательные функции тегов являются одной из самых популярных новых
возможностей в ASP.NET Core. Подробнее см. в разделе Дополнительные ресурсы.
Откройте контроллер Movies и изучите два метода действия Edit . В следующем
коде демонстрируется метод HTTP GET Edit , который выполняет выборку фильмов
и заполняет форму редактирования, созданную файлом Edit.cshtml Razor.
C#
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
В следующем коде показан метод HTTP POST Edit , который является владельцем
переданных значений фильмов:
C#
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you
want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id,
[Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
Атрибут [Bind] является одним из способов защиты от чрезмерной передачи
данных. Свойства необходимо включать только в тот атрибут [Bind] , который вы
хотите изменить. Дополнительные сведения см. в разделе Защита контроллера от
чрезмерной передачи данных. ViewModels
реализует альтернативный подход к
защите от чрезмерной передачи данных.
Обратите внимание на второй метод действия Edit , которому предшествует
атрибут [HttpPost] .
C#
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you
want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id,
[Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
Атрибут HttpPost указывает на то, что этот метод Edit может вызываться только
для запросов POST . Вы могли бы применить атрибут [HttpGet] к первому методу
редактирования, однако это необязательно, поскольку значение [HttpGet] задается
по умолчанию.
Атрибут ValidateAntiForgeryToken используется для защиты от подделки запросов и
используется совместно с соответствующим маркером безопасности, который
создается в файле представления редактирования ( Views/Movies/Edit.cshtml ). В
файле представления редактирования для создания маркера защиты от подделки
используется вспомогательная функция тега Form.
CSHTML
<form asp-action="Edit">
Вспомогательная функция тега Form создает скрытый маркер защиты от подделки,
который должен соответствовать [ValidateAntiForgeryToken] аналогичному
маркеру безопасности в методе Edit контроллера Movies. Дополнительные
сведения см. на странице Предотвращение атак с использованием подделки
межсайтовых запросов (XSRF/CSRF) в ASP.NET Core.
Метод HttpGet Edit принимает параметр фильма ID , выполняет поиск фильма с
использованием метода FindAsync платформы Entity Framework и возвращает
выбранный фильм в представление редактирования. Если фильм найти не удается,
возвращается ошибка NotFound (HTTP 404).
C#
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.FindAsync(id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
Если в представлении редактирования создана система формирования шаблонов,
она проверяет класс Movie и создает код для отображения элементов <label> и
<input> для каждого свойства класса. В следующем примере показано
представление редактирования, созданное системой формирования шаблонов
Visual Studio:
CSHTML
@model MvcMovie.Models.Movie
@{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Обратите внимание, что в начале файла шаблона представления содержится
оператор @model MvcMovie.Models.Movie . @model MvcMovie.Models.Movie указывает, что
в представлении требуется модель представления шаблона с типом Movie .
Для оптимизации разметки HTML сформированный код использует несколько
методов вспомогательных функций тегов. Вспомогательная функция тега Label
отображает имя поля ("Title", "ReleaseDate", "Genre" или "Price"). Вспомогательная
функция тега Input отображает элемент HTML <input> . Вспомогательная функция
тега Validation отображает любые сообщения проверки, связанные с указанным
свойством.
Запустите приложение и перейдите по URL-адресу /Movies . Щелкните ссылку Edit
(Изменить). Просмотрите исходный код страницы в окне браузера. Созданный
HTML-код для элемента <form> показан ниже.
HTML
<form action="/Movies/Edit/7" method="post">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div class="text-danger" />
<input type="hidden" data-val="true" data-val-required="The ID field
is required." id="ID" name="ID" value="7" />
<div class="form-group">
<label class="control-label col-md-2" for="Genre" />
<div class="col-md-10">
<input class="form-control" type="text" id="Genre"
name="Genre" value="Western" />
<span class="text-danger field-validation-valid" datavalmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="Price" />
<div class="col-md-10">
<input class="form-control" type="text" data-val="true"
data-val-number="The field Price must be a number." data-val-required="The
Price field is required." id="Price" name="Price" value="3.99" />
<span class="text-danger field-validation-valid" datavalmsg-for="Price" data-valmsg-replace="true"></span>
</div>
</div>
<!-- Markup removed for brevity -->
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
<input name="__RequestVerificationToken" type="hidden"
value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmq
UyXnJBXhmrjcUVDJyDUMm7MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>
Элементы <input> находятся в элементе HTML <form> , атрибут action которого
задает передачу данных по URL-адресу /Movies/Edit/id . Данные формы будут
передаваться на сервер при нажатии кнопки Save . В последней строке перед
закрывающим элементом </form> отображается скрытый маркер XSRF, созданный
вспомогательной функцией тега Form.
Обработка запроса POST
В следующем листинге демонстрируется версия [HttpPost] метода действия Edit .
C#
// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you
want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id,
[Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (id != movie.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(movie);
}
Атрибут [ValidateAntiForgeryToken] проверяет скрытый маркер безопасности XSRF,
который был создан генератором маркеров во вспомогательной функции тега
Form
Система модели привязки принимает переданные значения формы и создает
объект Movie , который передается в качестве параметра movie . Свойство
ModelState.IsValid проверяет, можно ли использовать переданные в форме
данные для изменения (редактирования или обновления) объекта Movie .
Допустимые данные сохраняются. Обновленные (измененные) данные фильма
сохраняются в базе данных посредством вызова метода SaveChangesAsync в
контексте базы данных. После сохранения данных код перенаправляет
пользователя в метод действия Index класса MoviesController , который отображает
коллекцию фильмов с учетом только что внесенных изменений.
Перед отправкой формы на сервер на стороне клиента проверяется выполнение
всех правил проверки для полей. При обнаружении ошибок проверки
отображается сообщение об ошибке, а форма не передается. Если JavaScript
отключен, проверка на стороне клиента не выполняется. Тем не менее, сервер
обнаружит переданные недопустимые значения, в результате чего значения
формы будут отображены повторно с сообщениями об ошибках. Далее в этом
руководстве мы более подробно изучим проверку модели. Вспомогательная
функция тега Validation в шаблоне представления Views/Movies/Edit.cshtml
обеспечивает отображение соответствующих сообщений об ошибке.
Все методы HttpGet в контроллере Movie имеют схожий шаблон. Они получают
объект фильма (или список объектов для метода Index ) и передают объект
(модель) в представление. Метод Create передает в представление пустой объект
фильма Create . Все методы, которые создают, редактируют, удаляют или иным
образом изменяют данные, делают это в перегрузке метода [HttpPost] . Изменение
данных в методе HTTP GET сопряжено с угрозой безопасности. Изменение данных в
методе HTTP GET также не соответствует рекомендациям по использованию
протокола HTTP и модели архитектуры REST , в которых указывается, что запросы
GET не должны изменять состояние приложения. Другими словами, операция GET
должна выполняться безопасным способом, то есть не иметь побочных эффектов и
не изменять существующие данные.
Дополнительные ресурсы
Глобализация и локализация
Общие сведения о вспомогательных функциях тегов
Создание вспомогательных функций тегов
Предотвращение атак с межсайтовой подделкой запросов (XSRF/CSRF) в
ASP.NET Core
Защита контроллера от чрезмерной передачи данных
ViewModels
Вспомогательная функция тега Form
Вспомогательная функция тега Input
Вспомогательная функция тега Label
Вспомогательная функция тега Select
Вспомогательная функция тега Validation
Назад
Вперед
Часть 7. Добавление поиска в
приложение MVC ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 19 мин
Автор: Рик Андерсон
(Rick Anderson)
В этом разделе вы добавите в метод действия Index возможности поиска, которые
позволяют выполнять поиск фильмов по жанру или названию.
Обновите метод Index , находящийся в файле Controllers/MoviesController.cs ,
добавив следующий код:
C#
public async Task<IActionResult> Index(string searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
return View(await movies.ToListAsync());
}
Следующая строка в методе Index action создает запрос LINQ для выбора фильмов:
C#
var movies = from m in _context.Movie
select m;
Этот запрос только определяется в этой точке и не выполняется для базы данных.
Если параметр searchString содержит строку, запрос фильмов изменяется для
фильтрации по значению в строке поиска:
C#
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
Приведенный выше код s => s.Title!.Contains(searchString) представляет собой
лямбда-выражение. Лямбда-выражения используются в запросах LINQ на основе
методов в качестве аргументов стандартных методов операторов запроса, таких
как метод Where или Contains (используется в приведенном выше коде). Запросы
LINQ не выполняются, если они определяются или изменяются путем вызова
метода, например Where , Contains или OrderBy . Вместо этого выполнение запроса
откладывается. Это означает, что вычисление выражения откладывается до тех пор,
пока не будет выполнена итерация его реализованного значения или пока не
будет вызван метод ToListAsync . Дополнительные сведения об отложенном и
немедленном выполнении запросов см. в разделе Выполнение запроса.
Примечание. Метод Contains выполняется в базе данных, а не в коде C#,
приведенном выше. Регистр символов запроса учитывается в зависимости от
параметров базы данных и сортировки. В SQL Server метод Contains
сопоставляется с SQL LIKE, в котором регистр символов не учитывается. В SQLite
при параметрах сортировки по умолчанию регистр символов учитывается.
Перейдите к /Movies/Index . Добавьте в URL-адрес строку запроса, например ?
searchString=Ghost . Отображаются отфильтрованные фильмы.
Если вы изменили сигнатуру метода Index и включили в нее параметр с именем
id , параметр id будет соответствовать необязательному заполнителю {id} для
маршрутов по умолчанию, который задан в файле Program.cs .
C#
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Измените параметр на id , а все вхождения searchString — на id .
Предыдущий метод Index :
C#
public async Task<IActionResult> Index(string searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
return View(await movies.ToListAsync());
}
Обновленный метод Index с параметром id :
C#
public async Task<IActionResult> Index(string id)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie'
}
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(id))
{
is null.");
movies = movies.Where(s => s.Title!.Contains(id));
}
return View(await movies.ToListAsync());
}
Теперь можно передать заголовок поиска в качестве данных маршрута (сегмент
URL-адреса) вместо значения строки запроса.
Тем не менее пользователи вряд ли будут каждый раз изменять URL-адрес для
поиска фильмов. Итак, теперь вам необходимо добавить элементы
пользовательского интерфейса для удобства фильтрации фильмов. Если вы
изменили сигнатуру метода Index для тестирования передачи параметра ID с
привязкой к маршруту, измените ее снова, чтобы она снова принимала параметр
searchString :
C#
public async Task<IActionResult> Index(string searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
var movies = from m in _context.Movie
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
return View(await movies.ToListAsync());
}
Откройте файл Views/Movies/Index.cshtml и добавьте разметку <form> , которая
выделена ниже:
CSHTML
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index">
<p>
Title: <input type="text" name="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>
Тег HTML <form> использует вспомогательную функцию тега Form, чтобы при
отправке формы строка фильтра передавалась в действие Index контроллера
movies. Сохраните изменения и протестируйте фильтр.
Вопреки ожиданиям, перегрузка [HttpPost] для метода Index отсутствует. Она не
нужна, поскольку метод не изменяет состояние приложения и просто выполняет
фильтрацию данных.
Можно добавить следующий метод [HttpPost] Index .
C#
[HttpPost]
public string Index(string searchString, bool notUsed)
{
return "From [HttpPost]Index: filter on " + searchString;
}
Параметр notUsed используется для создания перегрузки метода Index . Это мы
обсудим далее в этом учебнике.
При добавлении этого метода вызывающий метод действия будет сопоставлять
метод [HttpPost] Index , а метод [HttpPost] Index будет выполняться, как показано
на рисунке ниже.
Тем не менее при добавлении этой версии [HttpPost] метода Index существует
ограничение на общую реализацию. Допустим, вам необходимо добавить в
закладки конкретный поиск или отправить друзьям ссылку, по которой они могут
просмотреть аналогичный отфильтрованный список фильмов. Обратите внимание,
что URL-адрес запроса HTTP POST совпадает с URL-адресом запроса GET (localhost:
{ПОРТ}/Movies/Index) — в URL-адресе отсутствуют сведения о поиске. Данные
строки поиска отправляются на сервер в виде значения поля формы . Вы можете
проверить это с помощью средств разработчика для браузера или инструмента
Fiddler . На рисунке ниже показаны средства разработчика для браузера Chrome:
В теле запроса отображается параметр поиска и маркер XSRF. Обратите внимание,
что, как описывается в предыдущем руководстве, вспомогательная функция тега
Form создает маркер защиты от подделки XSRF. Поскольку мы не изменяем
данные, проверять маркер безопасности в методе контроллера не нужно.
Так как параметр поиска находится в теле запроса, а не в URL-адресе, эти сведения
о поиске нельзя добавить в закладки или открыть для общего доступа. Для
устранения этой проблемы укажите, что запрос типа HTTP GET должен находиться в
файле Views/Movies/Index.cshtml .
CSHTML
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>
Title: <input type="text" name="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
После отправки поиска URL-адрес содержит строку поискового запроса. Поиск
также переносится в метод HttpGet Index , даже если у вас определен метод
HttpPost Index .
В следующем примере разметки показано изменение тега form :
CSHTML
<form asp-controller="Movies" asp-action="Index" method="get">
Добавление поиска по жанру
Добавьте следующий класс MovieGenreViewModel в папку Models:
C#
using Microsoft.AspNetCore.Mvc.Rendering;
using System.Collections.Generic;
namespace MvcMovie.Models;
public class MovieGenreViewModel
{
public List<Movie>? Movies { get; set; }
public SelectList? Genres { get; set; }
public string? MovieGenre { get; set; }
public string? SearchString { get; set; }
}
Модель представления фильмов по жанру будет содержать:
Список фильмов.
Объект SelectList со списком жанров. В этом списке пользователь может
выбрать жанр фильма.
Объект MovieGenre , содержащий выбранный жанр.
SearchString , содержащий текст, который пользователи вводят в поле поиска.
Замените метод Index в файле MoviesController.cs следующим кодом:
C#
// GET: Movies
public async Task<IActionResult> Index(string movieGenre, string
searchString)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie' is null.");
}
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
var movies = from m in _context.Movie
select m;
if (!string.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title!.Contains(searchString));
}
if (!string.IsNullOrEmpty(movieGenre))
{
movies = movies.Where(x => x.Genre == movieGenre);
}
var movieGenreVM = new MovieGenreViewModel
{
Genres = new SelectList(await genreQuery.Distinct().ToListAsync()),
Movies = await movies.ToListAsync()
};
return View(movieGenreVM);
}
Следующий код определяет запрос LINQ , который извлекает все жанры из базы
данных.
C#
// Use LINQ to get list of genres.
IQueryable<string> genreQuery = from m in _context.Movie
orderby m.Genre
select m.Genre;
Объект SelectList со списком жанров создается путем проецирования отдельных
жанров (это необходимо, чтобы исключить повторяющиеся жанры).
Когда пользователь выполняет поиск элемента, значение поиска сохраняется в
поле поиска.
Добавление поиска по жанру в
представление индекса
Обновите файл Index.cshtml , находящийся в папке Views/Movies/ , следующим
образом:
CSHTML
@model MvcMovie.Models.MovieGenreViewModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<form asp-controller="Movies" asp-action="Index" method="get">
<p>
<select asp-for="MovieGenre" asp-items="Model.Genres">
<option value="">All</option>
</select>
Title: <input type="text" asp-for="SearchString" />
<input type="submit" value="Filter" />
</p>
</form>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Title)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].ReleaseDate)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Genre)
</th>
<th>
@Html.DisplayNameFor(model => model.Movies![0].Price)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Movies!)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-routeid="@item.Id">Details</a> |
<a asp-action="Delete" asp-routeid="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Проверьте лямбда-выражение, которое используется в следующем
вспомогательном методе HTML:
@Html.DisplayNameFor(model => model.Movies![0].Title)
В предыдущем коде вспомогательный метод HTML DisplayNameFor проверяет
свойство Title , указанное в лямбда-выражении, и определяет отображаемое имя.
Поскольку лямбда-выражение проверяется, а не вычисляется, в том случае, если
model , model.Movies или model.Movies[0] имеют значение null или пусты, не
происходит нарушение прав доступа. При вычислении лямбда-выражения
(например, @Html.DisplayFor(modelItem => item.Title) ) вычисляются значения для
свойств модели. После ! model.Movies является оператором, допускающим
значение NULL, который используется для объявления , который Movies не имеет
значения NULL.
Проверьте работу приложения, выполнив поиск по жанру, по названию фильма и
по обоим этим параметрам:
Назад
Вперед
Часть 8. Добавление нового поля в
приложение MVC ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 17 мин
Автор: Рик Андерсон
(Rick Anderson)
В этом разделе Entity Framework Code First Migrations используется для выполнения
следующих задач:
Добавление нового поля в модель.
Перенос нового поля в базу данных.
Если вы используете EF Code First для автоматического создания базы данных, Code
First:
Добавляет таблицу в базу данных для отслеживания схемы базы данных.
Проверяет, синхронизирована ли база данных с классами модели, из которых
она была создана. Если синхронизация нарушена, EF вызывает исключение.
Это позволяет упростить поиск проблем с согласованностью между базой
данных и кодом.
Добавление свойства Rating в модель Movie
Добавьте свойство Rating в Models/Movie.cs :
C#
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models;
public class Movie
{
public int Id { get; set; }
public string? Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string? Genre { get; set; }
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
public string? Rating {
get; set; }
}
Сборка приложения
Visual Studio
Ctrl+Shift+B
Поскольку в класс Movie было добавлено новое поле, необходимо обновить
список привязки свойств, включив в него новое свойство. В файле
MoviesController.cs обновите атрибут [Bind] для методов действия Create и Edit ,
включив свойство Rating :
C#
[Bind("Id,Title,ReleaseDate,Genre,Price,Rating")]
Обновите шаблоны представлений, чтобы реализовать отображение, создание и
редактирование нового свойства Rating в представлении браузера.
Измените файл /Views/Movies/Index.cshtml и добавьте поле Rating :
CSHTML
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model
</th>
<th>
@Html.DisplayNameFor(model
</th>
<th>
@Html.DisplayNameFor(model
</th>
<th>
@Html.DisplayNameFor(model
</th>
<th>
@Html.DisplayNameFor(model
</th>
<th></th>
</tr>
</thead>
<tbody>
=> model.Movies[0].Title)
=> model.Movies[0].ReleaseDate)
=> model.Movies[0].Genre)
=> model.Movies[0].Price)
=> model.Movies[0].Rating)
@foreach (var item in Model.Movies)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Rating)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-routeid="@item.Id">Details</a> |
<a asp-action="Delete" asp-routeid="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Обновите /Views/Movies/Create.cshtml , добавив поле Rating .
Visual Studio / Visual Studio для Mac
Вы можете скопировать и вставить предыдущую "группу форм" и дождаться
автоматического обновления полей с помощью IntelliSense. IntelliSense
работает со вспомогательными функциями тегов.
Обновите остальные шаблоны.
Обновите класс SeedData так, чтобы он предоставлял значение нового столбца.
Ниже показан пример изменения, которое необходимо выполнить для каждого
new Movie .
C#
new Movie
{
Title = "When Harry Met Sally",
ReleaseDate = DateTime.Parse("1989-1-11"),
Genre = "Romantic Comedy",
Rating = "R",
Price = 7.99M
},
Для работы приложения необходимо обновить базу данных, включив в нее новое
поле. Если он запущена, возникает следующее исключение SqlException :
SqlException: Invalid column name 'Rating'.
Эта ошибка связана с тем, что обновленный класс модели Movie отличается от
схемы таблицы Movie в существующей базе данных. (В таблице базы данных
отсутствует столбец Rating .)
Устранить эту ошибку можно несколькими способами:
1. Можно с помощью Entity Framework автоматически удалить и повторно
создать базу данных на основе новой схемы класса модели. Этот подход
удобен на ранних стадиях цикла разработки, когда все действия
осуществляются с тестовой базой данных. В этом случае развитие модели и
схемы базы данных осуществляется одновременно. Недостатком такого
подхода является потеря существующих данных в базе, в связи с чем
использовать его в рабочей базе данных невозможно. При разработке
приложения часто используется инициализатор для автоматического
заполнения базы тестовыми данными. Это хороший подход на ранних этапах
разработки и при использовании SQLite.
2. Можно явно изменить схему существующей базы данных в соответствии с
новыми классами модели. Преимущество такого подхода состоит в том, что
сохраняются все данные. Это изменение можно выполнить как вручную, так и
с помощью соответствующего скрипта базы данных.
3. Можно обновить схему базы данных с помощью Code First Migrations.
В этом руководстве используется Code First Migrations.
Visual Studio
В меню Сервис последовательно выберите пункты Диспетчер пакетов NuGet
> Консоль диспетчера пакетов.
В PMC введите следующие команды:
PowerShell
Add-Migration Rating
Update-Database
Команда Add-Migration указывает платформе миграции на необходимость
проверить текущую модель Movie с текущей схемой базы данных Movie и
создать нужный код для переноса базы данных в новую модель.
В качестве имени файла переноса используется произвольное имя "Rating".
Рекомендуется присваивать этому файлу понятное имя.
Если удалить все записи из базы данных, при инициализации она будет
заполнена значениями и в нее будет включено поле Rating .
Запустите приложение и проверьте возможность создания, редактирования и
отображения фильмов с использованием поля Rating .
Назад
Вперед
Часть 9. Добавление проверки в
приложение MVC ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 25 мин
Автор: Рик Андерсон
(Rick Anderson)
Содержание
К модели Movie добавляется логика проверки.
Убедитесь, что правила проверки применяются каждый раз, когда
пользователь создает или редактирует фильм.
Соблюдение принципа "Не повторяйся"
Принцип DRY
(от английского "Don't Repeat Yourself" — не повторяйся) является
одним из основополагающих принципов разработки в модели MVC. В модели
ASP.NET Core MVC рекомендуется задавать функциональные возможности или
поведение только один раз, а затем отражать их в других местах в приложении. Это
позволяет свести к минимуму объем кода, а также снизить риск возникновения в
нем ошибок и упростить его тестирование и поддержку.
Ярким примером применения принципа "Не повторяйся" является поддержка
проверки, реализуемая в модели MVC и на платформе Entity Framework Core Code
First. Правила проверки декларативно определяются в одном месте (в классе
модели) и затем применяются в рамках всего приложения.
Добавление правил проверки к модели
фильма
Пространство имен DataAnnotations предоставляет набор встроенных атрибутов
проверки, которые декларативно применяются к классу или свойству. Кроме того,
DataAnnotations содержит атрибуты форматирования (такие как DataType ), которые
обеспечивают форматирование и не предназначены для проверки.
Обновите класс Movie , чтобы использовать преимущества встроенных атрибутов
проверки Required , StringLength , RegularExpression и Range .
C#
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models;
public class Movie
{
public int Id { get; set; }
[StringLength(60, MinimumLength = 3)]
[Required]
public string? Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$")]
[Required]
[StringLength(30)]
public string? Genre { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
[StringLength(5)]
[Required]
public string? Rating { get; set; }
}
Атрибуты проверки определяют поведение для свойств модели, к которым они
применяются:
Атрибуты Required и MinimumLength указывают, что свойство должно иметь
значение. Тем не менее, чтобы удовлетворить требованиям проверки,
пользователю достаточно ввести пробел.
Атрибут RegularExpression ограничивает набор допустимых для ввода
символов. В приведенном выше коде в Genre:
должны использоваться только буквы;
первая буква должна быть прописной; Пробелы разрешены, а цифры и
специальные символы — нет.
В RegularExpression Rating:
первый символ должен быть прописной буквой;
допускаются специальные символы и цифры, а также последующие
пробелы. Значение "PG-13" допустимо для рейтинга, но недопустимо для
жанра.
Атрибут Range ограничивает диапазон значений.
Атрибут StringLength позволяет задать максимальную и при необходимости
минимальную длину строкового свойства.
Типы значений (например, decimal , int , float , DateTime ) по своей природе
являются обязательными и не требуют атрибута [Required] .
Наличие правил проверки, которые автоматически применяются ASP.NET Core,
помогает повысить степень надежности приложения. Это также гарантирует, что в
любом случае будут выполнены все проверки и в базе данных не будут случайно
оставлены поврежденные данные.
Интерфейс ошибки при проверке
Запустите приложение и перейдите к контроллеру фильмов.
Щелкните ссылку Создать, чтобы добавить новый фильм. Введите в форму какиелибо недопустимые значения. Если функция проверки jQuery на стороне клиента
обнаруживает ошибку, сведения о ней отображаются в соответствующем
сообщении.
7 Примечание
Возможно, вы не сможете вводить десятичные запятые в полях для
десятичных чисел. Чтобы обеспечить поддержку проверки jQuery
для
других языков, кроме английского, используйте вместо десятичной точки
запятую (","), а для отображения данных в форматах для других языков, кроме
английского, выполните действия, необходимые для глобализации вашего
приложения. Инструкции по добавлению десятичной запятой см. в этом
комментарии GitHub 4076 .
Обратите внимание, что для каждого поля, содержащего недопустимое значение, в
форме автоматически отображается соответствующее сообщение об ошибке
проверки. Эти ошибки применяются как на стороне клиента (с помощью JavaScript
и jQuery), так и на стороне сервера (если пользователь отключает JavaScript).
Серьезное преимущество заключается в том, что для реализации этого
пользовательского интерфейса проверки не требуется изменять код в классе
MoviesController или представлении Create.cshtml . В контроллере и
представлениях, создаваемых в рамках этого руководства, автоматически
применяются правила проверки, для определения которых к свойствам класса
модели Movie были применены атрибуты. При проверке с использованием метода
действия Edit применяются те же правила.
Данные формы передаются на сервер только после того, как будут устранены все
ошибки проверки на стороне клиента. Чтобы проверить это, установите точку
останова в методе HTTP Post с помощью инструмента Fiddler
или средств
разработчика F12.
Принципы работы проверки
Вам может быть интересно, как пользовательский интерфейс проверки создается
без обновления кода контроллера или представлений. В следующем примере кода
показаны два метода Create .
C#
// GET: Movies/Create
public IActionResult Create()
{
return View();
}
// POST: Movies/Create
// To protect from overposting attacks, enable the specific properties you
want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult>
Create([Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
if (ModelState.IsValid)
{
_context.Add(movie);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(movie);
}
Первый метод действия Create (HTTP GET) отображает исходную форму создания.
Вторая версия ( [HttpPost] ) обрабатывает передачу формы. Второй метод Create
(версия [HttpPost] ) вызывает ModelState.IsValid , который определяет наличие
ошибок проверки в фильме. При вызове этого метода оцениваются все атрибуты
проверки, которые были применены к объекту. При наличии ошибок проверки в
объекте метод Create повторно отображает форму. Если ошибок нет, метод
сохраняет новый фильм в базе данных. В этом примере форма передается на
сервер только после того, как будут устранены все ошибки проверки,
обнаруженные на стороне клиента. Второй метод Create не вызывается до тех пор,
пока на стороне клиента присутствуют ошибки проверки. При отключении
JavaScript в браузере также отключается проверка на стороне клиента. В этом
случае вы можете протестировать метод HTTP POST Create ModelState.IsValid ,
который обнаруживает наличие ошибок проверки.
Вы можете установить точку останова в метод [HttpPost] Create и убедиться, что
он не вызывается и данные формы не передаются, если на стороне клиента
присутствуют ошибки проверки. Если отключить JavaScript в браузере и отправить
форму с ошибками, будет достигнута точка останова. Без JavaScript вы попрежнему будете получать полную проверку.
На следующем рисунке показано, как отключить JavaScript в браузере Firefox.
На следующем рисунке показано, как отключить JavaScript в браузере Chrome.
После отключения JavaScript передайте недопустимые данные и запустите отладку
в пошаговом режиме.
Часть шаблона представления Create.cshtml показана в следующей разметке:
HTML
<h4>Movie</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
@*Markup removed for brevity.*@
Приведенная выше разметка используется методами действия для отображения
исходной формы и повторного вывода формы в случае ошибки.
Вспомогательная функция тега Input использует атрибуты DataAnnotations и
создает HTML-атрибуты, необходимые для проверки jQuery на стороне клиента.
Вспомогательная функция тега Validation отображает ошибки проверки.
Дополнительные сведения см. в разделе Проверка.
Этот подход удобен тем, что ни контроллер, ни шаблон представления Create
ничего не знают о фактически применяемых правилах проверки или
отображаемых сообщениях об ошибках. Правила проверки и строки ошибок
указываются только в классе Movie . Такие же правила проверки автоматически
применяются к представлению Edit и любым другим представлениям модели,
которые вы можете создавать или редактировать.
При необходимости вы можете изменить логику проверки в одном месте, добавив
атрибуты проверки в модель (в этом примере — в класс Movie ). Вам не придется
беспокоиться о несогласованности применения правил в различных частях
приложения, поскольку вся логика проверки будет определена в одном месте и
начнет применяться по всему приложению. Это позволяет максимально
оптимизировать код и обеспечить удобство его совершенствования и поддержки.
Кроме того, таким образом вы будете полностью соблюдать требования принципа
"Не повторяйся".
Использование атрибутов DataType
Откройте файл Movie.cs и проверьте класс Movie . В пространстве имен
System.ComponentModel.DataAnnotations в дополнение к набору встроенных
атрибутов проверки предоставляются атрибуты форматирования. К полям с датой
выпуска и ценой уже применено значение перечисления DataType . В следующем
коде показаны свойства ReleaseDate и Price с соответствующим атрибутом
DataType .
C#
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
Атрибуты DataType предоставляют модулю просмотра только рекомендации по
форматированию данных, а также другие элементы и атрибуты, например, <a> для
URL-адресов и <a href="mailto:EmailAddress.com"> для электронной почты.
Используйте атрибут RegularExpression для проверки формата данных. Атрибут
DataType позволяет указать тип данных с более точным определением по
сравнению со встроенным типом базы данных, но не предназначен для проверки.
В этом случае требуется отслеживать только дату, но не время. В перечислении
DataType представлено множество типов данных, таких как Date, Time,
PhoneNumber, Currency, EmailAddress и другие. Атрибут DataType также
обеспечивает автоматическое предоставление функций для определенных типов в
приложении. Например, может быть создана ссылка mailto: для
DataType.EmailAddress . Также в браузерах с поддержкой HTML5 может быть
предоставлен селектор даты для DataType.Date . Атрибуты DataType создают
атрибуты HTML 5 data- , которые используются браузерами с поддержкой HTML 5.
Атрибуты DataType не предназначены для проверки.
DataType.Date не задает формат отображаемой даты. По умолчанию поле данных
отображается с использованием форматов, установленных в параметрах
CultureInfo сервера.
С помощью атрибута DisplayFormat можно явно указать формат даты:
C#
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode =
true)]
public DateTime ReleaseDate { get; set; }
Параметр ApplyFormatInEditMode указывает, что формат также должен применяться
при отображении значения в текстовом поле для редактирования. (В некоторых
случаях такое поведение нежелательно. Например, в текстовом поле для
редактирования денежных значений обычно не требуется отображать
обозначение денежной единицы.)
Атрибут DisplayFormat может использоваться отдельно, однако чаще всего его
рекомендуется применять вместе с атрибутом DataType . Атрибут DataType передает
семантику данных (в отличие от способа их вывода на экран) и дает следующие
преимущества по сравнению с атрибутом DisplayFormat:
Поддержка функций HTML5 в браузере (отображение элемента управления
календарем, соответствующего языковому стандарту символа валюты, ссылок
электронной почты и т. д.)
По умолчанию формат отображения данных в браузере определяется в
соответствии с установленным языковым стандартом.
С помощью атрибута DataType модель MVC может выбрать подходящий
шаблон поля для отображения данных ( DisplayFormat при отдельном
использовании базируется на строковом шаблоне).
7 Примечание
Проверка jQuery не работает с атрибутом Range и DateTime . Например,
следующий код всегда приводит к возникновению ошибки проверки на
стороне клиента, даже если дата попадает в указанный диапазон:
[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]
Чтобы использовать атрибут Range с DateTime , необходимо отключить проверку
дат jQuery. Как правило, не рекомендуется компилировать модели с
фиксированными датами, поэтому использовать атрибуты Range и DateTime следует
крайне осторожно.
В следующем коде демонстрируется объединение атрибутов в одной строке:
C#
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MvcMovie.Models;
public class Movie
{
public int Id { get; set; }
[StringLength(60, MinimumLength = 3)]
public string Title { get; set; }
[Display(Name = "Release Date"), DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z\s]*$"), Required, StringLength(30)]
public string Genre { get; set; }
[Range(1, 100), DataType(DataType.Currency)]
[Column(TypeName = "decimal(18, 2)")]
public decimal Price { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$"), StringLength(5)]
public string Rating { get; set; }
}
В следующей части этой серии мы рассмотрим приложение и внесем ряд
изменений в автоматически создаваемые методы Details и Delete .
Дополнительные ресурсы
Работа с формами
Глобализация и локализация
Общие сведения о вспомогательных функциях тегов
Создание вспомогательных функций тегов
Назад
Вперед
Часть 10. Изучение методов Details и
Delete в приложении ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 7 мин
Автор: Рик Андерсон
(Rick Anderson)
Откройте контроллер Movie и изучите метод Details :
C#
// GET: Movies/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
Подсистема формирования шаблонов MVC, созданная этим методом действия,
добавляет комментарий, показывающий HTTP-запрос, который вызывает метод.
Здесь это запрос GET, состоящий из трех сегментов URL-адреса, контроллера
Movies , метода Details и значения id . Отзыв этих сегментов определяется в
Program.cs .
C#
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
EF упрощает поиск данных с помощью метода FirstOrDefaultAsync . Важной
функцией обеспечения безопасности, встроенной в метод, является то, что код
проверяет, что метод поиска обнаружил фильм до выполнения с ним каких-либо
действий. Например, злоумышленник может внести ошибки на сайт путем
изменения созданного ссылками URL-адреса с http://localhost:
{PORT}/Movies/Details/1 на что-то вроде http://localhost:
{PORT}/Movies/Details/12345 (или любое другое значение, которое не представляет
фактический фильм). Если вы не проверили наличие фильма со значением NULL,
приложение выдаст исключение.
Просмотрите методы Delete и DeleteConfirmed .
C#
// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie
.FirstOrDefaultAsync(m => m.Id == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
if (_context.Movie == null)
{
return Problem("Entity set 'MvcMovieContext.Movie'
}
is null.");
var movie = await _context.Movie.FindAsync(id);
_context.Movie.Remove(movie);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
Обратите внимание, что метод HTTP GET Delete не удаляет указанный фильм, он
возвращает представление фильма, где можно выполнить (HttpPost) удаление.
Выполнение операции удаления в ответ на запрос GET (или выполнение операции
редактирования, создания или любой другой операции, изменяющей данные)
открывает брешь в системе безопасности.
Метод [HttpPost] , который удаляет данные, называется DeleteConfirmed , поэтому
метод HTTP POST обладает уникальной сигнатурой или именем. Ниже приведены
сигнатуры двух методов:
C#
// GET: Movies/Delete/5
public async Task<IActionResult> Delete(int? id)
{
C#
// POST: Movies/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
Требуется, чтобы в среде CLR перегруженные методы имели уникальную сигнатуру
параметров (то же имя метода, но другой список параметров). Однако здесь
необходимы два метода Delete — один для GET и один для POST — с одинаковой
сигнатурой параметров. (Они оба должны принимать целочисленное значение в
качестве параметра.)
Существует два подхода к решению этой проблемы, один из которых заключается
в указании разных имен для методов. Именно это было представлено в
предыдущем примере механизма формирования шаблонов. Но в этом случае
возникает небольшая проблема: ASP.NET сопоставляет сегменты URL-адреса с
методами действий по имени, а при переименовании метода, как правило,
маршрутизация не сможет найти этот метод. Решение показано в примере, а
именно: в метод DeleteConfirmed следует добавить атрибут ActionName("Delete") .
Этот атрибут выполняет сопоставление для системы маршрутизации, чтобы URLадрес, включающий /Delete/ для запроса POST, смог найти метод DeleteConfirmed .
Другим распространенным решением проблемы для методов с одинаковыми
именами и сигнатурами является искусственное изменение сигнатуры метода POST
для включения дополнительного (неиспользуемого) параметра. Именно это было
сделано ранее, когда был добавлен параметр notUsed . То же самое можно сделать
для метода [HttpPost] Delete :
C#
// POST: Movies/Delete/6
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(int id, bool notUsed)
Публикация в Azure
Сведения о развертывании в Azure, см. в разделе Учебник: созданию приложения
ASP.NET Core и Базы данных SQL в Службе приложений Azure.
Назад
Учебники по ASP.NET Core Blazor
Статья • 28.01.2023 • Чтение занимает 2 мин
Вот доступные учебники по ASP.NET Core Blazor:
Создание первого приложения Blazor
(Blazor Server)
Создание приложения Blazor со списком дел (Blazor Server или Blazor
WebAssembly)
Использование ASP.NET Core SignalR с Blazor (Blazor Server ил Blazor
WebAssembly)
Учебники по Blazor Hybrid в ASP.NET Core
Модули Learn
Дополнительные сведения о моделях размещения Blazor, Blazor Server и Blazor
WebAssembly, см. в статье Модели размещения Blazor в ASP.NET Core.
Учебник. Создание веб-API с
помощью ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 60 мин
Авторы: Рик Андерсон
(Rick Anderson) и Кирк Ларкин
(Kirk Larkin)
В этом руководстве рассматриваются основы создания веб-API на основе
контроллера, использующего базу данных. Другой подход к созданию API в
ASP.NET Core заключается в создании минимальных API. Сведения о выборе между
минимальными API и API на основе контроллера см. в статье Общие сведения об
API. Руководство по созданию минимального API см. в статье Учебник. Создание
минимального API с помощью ASP.NET Core.
Обзор
В этом руководстве создается следующий API-интерфейс:
API
Описание
Текст
запроса
Текст ответа
GET /api/todoitems
Получение всех элементов
задач
Отсутствуют
Массив элементов
задач
GET
Отсутствуют
Элемент задачи
/api/todoitems/{id}
Получение объекта по
идентификатору
POST /api/todoitems
Добавление нового элемента
Элемент
Элемент задачи
задачи
PUT
Обновление существующего
Элемент
/api/todoitems/{id}
элемента
задачи
DELETE
Удаление элемента
Нет
/api/todoitems/{id}
На следующем рисунке показана структура приложения.
Отсутствуют
Отсутствуют
Предварительные требования
Visual Studio
Visual Studio 2022
с рабочей нагрузкой ASP.NET и веб-разработка.
Создайте веб-проект.
Visual Studio
В меню Файл выберите Создать >Проект.
В поле поиска введите Веб-API.
Выберите шаблон Веб-API ASP.NET Core и нажмите кнопку Далее.
В диалоговом окне Настроить новый проект присвойте проекту имя
TodoApi и нажмите кнопку Далее.
В диалоговом окне Дополнительные сведения выполните следующие
действия.
Убедитесь, что платформа является .NET 7.0 (или более поздней
версии).
Убедитесь, что флажок Use controllers (uncheck to use minimal APIs)
(Использовать контроллеры (снимите этот флажок для использования
минимальных API)) установлен.
Нажмите кнопку создания.
7 Примечание
Рекомендации по добавлению пакетов в приложения .NET см. в разделе
Способы установки пакетов NuGet в статье Рабочий процесс использования
пакета (документация по NuGet). Проверьте правильность версий пакета на
сайте NuGet.org .
Тестирование проекта
Шаблон проекта создает API WeatherForecast с поддержкой Swagger.
Visual Studio
Нажмите клавиши CTRL+F5, чтобы выполнить запуск без отладчика.
Visual Studio отображает следующее диалоговое окно, если проект еще не
настроен для использования SSL:
Выберите Да, чтобы сделать SSL-сертификат IIS Express доверенным.
Отобразится следующее диалоговое окно.
Выберите Да, если согласны доверять сертификату разработки.
Сведения о доверии к браузеру Firefox см. в разделе Ошибка сертификата
браузера Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
Visual Studio запустит браузер по умолчанию и перейдет к https://localhost:
<port>/swagger/index.html , где <port> — это номер порта, выбранный
случайным образом.
Откроется страница Swagger /swagger/index.html . Выберите Get (Получить)>Try it
out (Попробовать)>Execute (Выполнить). На странице отобразятся:
команда Curl
для тестирования API WeatherForecast;
URL-адрес для тестирования API WeatherForecast;
код, текст и заголовки ответа;
Раскрывающийся список с типами носителей, примером значения и схемы.
Если страница Swagger не отображается, см. эту проблему на сайте GitHub .
Swagger используется для создания полезной документации и страниц справки для
веб-API. В этом учебнике рассматривается создание веб-API. Дополнительные
сведения о Swagger см. в статье Документация по веб-API ASP.NET Core с
использованием Swagger (OpenAPI).
Скопируйте и вставьте URL-адрес запроса в адресную строку браузера:
https://localhost:<port>/weatherforecast
Возвращаемые данные JSON будут примерно такими:
JSON
[
{
"date": "2019-07-16T19:04:05.7257911-06:00",
"temperatureC": 52,
"temperatureF": 125,
"summary": "Mild"
},
{
"date": "2019-07-17T19:04:05.7258461-06:00",
"temperatureC": 36,
"temperatureF": 96,
"summary": "Warm"
},
{
"date": "2019-07-18T19:04:05.7258467-06:00",
"temperatureC": 39,
"temperatureF": 102,
"summary": "Cool"
},
{
"date": "2019-07-19T19:04:05.7258471-06:00",
"temperatureC": 10,
"temperatureF": 49,
"summary": "Bracing"
},
{
"date": "2019-07-20T19:04:05.7258474-06:00",
"temperatureC": -1,
"temperatureF": 31,
"summary": "Chilly"
}
]
Добавление класса модели
Модель — это набор классов, представляющих данные, которыми управляет
приложение. Для этого приложения используется класс модели TodoItem .
Visual Studio
В обозревателе решений щелкните проект правой кнопкой мыши.
Выберите Добавить>Новая папка. Назовите папку Models .
Щелкните папку Models правой кнопкой мыши и выберите пункты
Добавить>Класс. Присвойте классу имя TodoItem и выберите Добавить.
Замените код шаблона следующим кодом:
C#
namespace TodoApi.Models;
public class TodoItem
{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Свойство Id выступает в качестве уникального ключа реляционной базы данных.
Классы моделей можно размещать в любом месте проекта, но обычно для этого
используется папка Models .
Добавление контекста базы данных
Контекст базы данных —это основной класс, который координирует
функциональные возможности Entity Framework для модели данных. Этот класс
является производным от класса Microsoft.EntityFrameworkCore.DbContext.
Visual Studio
Добавление пакетов NuGet
В меню Средства выберите Диспетчер пакетов NuGet > Управление
пакетами NuGet для решения.
Перейдите на вкладку Обзор и введите
Microsoft.EntityFrameworkCore.InMemory в поле поиска.
В области слева щелкните Microsoft.EntityFrameworkCore.InMemory .
Установите флажок Проект в области справа и выберите Установить.
Добавление контекста базы данных
TodoContext
Щелкните папку Models правой кнопкой мыши и выберите пункты
Добавить>Класс. Назовите класс TodoContext и нажмите Добавить.
Введите следующий код:
C#
using Microsoft.EntityFrameworkCore;
namespace TodoApi.Models;
public class TodoContext : DbContext
{
public TodoContext(DbContextOptions<TodoContext> options)
: base(options)
{
}
public DbSet<TodoItem> TodoItems { get; set; } = null!;
}
Регистрация контекста базы данных
В ASP.NET Core службы (такие как контекст базы данных) должны быть
зарегистрированы с помощью контейнера внедрения зависимостей. Контейнер
предоставляет службу контроллерам.
Обновите файл Program.cs , используя следующий выделенный код:
C#
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Предыдущий код:
Добавляет using директивы.
Добавляет контекст базы данных в контейнер внедрения зависимостей.
Указывает, что контекст базы данных будет использовать базу данных в
памяти.
Формирование шаблонов контроллера
Visual Studio
Щелкните папку Controllers правой кнопкой мыши.
Щелкните Добавить>Создать шаблонный элемент.
Выберите Контроллер API с действиями, использующий
Entity Framework, а затем выберите Добавить.
В диалоговом окне Контроллер API с действиями, использующий
Entity Framework сделайте следующее:
Выберите TodoItem (TodoApi.Models) в поле Класс модели.
Выберите TodoContext (TodoApi.Models) в поле Класс контекста
данных.
Нажмите Добавить.
Если операция формирования шаблонов завершается неудачно, нажмите
кнопку Добавить, чтобы попытаться сформировать шаблон еще раз.
Сформированный код:
Пометьте этот класс атрибутом [ApiController]. Этот атрибут указывает, что
контроллер отвечает на запросы веб-API. Дополнительные сведения о
поведении, которое реализует этот атрибут, см. в статье Создание веб-API с
помощью ASP.NET Core.
Использует внедрение зависимостей для внедрения контекста базы данных
( TodoContext ) в контроллер. Контекст базы данных используется в каждом
методе создания, чтения, обновления и удаления
в контроллере.
Шаблоны ASP.NET Core для:
Контроллеры с представлениями включают [action] в шаблоне маршрута.
Контроллеры API не включают [action] в шаблоне маршрута.
[action] Если маркер отсутствует в шаблоне маршрута, имя действия (имя метода)
не включается в конечную точку. То есть имя связанного метода действия не
используется в соответствующем маршруте.
Обновление метода создания PostTodoItem
Измените инструкцию возврата в PostTodoItem и используйте оператор nameof:
C#
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
{
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();
//
return CreatedAtAction("GetTodoItem", new { id = todoItem.Id },
todoItem);
return CreatedAtAction(nameof(GetTodoItem), new { id = todoItem.Id },
todoItem);
}
Приведенный выше код является методом HTTP POST , как указано атрибутом
[HttpPost] . Метод получает значение TodoItem из текста HTTP-запроса.
Дополнительные сведения см. в разделе Маршрутизация атрибутов с помощью
атрибутов Http[Verb].
Метод CreatedAtAction:
В случае успеха возвращает код состояния HTTP 201 . HTTP 201 — это
стандартный HTTP POST ответ для метода, который создает новый ресурс на
сервере.
Добавляет в ответ заголовок Location . Заголовок Location указывает URI
новой созданной задачи. Дополнительные сведения см. в статье 10.2.2 201
"Создан ресурс"
.
Указывает действие GetTodoItem для создания URI заголовка Location .
Ключевое слово nameof C# используется для предотвращения жесткого
программирования имени действия в вызове CreatedAtAction .
Тестирование PostTodoItem
Нажмите клавиши CTRL+F5, чтобы запустить приложение.
В окне браузера Swagger выберите POST /api/TodoItems, а затем выберите
Попробовать.
В окне Входные данные текста запроса обновите JSзначение ON. Например,
примененная к объекту директива
JSON
{
"name": "walk dog",
"isComplete": true
}
Нажмите кнопку Выполнить.
Тестирование URI заголовка расположения
В предыдущем запросе POST в пользовательском интерфейсе Swagger
отображается заголовок расположения
в разделе Заголовки ответов. Например,
location: https://localhost:7260/api/TodoItems/1 . В заголовке расположения
отображается универсальный код ресурса (URI) для созданного ресурса.
Чтобы проверить заголовок расположения, выполните следующие действия.
В окне браузера Swagger выберите GET /api/TodoItems/{id}, а затем выберите
Попробовать.
Введите 1 в id поле ввода и нажмите кнопку Выполнить.
Знакомство с методами GET
Реализуются две конечные точки GET:
GET /api/todoitems
GET /api/todoitems/{id}
В предыдущем разделе показан пример /api/todoitems/{id} маршрута.
Следуйте инструкциям POST , чтобы добавить еще один элемент списка дел, а
затем протестировать /api/todoitems маршрут с помощью Swagger.
Это приложение использует выполняющуюся в памяти базу данных. Если
остановить и вновь запустить его, предшествующий запрос GET не возвратит
никаких данных. Если данные не возвращаются, данные для приложения
получаются методом POST.
Маршрутизация и пути URL
Атрибут [HttpGet] обозначает метод, который отвечает на HTTP GET запрос. Путь
URL для каждого метода формируется следующим образом:
Возьмите строку шаблона в атрибуте Route контроллера:
C#
[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
Замените [controller] именем контроллера (по соглашению это имя класса
контроллера без суффикса "Controller"). В этом примере класс контроллера
имеет имя TodoItems, а сам контроллер, соответственно, — "TodoItems". В
ASP.NET Core маршрутизация реализуется без учета регистра символов.
Если атрибут [HttpGet] имеет шаблон маршрута (например,
[HttpGet("products")] ), добавьте его к пути. В этом примере шаблон не
используется. Дополнительные сведения см. в разделе Маршрутизация
атрибутов с помощью атрибутов Http[Verb].
В следующем методе GetTodoItem "{id}" — это переменная-заполнитель для
уникального идентификатора элемента задачи. При вызове GetTodoItem параметру
метода id присваивается значение "{id}" в URL-адресе.
C#
[HttpGet("{id}")]
public async Task<ActionResult<TodoItem>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return todoItem;
}
Возвращаемые значения
Тип возвращаемого значения для методов GetTodoItems и GetTodoItem —
ActionResult<T>. ASP.NET Core автоматически сериализует объект в формат JSON
и записывает данные JSON в тело сообщения ответа. Код ответа для этого типа
возвращаемого значения равен 200 OK , что свидетельствует об отсутствии
необработанных исключений. Необработанные исключения преобразуются в
ошибки 5xx.
Типы возвращаемых значений ActionResult могут представлять широкий спектр
кодов состояний HTTP. Например, метод GetTodoItem может возвращать два
разных значения состояния:
Если запрошенному идентификатору не соответствует ни один элемент, метод
возвращает код ошибки 404
NotFound (Не найдено).
В противном случае метод возвращает код 200 с телом ответа в формате
JSON. Возвращает item результат в ответе HTTP 200 .
Метод PutTodoItem
Проверьте метод PutTodoItem .
C#
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItem todoItem)
{
if (id != todoItem.Id)
{
return BadRequest();
}
_context.Entry(todoItem).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TodoItemExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
PutTodoItem аналогичен PostTodoItem , за исключением того, что использует . HTTP
PUT Ответ — 204 (Нет содержимого)
. Согласно спецификации HTTP, запрос
требует от PUT клиента отправки всей обновленной сущности, а не только
изменений. Чтобы обеспечить поддержку частичных обновлений, используйте
HTTP PATCH.
Тестирование метода PutTodoItem
В этом примере используется база данных в памяти, которая должна быть
инициирована при каждом запуске приложения. При выполнении вызова PUT в
базе данных уже должен существовать какой-либо элемент. Для этого перед
вызовом PUT выполните вызов GET, чтобы убедиться в наличии такого элемента в
базе данных.
С помощью пользовательского интерфейса Swagger нажмите кнопку PUT, чтобы
обновить TodoItem объект с идентификатором = 1 и задать для его имени значение
"feed fish" . Обратите внимание, что ответ имеет значение HTTP 204 No Content
.
Метод DeleteTodoItem
Проверьте метод DeleteTodoItem .
C#
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return NoContent();
}
Тестирование метода DeleteTodoItem
Используйте пользовательский интерфейс Swagger, чтобы удалить объект
TodoItem с идентификатором 1. Обратите внимание, что ответ имеет значение HTTP
204 No Content .
Тестирование с помощью http-repl, Postman
или curl
Для тестирования API часто используются http-repl, Postman
и curl . Swagger
использует curl и отображает отправленную curl команду.
Инструкции по этим средствам см. по следующим ссылкам:
Тестирование API с помощью Postman
Установка и проверка API с помощью http-repl
Дополнительные сведения см. в http-repl статье Тестирование веб-API с помощью
HttpRepl.
Предотвращение избыточной публикации
В настоящее время пример приложения предоставляет весь объект TodoItem .
Рабочие приложения обычно ограничивают вводимые данные и возвращают их с
помощью подмножества модели. Такое поведение реализовано по нескольким
причинам, но в основном из соображений безопасности. Подмножество модели
обычно называется объектом передачи данных (DTO), моделью ввода или
моделью представления. В рамках этого руководства используется DTO.
DTO можно использовать для следующего:
Предотвращение избыточной публикации.
Скрытие свойств, которые не предназначены для просмотра клиентами.
Пропуск некоторых свойств, чтобы уменьшить размер полезной нагрузки.
Сведение графов объектов, содержащих вложенные объекты. Сведенные
графы объектов могут быть удобнее для клиентов.
Чтобы продемонстрировать подход с применением DTO, обновите класс TodoItem ,
включив в него поле секрета:
C#
namespace TodoApi.Models
{
public class TodoItem
{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
}
Поле секрета должно быть скрыто в этом приложении, однако административное
приложение может отобразить его.
Убедитесь, что вы можете отправить и получить секретное поле.
Создайте модель DTO:
C#
namespace TodoApi.Models;
public class TodoItemDTO
{
public long Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Обновите TodoItemsController для использования TodoItemDTO :
C#
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;
namespace TodoApi.Controllers;
[Route("api/[controller]")]
[ApiController]
public class TodoItemsController : ControllerBase
{
private readonly TodoContext _context;
public TodoItemsController(TodoContext context)
{
_context = context;
}
// GET: api/TodoItems
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItemDTO>>> GetTodoItems()
{
return await _context.TodoItems
.Select(x => ItemToDTO(x))
.ToListAsync();
}
// GET: api/TodoItems/5
// <snippet_GetByID>
[HttpGet("{id}")]
public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return ItemToDTO(todoItem);
}
// </snippet_GetByID>
// PUT: api/TodoItems/5
// To protect from overposting attacks, see
https://go.microsoft.com/fwlink/?linkid=2123754
// <snippet_Update>
[HttpPut("{id}")]
public async Task<IActionResult> PutTodoItem(long id, TodoItemDTO
todoDTO)
{
if (id != todoDTO.Id)
{
return BadRequest();
}
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
todoItem.Name = todoDTO.Name;
todoItem.IsComplete = todoDTO.IsComplete;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException) when (!TodoItemExists(id))
{
return NotFound();
}
return NoContent();
}
// </snippet_Update>
// POST: api/TodoItems
// To protect from overposting attacks, see
https://go.microsoft.com/fwlink/?linkid=2123754
// <snippet_Create>
[HttpPost]
public async Task<ActionResult<TodoItemDTO>> PostTodoItem(TodoItemDTO
todoDTO)
{
var todoItem = new TodoItem
{
IsComplete = todoDTO.IsComplete,
Name = todoDTO.Name
};
_context.TodoItems.Add(todoItem);
await _context.SaveChangesAsync();
return CreatedAtAction(
nameof(GetTodoItem),
new { id = todoItem.Id },
ItemToDTO(todoItem));
}
// </snippet_Create>
// DELETE: api/TodoItems/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTodoItem(long id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
_context.TodoItems.Remove(todoItem);
await _context.SaveChangesAsync();
return NoContent();
}
private bool TodoItemExists(long id)
{
return _context.TodoItems.Any(e => e.Id == id);
}
private static TodoItemDTO ItemToDTO(TodoItem todoItem) =>
new TodoItemDTO
{
Id = todoItem.Id,
Name = todoItem.Name,
IsComplete = todoItem.IsComplete
};
}
Убедитесь, что вы можете отправить или получить секретное поле.
Вызов веб-API с помощью JavaScript
См. руководство по : Вызовите веб-API ASP.NET Core с помощью JavaScript.
Серия видео о веб-API
См. видео: Серия для начинающих: Веб-API.
Добавление поддержки аутентификации в
веб-API
ASP.NET Core Identity позволяет использовать функцию входа в пользовательском
интерфейсе для веб-приложений ASP.NET Core. Чтобы защитить веб-API и
одностраничные приложения, используйте один из следующих способов:
Azure Active Directory
Azure Active Directory B2C (Azure AD B2C)
Duende Identity Server
Duende Identity Server — это платформа OpenID Connect и OAuth 2.0 для ASP.NET
Core. Duende Identity Server включает следующие функции безопасности:
Проверка подлинности как услуга (AaaS)
Единый вход (SSO) для нескольких типов приложений
Контроль доступа для API
Шлюз федерации
) Важно!
Компания Duende Software
может потребовать лицензионный сбор за
использование Duende IdentityServer в рабочей среде. Дополнительные
сведения см. в статье Миграция с ASP.NET Core 5.0 на 6.0.
Дополнительные сведения см. в документации по Duende Identity Server (на вебсайте ПО Duende) .
Публикация в Azure
Сведения о развертывании в Azure см. в разделе Краткое руководство.
Развертывание веб-приложения ASP.NET.
Дополнительные ресурсы
Просмотреть или скачать пример кода для этого учебника . См. раздел
Практическое руководство. Скачивание файла.
Дополнительные сведения см. в следующих ресурсах:
Создание веб-API с помощью ASP.NET Core
Руководство. Создание минимального API с помощью ASP.NET Core
Документация по веб-API ASP.NET Core с использованием Swagger (OpenAPI)
Razor Использование Pages с Entity Framework Core в ASP.NET Core:
руководство 1 из 8
Маршрутизация к действиям контроллера в ASP.NET Core
Типы возвращаемых действий контроллера в веб-API ASP.NET Core
Развертывание приложений ASP.NET Core в Службе приложений Azure
Размещение и развертывание ASP.NET Core
Создание веб-API с помощью ASP.NET Core
Руководство. Создание минимального
API с помощью ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 29 мин
Авторы: Рик Андерсон
(Rick Anderson) и Том Дайкстра
(Tom Dykstra)
Архитектура минимальных API позволяет создавать API для HTTP с минимальным
числом зависимостей. Они идеально подходят для микрослужб и приложений,
которым нужен небольшой набор файлов, компонентов и зависимостей на
платформе ASP.NET Core.
В этом руководстве описаны основы создания минимального API с помощью
ASP.NET Core. Другим подходом к созданию API в ASP.NET Core является
использование контроллеров. Сведения о выборе между минимальными API и API
на основе контроллера см. в статье Общие сведения об API. Руководство по
созданию проекта API на основе контроллеров , содержащих дополнительные
функции, см. в статье Создание веб-API.
Обзор
В этом руководстве создается следующий API-интерфейс:
API
Описание
Текст
запроса
Текст ответа
GET /todoitems
Получение всех элементов задач
Отсутствуют
Массив
элементов
задач
GET
Получение элементов выполненных
/todoitems/complete
заданий из списка
GET
Отсутствуют
/todoitems/{id}
Получение объекта по
идентификатору
Элемент
задачи
POST /todoitems
Добавление нового элемента
Элемент
задачи
Элемент
задачи
PUT
Обновление существующего элемента
Элемент
задачи
Отсутствуют
Удаление элемента
Нет
None
/todoitems/{id}
DELETE
/todoitems/{id}
Отсутствуют
Массив
элементов
задач
Предварительные требования
Visual Studio
Visual Studio 2022
с рабочей нагрузкой ASP.NET и веб-разработка.
Создание проекта API
Visual Studio
Запустите Visual Studio 2022 и нажмите Создать проект.
В диалоговом окне Создание нового проекта выполните следующие
действия.
Введите Empty в поле Поиск шаблонов.
Выберите шаблон ASP.NET Core Пустой и нажмите кнопку Далее.
Присвойте проекту имя TodoApi и щелкните Далее.
В диалоговом окне Дополнительные сведения выполните следующие
действия.
Выбор .NET 7.0
Снимите флажок Не использовать операторы верхнего уровня
Выберите Создать.
Анализ кода
Файл Program.cs содержит следующий код:
C#
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
Предыдущий код:
WebApplicationBuilder Создает и WebApplication с предварительно
настроенными значениями по умолчанию.
Создает конечную точку / HTTP GET, которая возвращает Hello World! :
Запуск приложения
Visual Studio
Нажмите клавиши CTRL+F5, чтобы выполнить запуск без отладчика.
Visual Studio отображает следующее диалоговое окно.
Выберите Да, чтобы сделать SSL-сертификат IIS Express доверенным.
Отобразится следующее диалоговое окно.
Выберите Да, если согласны доверять сертификату разработки.
Сведения о доверии к браузеру Firefox см. в разделе Ошибка сертификата
браузера Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
Visual Studio запускает Kestrel веб-сервер и открывает окно браузера.
Hello World! отображается в браузере. Файл Program.cs содержит минимальное,
но полное приложение.
Добавление пакетов NuGet
Для поддержки возможностей базы данных и диагностики, которые используются
в этом руководстве, необходимо добавить пакеты NuGet.
Visual Studio
В меню Средства выберите Диспетчер пакетов NuGet > Управление
пакетами NuGet для решения.
Выберите вкладку Обзор.
Введите Microsoft.EntityFrameworkCore.InMemory в поле поиска и
щелкните Microsoft.EntityFrameworkCore.InMemory .
Установите флажок Проект в области справа и выберите Установить.
Добавьте пакет Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore по
представленным выше инструкциям.
Классы контекста базы данных и модели
В папке проекта создайте файл Todo.cs со следующим кодом:
C#
class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}
Приведенный выше код создает модель для этого приложения. Класс модели
представляет данные, которыми управляет наше приложение.
Создайте файл с именем TodoDb.cs со следующим кодом:
C#
using Microsoft.EntityFrameworkCore;
class TodoDb : DbContext
{
public TodoDb(DbContextOptions<TodoDb> options)
: base(options) { }
public DbSet<Todo> Todos => Set<Todo>();
}
Приведенный выше код определяет контекст базы данных, который является
основным классом, который координирует функциональные возможности Entity
Framework для модели данных. Этот класс является производным от класса
Microsoft.EntityFrameworkCore.DbContext.
Добавление кода API
Замените содержимое файла Program.cs приведенным ниже кодом.
C#
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.Ok(todo);
}
return Results.NotFound();
});
app.Run();
Выделенный ниже код добавляет контекст базы данных в контейнер внедрения
зависимостей (DI) и позволяет отображать исключения, связанные с базой данных:
C#
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
Контейнер DI предоставляет доступ к контексту базы данных и другим службам.
Установка Postman для тестирования
приложения
В этом учебнике для тестирования API используется Postman.
Установка Postman
Запустите веб-приложение.
Запустите Postman.
Отключение параметра Проверка SSL-сертификата
Для Postman для Windows выберитеПараметрыфайла> (вкладка Общие) и
отключите проверку SSL-сертификата.
Для Postman для macOS выберитеПараметрыPostman> (вкладка Общие) и
отключите проверку SSL-сертификата.
2 Предупреждение
Повторно включите проверку SSL-сертификата после тестирования
примера приложения.
Проверка публикации данных
Следующий код в создает Program.cs конечную точку /todoitems HTTP POST,
которая добавляет данные в базу данных в памяти:
C#
app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
Запустите приложение. В браузере отображается ошибка 404, так как конечная /
точка больше не существует.
Используйте конечную точку POST для добавления данных в приложение:
Создайте новый HTTP-запрос.
Установите HTTP-метод POST .
Задайте для URI значение https://localhost:<port>/todoitems . Пример:
https://localhost:5001/todoitems
Откройте вкладку Тело.
Выберите необработанный.
Выберите тип JSON.
В теле запроса введите код JSON для элемента списка дел:
JSON
{
"name":"walk dog",
"isComplete":true
}
Нажмите кнопку Отправить.
Изучение конечных точек GET
Пример приложения реализует несколько конечных точек GET путем вызова
MapGet :
API
Описание
Текст
запроса
Текст ответа
GET /todoitems
Получение всех элементов задач
Отсутствуют
Массив
элементов задач
GET
Получение всех выполненных
Отсутствуют
Массив
/todoitems/complete
элементов заданий
GET
Получение объекта по
идентификатору
/todoitems/{id}
C#
app.MapGet("/todoitems", async (TodoDb db) =>
await db.Todos.ToListAsync());
элементов задач
Отсутствуют
Элемент задачи
app.MapGet("/todoitems/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
app.MapGet("/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
Тестирование конечных точек GET
Протестируйте приложение, вызвав конечные точки из браузера или Postman.
Следующие действия предназначены для Postman.
Создайте новый HTTP-запрос.
Укажите метод HTTP GET.
Задайте для URI запроса значение https://localhost:<port>/todoitems .
Например, https://localhost:5001/todoitems .
Нажмите кнопку Отправить.
Вызов метода GET /todoitems создает ответ, аналогичный следующему:
JSON
[
{
"id": 1,
"name": "walk dog",
"isComplete": false
}
]
Задайте для URI запроса значение https://localhost:<port>/todoitems/1 .
Например, https://localhost:5001/todoitems/1 .
Нажмите кнопку Отправить.
Ответ аналогичен следующему:
JSON
{
"id": 1,
"name": "walk dog",
"isComplete": false
}
Это приложение использует выполняющуюся в памяти базу данных. После
перезапуска приложения запрос GET не возвращает никаких данных. Если данные
не возвращаются, отправьте в приложение данные POST и повторите запрос GET.
Возвращаемые значения
ASP.NET Core автоматически сериализует объект в формат JSON
и записывает
данные JSON в тело сообщения ответа. Код ответа для этого типа возвращаемого
значения равен 200 OK , что свидетельствует об отсутствии необработанных
исключений. Необработанные исключения преобразуются в ошибки 5xx.
Типы возвращаемых значений могут представлять широкий спектр кодов
состояний HTTP. Например, метод GET /todoitems/{id} может возвращать два
разных значения состояния:
Если запрошенному идентификатору не соответствует ни один элемент, метод
возвращает код ошибки 404
NotFound (Не найдено).
В противном случае метод возвращает код 200 с телом ответа в формате
JSON. При возвращении item возвращается ответ HTTP 200.
Изучение конечной точки PUT
Этот пример приложения реализует одну конечную точку PUT с помощью MapPut :
C#
app.MapPut("/todoitems/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
Этот метод отличается от метода MapPost только тем, что использует метод HTTP
PUT. Успешный ответ возвращает состояние 204 (без содержимого) . Согласно
спецификации HTTP, запрос PUT требует, чтобы клиент отправлял всю
обновленную сущность, а не только изменения. Чтобы обеспечить поддержку
частичных обновлений, используйте HTTP PATCH.
Тестирование конечной точки PUT
В этом примере используется база данных в памяти, которая должна быть
инициирована при каждом запуске приложения. При выполнении вызова PUT в
базе данных уже должен существовать какой-либо элемент. Для этого перед
вызовом PUT выполните вызов GET, чтобы убедиться в наличии такого элемента в
базе данных.
Обновите элемент списка дел с идентификатором 1 и присвойте ему имя "feed
fish" :
JSON
{
"id": 1,
"name": "feed fish",
"isComplete": false
}
Изучение и тестирование конечной точки
DELETE
Этот пример приложения реализует одну конечную точку DELETE с помощью
MapDelete :
C#
app.MapDelete("/todoitems/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.Ok(todo);
}
return Results.NotFound();
});
Удалите элемент списка дел с помощью Postman:
Укажите метод DELETE .
Укажите URI удаляемого объекта (например,
https://localhost:5001/todoitems/1 ).
Нажмите кнопку Отправить.
Использование API MapGroup
Пример кода приложения повторяет todoitems префикс URL-адреса при каждой
настройке конечной точки. API-интерфейсы часто имеют группы конечных точек с
общим префиксом URL-адресов, и MapGroup для организации таких групп
доступен метод . Он сокращает количество повторяющихся кодов и позволяет
настраивать целые группы конечных точек с помощью одного вызова таких
методов, как RequireAuthorization и WithMetadata.
Замените все содержимое Program.cs следующим кодом:
C#
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", async (TodoDb db) =>
await db.Todos.ToListAsync());
todoItems.MapGet("/complete", async (TodoDb db) =>
await db.Todos.Where(t => t.IsComplete).ToListAsync());
todoItems.MapGet("/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound());
todoItems.MapPost("/", async (Todo todo, TodoDb db) =>
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todoitems/{todo.Id}", todo);
});
todoItems.MapPut("/{id}", async (int id, Todo inputTodo, TodoDb db) =>
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return Results.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return Results.NoContent();
});
todoItems.MapDelete("/{id}", async (int id, TodoDb db) =>
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return Results.Ok(todo);
}
return Results.NotFound();
});
app.Run();
Приведенный выше код содержит следующие изменения:
Добавляет var todoItems = app.MapGroup("/todoitems"); для настройки группы
с помощью префикса /todoitems URL-адреса .
Изменяет все app.Map<HttpVerb> методы на todoItems.Map<HttpVerb> .
Удаляет префикс /todoitems URL-адреса из Map<HttpVerb> вызовов метода.
Протестируйте конечные точки, чтобы убедиться, что они работают одинаково.
Использование API TypedResults
Методы Map<HttpVerb> могут вызывать методы обработчика маршрутов вместо
использования лямбда-выражений. Чтобы просмотреть пример, обновите Файл
Program.cs , используя следующий код:
C#
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t =>
t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.Ok(todo);
}
return TypedResults.NotFound();
}
Теперь Map<HttpVerb> код вызывает методы вместо лямбда-выражений:
C#
var todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/complete", GetCompleteTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
Эти методы возвращают объекты, которые реализуют IResult и определяются с
помощью TypedResults:
C#
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
static async Task<IResult> GetCompleteTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Where(t =>
t.IsComplete).ToListAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(todo)
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(Todo todo, TodoDb db)
{
db.Todos.Add(todo);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todo.Id}", todo);
}
static async Task<IResult> UpdateTodo(int id, Todo inputTodo, TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = inputTodo.Name;
todo.IsComplete = inputTodo.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.Ok(todo);
}
return TypedResults.NotFound();
}
Модульные тесты могут вызывать эти методы и проверять, что они возвращают
правильный тип. Например, если метод имеет значение GetAllTodos :
C#
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.ToArrayAsync());
}
Код модульного теста может проверить, возвращается ли объект типа Ok<Todo[]>
из метода обработчика. Например:
C#
public async Task GetAllTodos_ReturnsOkOfTodosResult()
{
// Arrange
var db = CreateDbContext();
// Act
var result = await TodosApi.GetAllTodos(db);
// Assert: Check for the correct returned type
Assert.IsType<Ok<Todo[]>>(result);
}
Предотвращение избыточной публикации
В настоящее время пример приложения предоставляет весь объект Todo . Рабочие
приложения обычно ограничивают вводимые данные и возвращают их с
помощью подмножества модели. Это связано с несколькими причинами, и
безопасность является основной. Подмножество модели обычно называется
объектом передачи данных (DTO), моделью ввода или моделью представления. В
этой статье используется DTO.
DTO можно использовать для следующего:
Предотвращение избыточной публикации.
Скрытие свойств, которые не предназначены для просмотра клиентами.
Пропуск некоторых свойств, чтобы уменьшить размер полезной нагрузки.
Сведение графов объектов, содержащих вложенные объекты. Сведенные
графы объектов могут быть удобнее для клиентов.
Чтобы продемонстрировать подход с применением DTO, обновите класс Todo ,
включив в него поле секрета:
C#
public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public string? Secret { get; set; }
}
Поле секрета должно быть скрыто в этом приложении, однако административное
приложение может отобразить его.
Убедитесь, что вы можете отправить и получить секретное поле.
Создайте файл с именем TodoItemDTO.cs со следующим кодом:
C#
public class TodoItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
public TodoItemDTO() { }
public TodoItemDTO(Todo todoItem) =>
(Id, Name, IsComplete) = (todoItem.Id, todoItem.Name,
todoItem.IsComplete);
}
Обновите код в , Program.cs чтобы использовать эту модель DTO:
C#
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<TodoDb>(opt =>
opt.UseInMemoryDatabase("TodoList"));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
RouteGroupBuilder todoItems = app.MapGroup("/todoitems");
todoItems.MapGet("/", GetAllTodos);
todoItems.MapGet("/{id}", GetTodo);
todoItems.MapPost("/", CreateTodo);
todoItems.MapPut("/{id}", UpdateTodo);
todoItems.MapDelete("/{id}", DeleteTodo);
app.Run();
static async Task<IResult> GetAllTodos(TodoDb db)
{
return TypedResults.Ok(await db.Todos.Select(x => new
TodoItemDTO(x)).ToArrayAsync());
}
static async Task<IResult> GetTodo(int id, TodoDb db)
{
return await db.Todos.FindAsync(id)
is Todo todo
? TypedResults.Ok(new TodoItemDTO(todo))
: TypedResults.NotFound();
}
static async Task<IResult> CreateTodo(TodoItemDTO todoItemDTO, TodoDb db)
{
var todoItem = new Todo
{
IsComplete = todoItemDTO.IsComplete,
Name = todoItemDTO.Name
};
db.Todos.Add(todoItem);
await db.SaveChangesAsync();
return TypedResults.Created($"/todoitems/{todoItem.Id}", todoItemDTO);
}
static async Task<IResult> UpdateTodo(int id, TodoItemDTO todoItemDTO,
TodoDb db)
{
var todo = await db.Todos.FindAsync(id);
if (todo is null) return TypedResults.NotFound();
todo.Name = todoItemDTO.Name;
todo.IsComplete = todoItemDTO.IsComplete;
await db.SaveChangesAsync();
return TypedResults.NoContent();
}
static async Task<IResult> DeleteTodo(int id, TodoDb db)
{
if (await db.Todos.FindAsync(id) is Todo todo)
{
db.Todos.Remove(todo);
await db.SaveChangesAsync();
return TypedResults.Ok(todo);
}
return TypedResults.NotFound();
}
Убедитесь, что вы можете опубликовать и получить все поля, кроме поля секрета.
Дальнейшие действия
Настройка параметров сериализации JSON
Сведения о настройке JSсериализации ON в приложениях Минимального API см. в
разделе Настройка JSпараметров сериализации ON.
Обработка ошибок и исключений
Страница исключений разработчика включена по умолчанию в среде разработки
для минимальных приложений API. Сведения об обработке ошибок и исключений
см. в статье Обработка ошибок в API ASP.NET Core.
Тестирование минимальных приложений API
Пример тестирования для минимального приложения API см. в этом примере на
GitHub .
Использование OpenAPI (Swagger)
Сведения об использовании OpenAPI с минимальными приложениями API см. в
статье Поддержка OpenAPI в минимальных API.
Публикация в Azure
Сведения о развертывании в Azure см. в статье Краткое руководство.
Развертывание веб-приложения ASP.NET.
Подробнее
Дополнительные сведения о минимальных приложениях API см. в кратком
справочнике по минимальным API.
Создание веб-API с помощью ASP.NET
Core и MongoDB
Статья • 28.01.2023 • Чтение занимает 19 мин
Авторы Пратик Ханделвал
(Pratik Khandelwal) и Скотт Эдди
(Scott Addie).
В этом руководстве описано, как создать веб-API, который выполняет операции
создания, чтения, обновления и удаления (CRUD) с базой данных NoSQL
MongoDB .
В этом руководстве вы узнаете, как:
" Настройка MongoDB
" создать базу данных MongoDB;
" определить коллекцию и схему MongoDB;
" выполнить операции CRUD MongoDB из веб-API.
" Настройка сериализации JSON
Предварительные требования
MongoDB
оболочка MongoDB ,
Visual Studio
Visual Studio 2022
с рабочей нагрузкой ASP.NET и веб-разработка.
Настройка MongoDB
В Windows MongoDB по умолчанию устанавливается в папку C:\Program
Files\MongoDB. Добавьте C:\Program Files\MongoDB\Server\version_number>\bin в
переменную среды Path . Это изменение обеспечит доступ к MongoDB из любого
места на компьютере разработки.
В следующих шагах используйте ранее установленный интерфейс MongoDB, чтобы
создать базу данных и коллекции и сохранить документы. Дополнительные
сведения о командах MongoDB: mongosh .
1. Выберите папку на компьютере разработки для хранения данных. Например
C:\BooksData при работе в Windows. Если такого каталога нет, создайте его. В
mongo Shell нельзя создавать каталоги.
2. Откройте командную оболочку. Выполните следующую команду для
подключения к MongoDB через порт 27017, заданный по умолчанию. Не
забудьте заменить <data_directory_path> каталогом, созданным на
предыдущем этапе.
Консоль
mongod --dbpath <data_directory_path>
3. Откройте другой экземпляр командной оболочки. Подключитесь к тестовой
базе данных по умолчанию, выполнив такую команду:
Консоль
mongosh
4. В командной оболочке выполните следующую команду:
Консоль
use BookStore
Будет создана база данных с именем BookStore, если она не существует. Если
такая база данных существует, для нее уже установлено подключение для
транзакций.
5. Создайте коллекцию Books с помощью такой команды:
Консоль
db.createCollection('Books')
Отобразится такой результат:
Консоль
{ "ok" : 1 }
6. Определите схему для коллекции Books и вставьте два документа, используя
следующую команду:
Консоль
db.Books.insertMany([{ "Name": "Design Patterns", "Price": 54.93,
"Category": "Computers", "Author": "Ralph Johnson" }, { "Name": "Clean
Code", "Price": 43.15, "Category": "Computers","Author": "Robert C.
Martin" }])
Отобразится примерно такой результат:
Консоль
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("61a6058e6c43f32854e51f51"),
ObjectId("61a6058e6c43f32854e51f52")
]
}
7 Примечание
ObjectId , показанные в предыдущем результате, не будут соответствовать
отображаемому в вашей командной оболочке.
7. Просмотрите документы в базе данных, используя такую команду:
Консоль
db.Books.find().pretty()
Отобразится примерно такой результат:
Консоль
{
"_id" : ObjectId("61a6058e6c43f32854e51f51"),
"Name" : "Design Patterns",
"Price" : 54.93,
"Category" : "Computers",
"Author" : "Ralph Johnson"
}
{
"_id" : ObjectId("61a6058e6c43f32854e51f52"),
"Name" : "Clean Code",
"Price" : 43.15,
"Category" : "Computers",
"Author" : "Robert C. Martin"
}
Схема добавляет автоматически созданное свойство _id типа ObjectId к
каждому документу.
Создание проекта веб-API ASP.NET Core
Visual Studio
1. Выберите ФайлСоздатьПроект.
2. Выберите тип проекта Веб-API ASP.NET Core и щелкните Далее.
3. Назовите проект BookStoreApi и щелкните Создать.
4. Выберите платформу .NET 6.0 (долгосрочная поддержка) и щелкните
Создать.
5. В окне Консоль диспетчера пакетов перейдите в корневую папку
проекта. Выполните следующую команду, чтобы установить драйвер .NET
для MongoDB:
PowerShell
Install-Package MongoDB.Driver
Добавление модели сущности
1. Добавьте каталог Models в корневую папку проекта.
2. Добавьте класс Book в каталог Book с помощью такого кода:
C#
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace BookStoreApi.Models;
public class Book
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string? Id { get; set; }
[BsonElement("Name")]
public string BookName { get; set; } = null!;
public decimal Price { get; set; }
public string Category { get; set; } = null!;
public string Author { get; set; } = null!;
}
В классе выше свойство Id :
Требуется для сопоставления объекта среды CLR с коллекцией MongoDB.
Помечается с помощью [BsonId]
для назначения этого свойства в
качестве первичного ключа документа.
Помечается с помощью [BsonRepresentation(BsonType.ObjectId)] , чтобы
разрешить передачу параметра в качестве типа string вместо структуры
[BsonRepresentation(BsonType.ObjectId)] . Mongo обрабатывает
преобразование из string в ObjectId .
Свойство BookName помечено атрибутом [BsonElement] . Значение атрибута
Name представляет имя свойства в коллекции MongoDB.
Добавление модели конфигурации
1. Добавьте в файл appsettings.json следующие значения конфигурации базы
данных:
JSON
{
"BookStoreDatabase": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "BookStore",
"BooksCollectionName": "Books"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
2. Добавьте класс BookStoreDatabaseSettings в каталог BookStoreDatabaseSettings
с помощью такого кода:
C#
namespace BookStoreApi.Models;
public class BookStoreDatabaseSettings
{
public string ConnectionString { get; set; } = null!;
public string DatabaseName { get; set; } = null!;
public string BooksCollectionName { get; set; } = null!;
}
Предыдущий класс BookStoreDatabaseSettings используется для хранения
значений свойств BookStoreDatabase файла appsettings.json . Свойства JSON и
C# имеют одинаковые имена, что упрощает сопоставление.
3. Добавьте выделенный ниже код в Program.cs :
C#
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.Configure<BookStoreDatabaseSettings>(
builder.Configuration.GetSection("BookStoreDatabase"));
В предыдущем коде экземпляр конфигурации, к которому привязан раздел
BookStoreDatabase файла appsettings.json , зарегистрирован в контейнере
внедрения зависимостей (DI). Например, свойство ConnectionString объекта
BookStoreDatabaseSettings заполняется свойством
BookStoreDatabase:ConnectionString в appsettings.json .
4. Добавьте следующий код в самое начало файла Program.cs , чтобы разрешить
ссылку BookStoreDatabaseSettings :
C#
using BookStoreApi.Models;
Добавление службы операций CRUD
1. Добавьте каталог Services в корневую папку проекта.
2. Добавьте класс BooksService в каталог BooksService с помощью такого кода:
C#
using BookStoreApi.Models;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
namespace BookStoreApi.Services;
public class BooksService
{
private readonly IMongoCollection<Book> _booksCollection;
public BooksService(
IOptions<BookStoreDatabaseSettings> bookStoreDatabaseSettings)
{
var mongoClient = new MongoClient(
bookStoreDatabaseSettings.Value.ConnectionString);
var mongoDatabase = mongoClient.GetDatabase(
bookStoreDatabaseSettings.Value.DatabaseName);
_booksCollection = mongoDatabase.GetCollection<Book>(
bookStoreDatabaseSettings.Value.BooksCollectionName);
}
public async Task<List<Book>> GetAsync() =>
await _booksCollection.Find(_ => true).ToListAsync();
public async Task<Book?> GetAsync(string id) =>
await _booksCollection.Find(x => x.Id ==
id).FirstOrDefaultAsync();
public async Task CreateAsync(Book newBook) =>
await _booksCollection.InsertOneAsync(newBook);
public async Task UpdateAsync(string id, Book updatedBook) =>
await _booksCollection.ReplaceOneAsync(x => x.Id == id,
updatedBook);
public async Task RemoveAsync(string id) =>
await _booksCollection.DeleteOneAsync(x => x.Id == id);
}
В предыдущем коде экземпляр BookStoreDatabaseSettings извлекается из DI
путем внедрения конструктора. Таким образом обеспечивается доступ к
значениям конфигурации в файле appsettings.json , которые были добавлены
в разделе appsettings.json .
3. Добавьте выделенный ниже код в Program.cs :
C#
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.Configure<BookStoreDatabaseSettings>(
builder.Configuration.GetSection("BookStoreDatabase"));
builder.Services.AddSingleton<BooksService>();
В предыдущем коде класс BooksService регистрируется в DI, чтобы обеспечить
поддержку внедрения через конструктор в используемые классы. Время
существования отдельной службы — наиболее подходящий вариант, так как
BooksService имеет прямую зависимость от MongoClient . В соответствии с
официальными правилами повторного использования клиента Mongo
следует регистрировать в DI с использованием времени существования
отдельной службы.
4. Добавьте следующий код в самое начало файла Program.cs , чтобы разрешить
ссылку BooksService :
C#
using BookStoreApi.Services;
Класс BooksService использует следующие члены MongoDB.Driver для выполнения
операций CRUD с базой данных:
MongoClient
— считывает экземпляр сервера для выполнения операций с
базой данных. Конструктор этого класса предоставляет строку подключения
MongoDB.
C#
public BooksService(
IOptions<BookStoreDatabaseSettings> bookStoreDatabaseSettings)
{
var mongoClient = new MongoClient(
bookStoreDatabaseSettings.Value.ConnectionString);
var mongoDatabase = mongoClient.GetDatabase(
bookStoreDatabaseSettings.Value.DatabaseName);
_booksCollection = mongoDatabase.GetCollection<Book>(
bookStoreDatabaseSettings.Value.BooksCollectionName);
}
IMongoDatabase
— представляет базу данных Mongo для выполнения
операций. В этом руководстве используется универсальный метод
GetCollection
TDocument>(collection) интерфейса для получения доступа к
данным в определенной коллекции. Выполняйте операции CRUD с
коллекцией после вызова этого метода. В вызове метода
GetCollection<TDocument>(collection) :
collection представляет имя коллекции;
TDocument представляет тип объекта среды CLR, хранящегося в коллекции;
GetCollection<TDocument>(collection) возвращает объект GetCollection<TDocument>
(collection) , представляющий коллекцию. В этом руководстве следующие методы
вызываются для коллекции:
DeleteOne
— удаляет один документ, отвечающий заданным критериям
поиска.
Find
TDocument> — возвращает все документы в коллекции,
соответствующие заданным критериям поиска.
InsertOneAsync
— вставляет предоставленный объект в виде нового
документа в коллекции.
ReplaceOneAsync
— заменяет один документ, отвечающий заданным
критериям поиска, предоставленным объектом.
Добавление контроллера
Добавьте класс BooksController в каталог BooksController с помощью такого кода:
C#
using BookStoreApi.Models;
using BookStoreApi.Services;
using Microsoft.AspNetCore.Mvc;
namespace BookStoreApi.Controllers;
[ApiController]
[Route("api/[controller]")]
public class BooksController : ControllerBase
{
private readonly BooksService _booksService;
public BooksController(BooksService booksService) =>
_booksService = booksService;
[HttpGet]
public async Task<List<Book>> Get() =>
await _booksService.GetAsync();
[HttpGet("{id:length(24)}")]
public async Task<ActionResult<Book>> Get(string id)
{
var book = await _booksService.GetAsync(id);
if (book is null)
{
return NotFound();
}
return book;
}
[HttpPost]
public async Task<IActionResult> Post(Book newBook)
{
await _booksService.CreateAsync(newBook);
return CreatedAtAction(nameof(Get), new { id = newBook.Id },
newBook);
}
[HttpPut("{id:length(24)}")]
public async Task<IActionResult> Update(string id, Book updatedBook)
{
var book = await _booksService.GetAsync(id);
if (book is null)
{
return NotFound();
}
updatedBook.Id = book.Id;
await _booksService.UpdateAsync(id, updatedBook);
return NoContent();
}
[HttpDelete("{id:length(24)}")]
public async Task<IActionResult> Delete(string id)
{
var book = await _booksService.GetAsync(id);
if (book is null)
{
return NotFound();
}
await _booksService.RemoveAsync(id);
return NoContent();
}
}
Предыдущий контроллер веб-API:
использует класс BooksService для выполнения операций CRUD;
содержит методы действий для поддержки запросов HTTP GET, POST, PUT и
DELETE.
Вызывает CreatedAtAction в методе действия Create для возврата ответа
CreatedAtAction. Код состояния 201 представляет собой стандартный ответ
метода HTTP POST, создающего ресурс на сервере. CreatedAtAction также
добавляет заголовок Location в ответ. Заголовок Location указывает
универсальный код ресурса (URI) созданной книги.
Тестирование веб-API
1. Выполните сборку и запуск приложения.
2. Перейдите к https://localhost:<port>/api/books , где <port> — это
автоматически назначаемый номер порта для приложения, чтобы
протестировать метод действия Get контроллера без параметров.
Отобразится примерно такой ответ JSON:
JSON
[
{
"id": "61a6058e6c43f32854e51f51",
"bookName": "Design Patterns",
"price": 54.93,
"category": "Computers",
"author": "Ralph Johnson"
},
{
"id": "61a6058e6c43f32854e51f52",
"bookName": "Clean Code",
"price": 43.15,
"category": "Computers",
"author": "Robert C. Martin"
}
]
3. Перейдите по адресу https://localhost:<port>/api/books/{id here} , чтобы
протестировать перегруженный метод действия Get контроллера.
Отобразится примерно такой ответ JSON:
JSON
{
"id": "61a6058e6c43f32854e51f52",
"bookName": "Clean Code",
"price": 43.15,
"category": "Computers",
"author": "Robert C. Martin"
}
Настройка параметров сериализации JSON
Нужно изменить два параметра для возвращаемых ответов JSON в разделе
Тестирование веб-API:
Смешанный регистр имен свойств по умолчанию следует изменить в
соответствии с регистром Pascal имен свойств объекта CLR.
Свойство bookName должно возвращаться как Name .
Чтобы удовлетворить эти требования, внесите следующие изменения:
1. В Program.cs вставьте следующий выделенный код в вызов метода
AddControllers :
C#
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.Configure<BookStoreDatabaseSettings>(
builder.Configuration.GetSection("BookStoreDatabase"));
builder.Services.AddSingleton<BooksService>();
builder.Services.AddControllers()
.AddJsonOptions(
options => options.JsonSerializerOptions.PropertyNamingPolicy =
null);
После предыдущего изменения имена свойств в ответе сериализованного
JSON веб-API соответствуют именам свойств в типе объекта CLR. Например,
свойство Author класса Book сериализуется как Author , а не author .
2. В Models/Book.cs добавьте к свойству BookName атрибут [JsonPropertyName]:
C#
[BsonElement("Name")]
[JsonPropertyName("Name")]
public string BookName { get; set; } = null!;
Атрибут [JsonPropertyName] со значением Name представляет имя свойства в
сериализованном ответе веб-API в формате JSON.
3. Добавьте следующий код в самое начало файла Models/Book.cs , чтобы
разрешить ссылку на атрибут [JsonProperty] :
C#
using System.Text.Json.Serialization;
4. Повторите действия, описанные в разделе Тестирование веб-API. Обратите
внимание на различие в именах свойств JSON.
Добавление поддержки аутентификации в
веб-API
ASP.NET Core Identity позволяет использовать функцию входа в пользовательском
интерфейсе для веб-приложений ASP.NET Core. Чтобы защитить веб-API и
одностраничные приложения, используйте один из следующих способов:
Azure Active Directory
Azure Active Directory B2C (Azure AD B2C)
Duende Identity Server
Duende Identity Server — это платформа OpenID Connect и OAuth 2.0 для ASP.NET
Core. Duende Identity Server включает следующие функции безопасности:
Проверка подлинности как услуга (AaaS)
Единый вход (SSO) для нескольких типов приложений
Контроль доступа для API
Шлюз федерации
) Важно!
Компания Duende Software
может потребовать лицензионный сбор за
использование Duende IdentityServer в рабочей среде. Дополнительные
сведения см. в статье Миграция с ASP.NET Core 5.0 на 6.0.
Дополнительные сведения см. в документации по Duende Identity Server (на вебсайте ПО Duende) .
Дополнительные ресурсы
Просмотреть или скачать образец кода
(как скачивать)
Создание веб-API с помощью ASP.NET Core
Типы возвращаемых действий контроллера в веб-API ASP.NET Core
Создание веб-API с помощью ASP.NET Core
Учебник. Вызов веб-API ASP.NET Core
с помощью JavaScript
Статья • 28.01.2023 • Чтение занимает 10 мин
Автор: Рик Андерсон
(Rick Anderson)
В этом руководстве описано, как вызвать веб-API ASP.NET Core с помощью
JavaScript и Fetch API
.
Предварительные требования
Изучите Учебник. Создание веб-API.
Опыт работы с CSS, HTML и JavaScript.
Вызов веб-API с помощью JavaScript
В этом разделе описано, как добавить HTML-страницу, содержащую формы для
создания и администрирования элементов списка задач. Обработчики событий
присоединяются к элементам на странице. При использовании обработчиков
событий создаются запросы HTTP к методам действия веб-API. Функция Fetch API
fetch инициирует каждый такой запрос HTTP.
Функция fetch возвращает объект Promise , который содержит ответ HTTP,
представленный в виде объекта Response . Распространенным подходом является
извлечение текста ответа JSON путем вызова функции json для объекта Response .
JavaScript изменяет страницу, используя сведения из ответа API.
Самый простой вызов fetch принимает один параметр, представляющий маршрут.
Второй параметр (объект init ) является необязательным. init используется для
настройки запроса HTTP.
1. Настройте в приложении обслуживание статических файлов и включите
сопоставление файлов по умолчанию. Вставьте в Program.cs следующий
выделенный код:
C#
using Microsoft.EntityFrameworkCore;
using TodoApi.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
var app = builder.Build();
if (builder.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
1. Создайте папку wwwroot в корневом каталоге проекта.
2. Создайте папку css в папке wwwroot.
3. Создайте папку js в папке wwwroot.
4. Добавьте HTML-файл index.html в папку wwwroot. Замените содержимое
файла index.html следующей разметкой:
HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>To-do CRUD</title>
<link rel="stylesheet" href="css/site.css" />
</head>
<body>
<h1>To-do CRUD</h1>
<h3>Add</h3>
<form action="javascript:void(0);" method="POST"
onsubmit="addItem()">
<input type="text" id="add-name" placeholder="New to-do">
<input type="submit" value="Add">
</form>
<div id="editForm">
<h3>Edit</h3>
<form action="javascript:void(0);" onsubmit="updateItem()">
<input type="hidden" id="edit-id">
<input type="checkbox" id="edit-isComplete">
<input type="text" id="edit-name">
<input type="submit" value="Save">
<a onclick="closeInput()" aria-label="Close">✖</a>
</form>
</div>
<p id="counter"></p>
<table>
<tr>
<th>Is Complete?</th>
<th>Name</th>
<th></th>
<th></th>
</tr>
<tbody id="todos"></tbody>
</table>
<script src="js/site.js" asp-append-version="true"></script>
<script type="text/javascript">
getItems();
</script>
</body>
</html>
5. Добавьте CSS-файл с именем site.css в папку wwwroot/css. Замените
содержимое файла site.css следующими стилями:
css
input[type='submit'], button, [aria-label] {
cursor: pointer;
}
#editForm {
display: none;
}
table {
font-family: Arial, sans-serif;
border: 1px solid;
border-collapse: collapse;
}
th {
background-color: #f8f8f8;
padding: 5px;
}
td {
border: 1px solid;
padding: 5px;
}
6. Добавьте файл JavaScript с именем site.js в папку wwwroot/js. Замените все
содержимое site.js следующим кодом:
JavaScript
const uri = 'api/todoitems';
let todos = [];
function getItems() {
fetch(uri)
.then(response => response.json())
.then(data => _displayItems(data))
.catch(error => console.error('Unable to get items.', error));
}
function addItem() {
const addNameTextbox = document.getElementById('add-name');
const item = {
isComplete: false,
name: addNameTextbox.value.trim()
};
fetch(uri, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(item)
})
.then(response => response.json())
.then(() => {
getItems();
addNameTextbox.value = '';
})
.catch(error => console.error('Unable to add item.', error));
}
function deleteItem(id) {
fetch(`${uri}/${id}`, {
method: 'DELETE'
})
.then(() => getItems())
.catch(error => console.error('Unable to delete item.', error));
}
function displayEditForm(id) {
const item = todos.find(item => item.id === id);
document.getElementById('edit-name').value = item.name;
document.getElementById('edit-id').value = item.id;
document.getElementById('edit-isComplete').checked = item.isComplete;
document.getElementById('editForm').style.display = 'block';
}
function updateItem() {
const itemId = document.getElementById('edit-id').value;
const item = {
id: parseInt(itemId, 10),
isComplete: document.getElementById('edit-isComplete').checked,
name: document.getElementById('edit-name').value.trim()
};
fetch(`${uri}/${itemId}`, {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(item)
})
.then(() => getItems())
.catch(error => console.error('Unable to update item.', error));
closeInput();
return false;
}
function closeInput() {
document.getElementById('editForm').style.display = 'none';
}
function _displayCount(itemCount) {
const name = (itemCount === 1) ? 'to-do' : 'to-dos';
document.getElementById('counter').innerText = `${itemCount}
${name}`;
}
function _displayItems(data) {
const tBody = document.getElementById('todos');
tBody.innerHTML = '';
_displayCount(data.length);
const button = document.createElement('button');
data.forEach(item => {
let isCompleteCheckbox = document.createElement('input');
isCompleteCheckbox.type = 'checkbox';
isCompleteCheckbox.disabled = true;
isCompleteCheckbox.checked = item.isComplete;
let editButton = button.cloneNode(false);
editButton.innerText = 'Edit';
editButton.setAttribute('onclick', `displayEditForm(${item.id})`);
let deleteButton = button.cloneNode(false);
deleteButton.innerText = 'Delete';
deleteButton.setAttribute('onclick', `deleteItem(${item.id})`);
let tr = tBody.insertRow();
let td1 = tr.insertCell(0);
td1.appendChild(isCompleteCheckbox);
let td2 = tr.insertCell(1);
let textNode = document.createTextNode(item.name);
td2.appendChild(textNode);
let td3 = tr.insertCell(2);
td3.appendChild(editButton);
let td4 = tr.insertCell(3);
td4.appendChild(deleteButton);
});
todos = data;
}
Может потребоваться изменение параметров запуска проекта ASP.NET Core для
локального тестирования HTML-страницы:
1. Откройте файл Properties\launchSettings.json.
2. Удалите свойство launchUrl , чтобы приложение открылось через index.html
— файл проекта по умолчанию.
В этом примере вызываются все методы CRUD в веб-API. Ниже приводится
пояснение запросов веб-API.
Получение списка элементов задач
В следующем коде HTTP-запрос GET направляется по пути api/todoitems:
JavaScript
fetch(uri)
.then(response => response.json())
.then(data => _displayItems(data))
.catch(error => console.error('Unable to get items.', error));
Когда веб-API возвращает код состояния, указывающий на успешное выполнение,
вызывается функция _displayItems . Каждый элемент списка задач в параметре
массива, который принимается _displayItems , добавляется в таблицу с помощью
кнопок Изменить и Удалить. Если запрос веб-API завершается сбоем, в консоли
браузера регистрируется сообщение об ошибке.
Добавление элемента задачи
В приведенном ниже коде выполняется следующее:
Переменная item объявляется для создания представления объектного
литерала элемента списка задач.
Для запроса Fetch настраиваются следующие параметры:
method определяет команду действия HTTP POST.
body определяет текст запроса в представлении JSON. Код JSON создается
путем передачи литерала объекта, хранящегося в item , в функцию
JSON.stringify .
headers определяет заголовки Accept и Content-Type запросов HTTP. Для
обеих параметров устанавливается значение application/json , чтобы
классифицировать тип носителя при получении и отправке соответственно.
HTTP-запрос POST направляется по пути api/todoitems.
JavaScript
function addItem() {
const addNameTextbox = document.getElementById('add-name');
const item = {
isComplete: false,
name: addNameTextbox.value.trim()
};
fetch(uri, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(item)
})
.then(response => response.json())
.then(() => {
getItems();
addNameTextbox.value = '';
})
.catch(error => console.error('Unable to add item.', error));
}
Когда веб-API возвращает код состояния, указывающий на успешное выполнение,
вызывается функция getItems для обновления таблицы HTML. Если запрос веб-API
завершается сбоем, в консоли браузера регистрируется сообщение об ошибке.
Обновление элемента задачи
Обновление элемента списка задач аналогично его добавлению. Но есть два
существенных отличия:
Путь имеет суффикс с уникальным идентификатором обновляемого элемента.
Например, api/todoitems/1.
Команда действия HTTP — это PUT, как указано в параметре method .
JavaScript
fetch(`${uri}/${itemId}`, {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(item)
})
.then(() => getItems())
.catch(error => console.error('Unable to update item.', error));
Удаление элемента задачи
Чтобы удалить элемент списка задач, укажите для параметра запроса method
значение DELETE и определите уникальный идентификатор элемента в URL-адресе.
JavaScript
fetch(`${uri}/${id}`, {
method: 'DELETE'
})
.then(() => getItems())
.catch(error => console.error('Unable to delete item.', error));
Перейдите к следующему руководству, в котором описано, как создавать страницы
справки по веб-API:
Начало работы с Swashbuckle и ASP.NET Core
Создание внутренних служб для
собственных мобильных приложений
в ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 7 мин
Джеймс Монтемагно
Мобильные приложения могут взаимодействовать с внутренними службами
ASP.NET Core. Инструкции по подключению локальных веб-служб из симуляторов
iOS и эмуляторов Android см. в статье о подключении к локальным веб-службам из
симуляторов iOS и эмуляторов Android.
Просмотреть или скачать пример кода внутренней службы
Пример собственного мобильного
приложения
В этом руководстве показано, как создавать внутренние службы с помощью
ASP.NET Core для поддержки собственных мобильных приложений. Он использует
приложение TodoRest Xamarin.Forms в качестве собственного клиента, которое
включает отдельные собственные клиенты для Android, iOS и Windows. Вы можете
следовать приложенному руководству, чтобы создать собственное приложение (и
установить необходимые бесплатные средства Xamarin), а также скачать пример
решения Xamarin. Пример Xamarin включает проект служб веб-API ASP.NET Core,
который ASP.NET Core приложение этой статьи заменяет (без изменений,
необходимых клиенту).
Компоненты
Приложение TodoREST
поддерживает перечисление, добавление, удаление и
обновление To-Do элементов. Каждый элемент имеет идентификатор, имя, заметки
и свойство, указывающее, выполнен ли он.
Основное представление элементов, как показано выше, выводит список с именем
каждого элемента, указывая, выполнен ли он, с помощью флажка.
Если выбрать значок + , открывается диалоговое окно добавления элемента:
При выборе элемента на экране основного списка открывается диалоговое окно
редактирования, где можно изменить параметры имени, заметок и готовности
элемента, а также удалить его:
Чтобы протестировать его самостоятельно с помощью приложения ASP.NET Core,
созданного в следующем разделе, работающем на компьютере, обновите
константу RestUrl
приложения.
Эмуляторы Android не выполняются на локальном компьютере и используют IPадрес замыкания на себя (10.0.2.2) для взаимодействия с локальным компьютером.
Используйте Xamarin.Essentials DeviceInfo , чтобы определить, какая операционная
система работает, чтобы использовать правильный URL-адрес.
Перейдите к TodoREST
проекту и откройте Constants.cs
Constants.cs содержит следующую конфигурацию.
файл. Файл
C#
using Xamarin.Essentials;
using Xamarin.Forms;
namespace TodoREST
{
public static class Constants
{
// URL of REST service
//public static string RestUrl =
"https://YOURPROJECT.azurewebsites.net:8081/api/todoitems/{0}";
// URL of REST service (Android does not use localhost)
// Use http cleartext for local deployment. Change to https for
production
public static string RestUrl = DeviceInfo.Platform ==
DevicePlatform.Android ? "http://10.0.2.2:5000/api/todoitems/{0}" :
"http://localhost:5000/api/todoitems/{0}";
}
}
При необходимости можно развернуть веб-службу в облачной службе, такой как
Azure, и обновить . RestUrl
Создание проекта ASP.NET Core
Создайте веб-приложение ASP.NET Core в Visual Studio. Выберите шаблон веб-API.
Назовите проект TodoAPI.
Приложение должно отвечать на все запросы, сделанные на порт 5000, включая
трафик HTTP для нашего мобильного клиента. Обновите Startup.cs так, чтобы
UseHttpsRedirection не выполнялось в разработке:
C#
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// For mobile apps, allow http traffic.
app.UseHttpsRedirection();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
7 Примечание
Запустите приложение напрямую, а не за IIS Express. IIS Express игнорирует не
локальные запросы по умолчанию. Запустите dotnet run из командной строки
или выберите профиль имени приложения в раскрывающемся списке
"Целевой объект отладки" на панели инструментов Visual Studio.
Добавьте класс модели для представления элементов задач. Пометьте
обязательные поля с помощью атрибута [Required] :
C#
using System.ComponentModel.DataAnnotations;
namespace TodoAPI.Models
{
public class TodoItem
{
[Required]
public string ID { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Notes { get; set; }
public bool Done { get; set; }
}
}
Методам API нужен определенный способ для работы с данными. Используйте тот
же интерфейс ITodoRepository , который использует исходный пример Xamarin:
C#
using System.Collections.Generic;
using TodoAPI.Models;
namespace TodoAPI.Interfaces
{
public interface ITodoRepository
{
bool DoesItemExist(string id);
IEnumerable<TodoItem> All { get; }
TodoItem Find(string id);
void Insert(TodoItem item);
void Update(TodoItem item);
void Delete(string id);
}
}
В этом примере реализация использует просто частную коллекцию элементов:
C#
using
using
using
using
System.Collections.Generic;
System.Linq;
TodoAPI.Interfaces;
TodoAPI.Models;
namespace TodoAPI.Services
{
public class TodoRepository : ITodoRepository
{
private List<TodoItem> _todoList;
public TodoRepository()
{
InitializeData();
}
public IEnumerable<TodoItem> All
{
get { return _todoList; }
}
public bool DoesItemExist(string id)
{
return _todoList.Any(item => item.ID == id);
}
public TodoItem Find(string id)
{
return _todoList.FirstOrDefault(item => item.ID == id);
}
public void Insert(TodoItem item)
{
_todoList.Add(item);
}
public void Update(TodoItem item)
{
var todoItem = this.Find(item.ID);
var index = _todoList.IndexOf(todoItem);
_todoList.RemoveAt(index);
_todoList.Insert(index, item);
}
public void Delete(string id)
{
_todoList.Remove(this.Find(id));
}
private void InitializeData()
{
_todoList = new List<TodoItem>();
var todoItem1 = new TodoItem
{
ID = "6bb8a868-dba1-4f1a-93b7-24ebce87e243",
Name = "Learn app development",
Notes = "Take Microsoft Learn Courses",
Done = true
};
var todoItem2 = new TodoItem
{
ID = "b94afb54-a1cb-4313-8af3-b7511551b33b",
Name = "Develop apps",
Notes = "Use Visual Studio and Visual Studio for Mac",
Done = false
};
var todoItem3 = new TodoItem
{
ID = "ecfa6f80-3671-4911-aabe-63cc442c1ecf",
Name = "Publish apps",
Notes = "All app stores",
Done = false,
};
_todoList.Add(todoItem1);
_todoList.Add(todoItem2);
_todoList.Add(todoItem3);
}
}
}
Настройка реализации в Startup.cs :
C#
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ITodoRepository, TodoRepository>();
services.AddControllers();
}
Создание контроллера
Добавьте новый контроллер в проект TodoItemsController
. Он должен
наследовать от ControllerBase. Добавьте атрибут Route , чтобы указать, что
контроллер будет обрабатывать запросы, выполняемые по путям, начинающимся с
api/todoitems . Токен [controller] в маршруте заменяется на имя контроллера
(суффикс Controller опускается) и особенно удобен для глобальных маршрутов.
Узнайте больше о маршрутизации.
Для работы контроллеру нужен ITodoRepository ; запросите экземпляр этого типа
через конструктор контроллера. Во время выполнения этот экземпляр будет
предоставляться с помощью поддержки внедрения зависимостей на платформе.
C#
[ApiController]
[Route("api/[controller]")]
public class TodoItemsController : ControllerBase
{
private readonly ITodoRepository _todoRepository;
public TodoItemsController(ITodoRepository todoRepository)
{
_todoRepository = todoRepository;
}
Этот API поддерживает четыре различных HTTP-команды для выполнения
операций CRUD (создание, чтение, обновление, удаление) с источником данных.
Самой простой из них является операция чтения, которая соответствует HTTPзапросу GET.
Чтение элементов
Запрос списка элементов, выполняется с помощью отправки запроса GET в метод
List . Атрибут [HttpGet] в методе List указывает, что это действие должно
обрабатывать только запросы GET. Маршрут для этого действия соответствует
маршруту, указанному на контроллере. Использовать имя действия в составе
маршрута необязательно. Нужно лишь убедиться, что каждое действие имеет
уникальный и однозначный маршрут. Атрибуты маршрутизации можно применять
на уровне как контроллера, так и метода для создания определенных маршрутов.
C#
[HttpGet]
public IActionResult List()
{
return Ok(_todoRepository.All);
}
Метод List возвращает код отклика 200 OK и все элементы Todo, сериализованные
как JSON.
Вы можете проверить новый метод API с помощью различных средств, таких как
Postman , как показано ниже:
Создание элементов
По соглашению создание элементов данных сопоставляется с HTTP-командой
POST. Метод Create имеет примененный к нему атрибут [HttpPost] и принимает
экземпляр TodoItem . Так как аргумент item передается в текст POST, этот параметр
указывает атрибут [FromBody] .
Внутри метода элемент проверяется на допустимость и предшествующее
существование в хранилище данных, а при отсутствии проблем он добавляется с
помощью репозитория. Проверка ModelState.IsValid приводит к проверке модели
и должна выполняться в каждом методе API, принимающем вводимые
пользователем данные.
C#
[HttpPost]
public IActionResult Create([FromBody]TodoItem item)
{
try
{
if (item == null || !ModelState.IsValid)
{
return
BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
}
bool itemExists = _todoRepository.DoesItemExist(item.ID);
if (itemExists)
{
return StatusCode(StatusCodes.Status409Conflict,
ErrorCode.TodoItemIDInUse.ToString());
}
_todoRepository.Insert(item);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotCreateItem.ToString());
}
return Ok(item);
}
В примере используется enum содержащий коды ошибок, передаваемые
мобильному клиенту:
C#
public enum ErrorCode
{
TodoItemNameAndNotesRequired,
TodoItemIDInUse,
RecordNotFound,
CouldNotCreateItem,
CouldNotUpdateItem,
CouldNotDeleteItem
}
Протестируйте добавление новых элементов с помощью Postman, выбрав команду
POST, предоставляющую новый объект в JSформате ON в тексте запроса. Вам
также нужно добавить заголовок запроса, указав Content-Type типа
application/json .
Метод возвращает вновь созданный элемент в отклике.
Обновление элементов
Изменение записей осуществляется с помощью HTTP-запросов PUT. За
исключением этого изменения, метод Edit практически идентичен Create .
Обратите внимание, что если запись не найдена, то действие Edit возвратит
отклик NotFound (404).
C#
[HttpPut]
public IActionResult Edit([FromBody] TodoItem item)
{
try
{
if (item == null || !ModelState.IsValid)
{
return
BadRequest(ErrorCode.TodoItemNameAndNotesRequired.ToString());
}
var existingItem = _todoRepository.Find(item.ID);
if (existingItem == null)
{
return NotFound(ErrorCode.RecordNotFound.ToString());
}
_todoRepository.Update(item);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotUpdateItem.ToString());
}
return NoContent();
}
Чтобы выполнить проверку с помощью Postman, измените команду на PUT.
Укажите обновленные данные объекта в тексте запроса.
Этот метод возвращает отклик NoContent (204) при успешном выполнении, чтобы
обеспечить согласованность с ранее существовавшими API.
Удаление элементов
Удаление записей сопровождается отправкой запросов DELETE в службу и
передачей идентификатора удаляемого элемента. Как и в случае с обновлениями,
запросы несуществующих элементов будут получать отклики NotFound . В
противном случае успешный запрос получит отклик NoContent (204).
C#
[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
try
{
var item = _todoRepository.Find(id);
if (item == null)
{
return NotFound(ErrorCode.RecordNotFound.ToString());
}
_todoRepository.Delete(id);
}
catch (Exception)
{
return BadRequest(ErrorCode.CouldNotDeleteItem.ToString());
}
return NoContent();
}
Обратите внимание, что при тестировании функции удаления в тексте запроса не
требуется никаких элементов.
Предотвращение избыточной публикации
В настоящее время пример приложения предоставляет весь объект TodoItem .
Рабочие приложения обычно ограничивают вводимые данные и возвращают их с
помощью подмножества модели. Это связано с несколькими причинами, и
безопасность является основной. Подмножество модели обычно называется
объектом передачи данных (DTO), моделью ввода или моделью представления. В
этой статье используется DTO.
DTO можно использовать для следующего:
Предотвращение избыточной публикации.
Скрытие свойств, которые не предназначены для просмотра клиентами.
Пропуск некоторых свойств, чтобы уменьшить размер полезной нагрузки.
Сведение графов объектов, содержащих вложенные объекты. Сведенные
графы объектов могут быть удобнее для клиентов.
Чтобы продемонстрировать подход DTO, см. раздел "Предотвращение чрезмерной
публикации"
Общие соглашения веб-API
При разработке внутренних служб для своего приложения вам потребуется
согласованный набор соглашений или политик для обработки сквозных задач.
Например, в приведенной выше службе на запросы запрашивает определенные
записи, которые не были найдены, и был получен отклик NotFound , а не BadRequest .
Аналогичным образом, команды, выполненные для этой службы, которые
передавались в привязанные типы модели, всегда проверяли ModelState.IsValid и
возвращали BadRequest для недопустимых типов модели.
Определив общую политику для своих API, вы обычно можете инкапсулировать ее
в фильтр. Дополнительные сведения о том, как инкапсулировать общие политики
API в приложения ASP.NET Core MVC.
Дополнительные ресурсы
Xamarin.Forms: проверка подлинности веб-службы
Xamarin.Forms: использование RESTful веб-службы
Использование REST веб-служб в Xamarin Apps
Создание веб-API с помощью ASP.NET Core
Публикация веб-API ASP.NET Core в
службе управления API Azure с
помощью Visual Studio
Статья • 28.01.2023 • Чтение занимает 3 мин
Автор: Мэтт Сукуп
(Matt Soucoup)
В этом руководстве вы узнаете, как создать проект веб-API ASP.NET Core с
помощью Visual Studio, обеспечить поддержку OpenAPI, а затем опубликовать вебAPI как в Служба приложений Azure, так и в Azure Управление API.
Настройка
Для работы с этим руководством вам потребуется учетная запись Azure.
Создайте бесплатную учетную запись Azure
, если у вас ее нет.
Создание веб-API ASP.NET Core
Visual Studio позволяет легко создать проект веб-API ASP.NET Core на основе
шаблона. Следуйте этим инструкциям, чтобы создать проект веб-API ASP.NET Core:
В меню Файл выберите Создать >Проект.
В поле поиска введите Веб-API.
Выберите шаблон Веб-API ASP.NET Core и нажмите кнопку Далее.
В диалоговом окне Настройка нового проекта присвойте проекту имя
WeatherAPI и нажмите кнопку Далее.
В диалоговом окне Дополнительные сведения выполните следующие
действия.
Убедитесь, что для параметра Платформа выбрано значение .NET 6.0
(долгосрочная поддержка).
Убедитесь, что установлен флажок Использовать контроллеры (снимите
флажок для использования минимальных API).
Убедитесь, что установлен флажок Включить поддержку OpenAPI .
Нажмите кнопку создания.
Обзор кода
Определения Swagger позволяют Управление API Azure считывать определения API
приложения. Установив флажок Включить поддержку OpenAPI во время создания
приложения, Visual Studio автоматически добавляет код для создания определений
Swagger. Откройте файл со Program.cs следующим кодом:
C#
...
builder.Services.AddSwaggerGen();
...
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
...
Убедитесь, что определения Swagger всегда создаются
Azure Управление API требует, чтобы определения Swagger всегда присутствовали
независимо от среды приложения. Чтобы убедиться, что они всегда создаются,
переместите app.UseSwagger(); за пределы if (app.Environment.IsDevelopment())
блока.
Обновленный код выглядит следующим образом:
C#
...
app.UseSwagger();
if (app.Environment.IsDevelopment())
{
app.UseSwaggerUI();
}
...
Изменение маршрутизации API
Измените структуру URL-адреса, необходимую Get для доступа к действию
WeatherForecastController . Выполните следующие шаги:
1. Откройте файл WeatherForecastController.cs .
2. Замените [Route("[controller]")] атрибут уровня класса на [Route("/")] .
Обновленное определение класса :
C#
[ApiController]
[Route("/")]
public class WeatherForecastController : ControllerBase
Публикация веб-API в Службе приложений
Azure
Выполните следующие действия, чтобы опубликовать веб-API ASP.NET Core в
службе управления API Azure.
1. Публикация приложения API в Службе приложений Azure
2. Опубликуйте приложение веб-API ASP.NET Core в экземпляре службы
управления API Azure.
Публикация приложения API в Службе приложений
Azure
Выполните следующие действия, чтобы опубликовать веб-API ASP.NET Core в
службе управления API Azure.
1. Щелкните правой кнопкой мыши проект в обозревателе решений и
выберите пункт Опубликовать.
2. В диалоговом окне Публикация выберите Azure и нажмите кнопку Далее .
3. Выберите Служба приложений Azure (Windows) и нажмите кнопку Далее.
4. Выберите Создание новой службы приложений Azure.
Откроется диалоговое окно Создать службу приложений. Заполняются поля
ввода Имя приложения, Группа ресурсов и План службы приложений. Вы
можете сохранить эти имена или изменить их.
5. Нажмите кнопку Создать.
6. После создания службы приложений нажмите кнопку Далее .
7. Выберите Создать новую службу Управление API.
Откроется диалоговое окно Создание службы Управление API. Можно
оставить поля Имя API, Имя подписки и Группа ресурсов . Нажмите кнопку
создать рядом с записью Управление API Service (Служба Управление API) и
введите необходимые поля в этом диалоговом окне.
Нажмите кнопку ОК, чтобы создать службу Управление API.
8. Нажмите кнопку Создать, чтобы продолжить создание службы Управление
API. Этот шаг может занять несколько минут.
9. По завершении нажмите кнопку Готово .
10. Диалоговое окно закроется, а затем появится экран сводки со сведениями о
публикации. Нажмите кнопку Опубликовать.
Веб-API публикуется как в Служба приложений Azure, так и в azure
Управление API. Появится новое окно браузера, в котором будет показан API,
работающий в службе приложений Azure. Это окно можно закрыть.
11. Откройте портал Azure в веб-браузере и перейдите к созданному экземпляру
Управление API.
12. Выберите параметр API в меню слева.
13. Выберите API, созданный на предыдущих шагах. Теперь он заполнен, и вы
можете изучить его.
Настройка имени опубликованного API
Обратите внимание, что имя API называется WeatherAPI; тем не менее, мы хотели
бы назвать это прогнозы погоды. Чтобы обновить имя, выполните следующие
действия.
1. Добавьте следующий код в сразу Program.cs после servies.AddSwaggerGen();
C#
builder.Services.ConfigureSwaggerGen(setup =>
{
setup.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Title = "Weather Forecasts",
Version = "v1"
});
});
2. Повторно опубликуйте веб-API ASP.NET Core и откройте экземпляр службы
управления API Azure на портале Azure.
3. Обновите страницу в браузере. Теперь отобразится правильное имя API.
Проверка работоспособности веб-API
Вы можете протестировать развернутый веб-API ASP.NET Core в службе
управления API Azure на портале Azure, выполнив следующие действия.
1. Откройте вкладку Тест.
2. Выберите / или операцию Get.
3. Нажмите кнопку Отправить.
Очистка
Завершив тестирование приложения, перейдите на портал Azure
и удалите
приложение.
1. Выберите пункт Группы ресурсов, а затем созданную группу ресурсов.
2. На странице Группы ресурсов выберите Удалить.
3. Введите имя группы ресурсов и выберите Удалить. Ваше приложение и все
ресурсы, созданные при работе с этим руководством, удалены из Azure.
Дополнительные ресурсы
Управление API Azure
Служба приложений Azure
Учебник. Начало работы с SignalR
ASP.NET Core
Статья • 02.12.2022 • Чтение занимает 12 мин
В этом учебнике описаны основы создания приложения, работающего в режиме
реального времени, с помощью SignalR. Вы научитесь:
" Создайте веб-проект.
" добавлять клиентскую библиотеку SignalR.
" создавать концентратор SignalR.
" настраивать проект для использования SignalR;
" Добавлять код для отправки сообщений из любого клиента всем
подключенным клиентам.
В итоге вы получите работающее приложение чата:
Предварительные требования
Visual Studio
Visual Studio 2022
с рабочей нагрузкой ASP.NET и веб-разработка.
Создание проекта веб-приложения
Visual Studio
1. Запустите Visual Studio 2022 и нажмите Создать проект.
2. В диалоговом окне Создать проект выберите Веб-приложение
ASP.NET Core и нажмите Далее.
3. В диалоговом окне Настроить новый проект введите SignalRChat в поле
Имя проекта. Важно присвоить проекту имя SignalRChat, включая
сопоставление регистра букв, чтобы обеспечить соответствие
пространств имен при копировании и вставке примера кода.
4. Выберите Далее.
5. В диалоговом окне Дополнительные сведения выберите .NET 6.0
(долгосрочная поддержка) и щелкните Создать.
Добавление клиентской библиотеки SignalR
Серверная библиотека SignalR входит в состав общей платформы ASP.NET Core.
Клиентская библиотека JavaScript не добавляется в проект автоматически. В этом
руководстве показано, как использовать диспетчер библиотек (LibMan), чтобы
получить клиентскую библиотеку из unpkg
. unpkg это быстрая глобальная сеть
доставки содержимого для всего в npm .
Visual Studio
В обозревателе решений щелкните проект правой кнопкой мыши и
выберите Добавить>Client-Side Library (Клиентская библиотека).
В диалоговом окне Добавление библиотеки на стороне клиента:
Выберите unpkg для параметра Поставщик.
Введите @microsoft/signalr@latest для параметра Библиотека.
Щелкните Choose specific files (Выбрать определенные файлы),
разверните папку dist/browser и выберите signalr.js и signalr.min.js .
В поле Целевое расположение укажите wwwroot/js/signalr/.
Щелкните Установить.
LibMan создает папку wwwroot/js/signalr и копирует в нее выбранные файлы.
Создание концентратора SignalR
hub — это класс, который служит в качестве конвейера высокого уровня для
обработки взаимодействия между клиентом и сервером.
В папке проекта SignalRChat создайте папку Hubs.
В папке Hubs создайте класс ChatHub , содержащий следующий код:
C#
using Microsoft.AspNetCore.SignalR;
namespace SignalRChat.Hubs
{
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}
Класс ChatHub наследуется от класса SignalRHub. Класс Hub управляет
подключениями, группами и обменом сообщениями.
Метод SendMessage может вызываться подключенным клиентом, чтобы отправить
сообщение всем клиентам. Далее в этом учебника показан клиентский код
JavaScript, который вызывает метод. Код SignalR является асинхронным, поэтому
обеспечивает максимальную масштабируемость.
Настройка SignalR
Сервер SignalR должен быть настроен для передачи запросов SignalR к SignalR.
Добавьте следующий выделенный код в файл Program.cs :
C#
using SignalRChat.Hubs;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSignalR();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.MapHub<ChatHub>("/chatHub");
app.Run();
Выделенный выше код добавляет SignalR к системам маршрутизации и внедрения
зависимостей ASP.NET Core.
Добавление клиентского кода SignalR
Замените все содержимое в Pages/Index.cshtml следующим кодом:
CSHTML
@page
<div class="container">
<div class="row"> </div>
<div class="row">
<div class="col-2">User</div>
<div class="col-4"><input type="text" id="userInput" />
</div>
</div>
<div class="row">
<div class="col-2">Message</div>
<div class="col-4"><input type="text" id="messageInput" />
</div>
</div>
<div class="row"> </div>
<div class="row">
<div class="col-6">
<input type="button" id="sendButton" value="Send
Message" />
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<hr />
</div>
</div>
<div class="row">
<div class="col-6">
<ul id="messagesList"></ul>
</div>
</div>
<script src="~/js/signalr/dist/browser/signalr.js"></script>
<script src="~/js/chat.js"></script>
Предыдущая разметка:
Создает текстовые поля и кнопку отправки.
Создает список с id="messagesList" для отображения сообщений,
полученных от концентратора SignalR.
Содержит ссылки на скрипты для SignalR и код приложения chat.js ,
который создается на следующем шаге.
В папке wwwroot/js создайте файл chat.js со следующим кодом:
JavaScript
"use strict";
var connection = new
signalR.HubConnectionBuilder().withUrl("/chatHub").build();
//Disable the send button until connection is established.
document.getElementById("sendButton").disabled = true;
connection.on("ReceiveMessage", function (user, message) {
var li = document.createElement("li");
document.getElementById("messagesList").appendChild(li);
// We can assign user-supplied strings to an element's textContent
because it
// is not interpreted as markup. If you're assigning in any other
way, you
// should be aware of possible script injection concerns.
li.textContent = `${user} says ${message}`;
});
connection.start().then(function () {
document.getElementById("sendButton").disabled = false;
}).catch(function (err) {
return console.error(err.toString());
});
document.getElementById("sendButton").addEventListener("click",
function (event) {
var user = document.getElementById("userInput").value;
var message = document.getElementById("messageInput").value;
connection.invoke("SendMessage", user, message).catch(function
(err) {
return console.error(err.toString());
});
event.preventDefault();
});
Предыдущий код JavaScript:
Создает и запускает подключение.
Добавляет к кнопке отправки обработчик, который отправляет сообщения
в концентратор.
Добавляет к объекту подключения обработчик, который получает
сообщения из концентратора и добавляет их в список.
Запуск приложения
Visual Studio
Нажмите клавиши CTRL+F5, чтобы запустить приложение без отладки.
Скопируйте URL-адрес из адресной строки, откройте другой экземпляр или
вкладку браузера и вставьте URL-адрес в адресную строку.
Выберите любой браузер, введите имя и сообщение и нажмите кнопку
Отправить сообщение. Имя и сообщение отображаются на обеих страницах
мгновенно.
 Совет
Если приложение не работает, откройте средства разработчика для
браузера (F12) и перейдите в консоль. Вы можете увидеть ошибки,
связанные с вашим кодом HTML и JavaScript. Предположим, вы
поместили signalr.js не в ту папку, которую указали. В этом случае
ссылка на этот файл не будет работать, и вы увидите сообщение об
ошибке 404 в консоли.
Если возникает ошибка ERR_SPDY_INADEQUATE_TRANSPORT_SECURITY в
Chrome, выполните эти команды, чтобы обновить сертификат разработки:
Интерфейс командной строки.NET
dotnet dev-certs https --clean
dotnet dev-certs https --trust
Публикация в Azure
Сведения о развертывании в Azure см. в разделе Краткое руководство.
Развертывание веб-приложения ASP.NET.
Учебник. Начало работы с ASP.NET
Core SignalR с использованием
TypeScript и Webpack.
Статья • 28.01.2023 • Чтение занимает 29 мин
Авторы: Себастьен Сунье (Sébastien Sougnez)
и Скотт Эдди (Scott Addie)
В этом учебнике демонстрируется использование средства Webpack
для
создания пакета и сборки веб-приложения ASP.NET Core SignalR, а также
объединения и создания клиента, который написан на языке TypeScript . С
помощью средства Webpack разработчики могут создавать пакеты и выполнять
сборку ресурсов на стороне клиента для веб-приложения.
В этом руководстве описано следующее:
" Создание приложения ASP.NET Core SignalR
" настроить сервер SignalR.
" Настроить конвейер сборки с использованием Webpack
" настроить клиент TypeScript SignalR;
" Включение обмена данными между клиентом и сервером.
Просмотреть или скачать образец кода
(как скачивать)
Предварительные требования
Node.js
с npm
Visual Studio
Visual Studio 2022
с рабочей нагрузкой ASP.NET и веб-разработка.
Создание веб-приложения ASP.NET Core
Visual Studio
По умолчанию Visual Studio использует версию npm, которая находится в его
каталоге установки. Настройка Visual Studio на поиск npm в переменной среды
PATH :
1. Запустите Visual Studio. В начальном окне выберите Продолжить без
кода.
2. Щелкните Сервис>Параметры>Проекты и решения>Управление вебпакетами>Внешние веб-инструменты.
3. Выберите элемент $(PATH) в списке. Щелкните стрелку вверх, чтобы
переместить этот элемент на вторую позицию в списке, и нажмите OK.
Создание веб-приложения ASP.NET Core:
1. Перейдите в меню Файл>Создать>Проект и выберите шаблон Пустой
шаблон ASP.NET Core. Выберите Далее.
2. Задайте для проекта имя SignalRWebpack и нажмите кнопку Создать.
3. Выберите .NET 6.0 (Long-term support) в раскрывающемся меню
Платформа. Щелкните Создать.
Добавьте в проект пакет NuGet Microsoft.TypeScript.MSBuild .
1. В обозревателе решений щелкните правой кнопкой мыши узел проекта и
выберите Управление пакетами NuGet. На вкладке Обзор найдите
Microsoft.TypeScript.MSBuild , а затем нажмите кнопку Установить справа,
чтобы установить пакет.
Visual Studio добавляет пакет NuGet в узел Зависимости в обозревателе
решений, разрешая компиляцию TypeScript в проекте.
Настройка сервера
В этом разделе вы настроите веб-приложение ASP.NET Core для отправки и
получения сообщений SignalR.
1. В Program.cs вызовите AddSignalR.
C#
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSignalR();
2. В Program.cs снова вызовите UseDefaultFiles и UseStaticFiles:
C#
var app = builder.Build();
app.UseDefaultFiles();
app.UseStaticFiles();
Приведенный выше код позволяет серверу размещать и обслуживать файл
index.html . Файл обрабатывается независимо от того, вводит ли пользователь
полный или корневой URL-адрес веб-приложения.
3. Создайте новый каталог с именем Hubs в корневом каталоге проекта
SignalRWebpack/ для хранения класса концентратора SignalR.
4. Создайте файл Hubs/ChatHub.cs со следующим кодом:
C#
using Microsoft.AspNetCore.SignalR;
namespace SignalRWebpack.Hubs;
public class ChatHub : Hub
{
public async Task NewMessage(long username, string message) =>
await Clients.All.SendAsync("messageReceived", username,
message);
}
Приведенный выше код осуществляет широковещательную рассылку
полученных сервером сообщений всем подключенным пользователям.
Универсальный метод on для получения всех сообщений не требуется. Имя
метода указывается после суффиксов имени сообщения.
В этом примере клиент TypeScript отправляет сообщение,
идентифицированное как newMessage . Метод C# NewMessage ожидает данные,
отправленные клиентом. Выполняется вызов метода SendAsync для Clients.All.
Полученные сообщения отправляются всем клиентам, подключенным к
концентратору.
5. Добавьте следующий оператор using в самое начало Program.cs , чтобы
разрешить ссылку ChatHub :
C#
using SignalRWebpack.Hubs;
6. В Program.cs сопоставьте маршрут /hub с концентратором ChatHub . Замените
код, отображающий сообщение Hello World! , следующим кодом:
C#
app.MapHub<ChatHub>("/hub");
Настройка клиента
В этом разделе вы создадите проект Node.js
для преобразования TypeScript в
JavaScript и объединения ресурсов на стороне клиента, включая HTML и CSS, с
помощью Webpack.
1. В корневом элементе проекта выполните следующую команду, чтобы создать
файл package.json :
Консоль
npm init -y
2. Добавьте выделенное свойство в файл package.json и сохраните изменения:
JSON
{
"name": "SignalRWebpack",
"version": "1.0.0",
"private": true,
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Если свойству private присвоено значение true , на следующем шаге не будут
отображаться предупреждения об установке пакета.
3. Установите необходимые пакеты npm. Выполните следующую команду в
корневом элементе проекта:
Консоль
npm i -D -E clean-webpack-plugin css-loader html-webpack-plugin minicss-extract-plugin ts-loader typescript webpack webpack-cli
Использование параметра -E позволяет отключить установленное по
умолчанию поведение npm, предусматривающее запись операторов
диапазона семантического управления версиями
в файл package.json .
Например, "webpack": "5.70.0" используется вместо "webpack": "^5.70.0" .
Этот параметр позволяет исключить непреднамеренное обновление до более
новых версий пакета.
Дополнительные сведения см. в npm-install
.
4. Замените свойство scripts в файле package.json следующим кодом:
JSON
"scripts": {
"build": "webpack --mode=development --watch",
"release": "webpack --mode=production",
"publish": "npm run release && dotnet publish -c Release"
},
Определены следующие скрипты:
build . Создает пакет ваших ресурсов на стороне клиента в режиме
разработки и отслеживает изменения. Наблюдатель за файлами
выполняет повторное создание пакета при каждом изменении файла
проекта. Параметр mode позволяет отключить оптимизации в рабочей
среде, такие как встряхивание дерева и минификация. build
используется только в среде разработки.
release . Создает пакет ресурсов на стороне клиента в рабочем режиме.
publish . запускает скрипт release для создания пакета ресурсов на
стороне клиента в рабочем режиме. Этот скрипт вызывает команду
publish интерфейса командной строки .NET для публикации приложения.
5. Создайте в корневом элементе проекта файл webpack.config.js со следующим
кодом:
JavaScript
const
const
const
const
path = require("path");
HtmlWebpackPlugin = require("html-webpack-plugin");
{ CleanWebpackPlugin } = require("clean-webpack-plugin");
MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: "./src/index.ts",
output: {
path: path.resolve(__dirname, "wwwroot"),
filename: "[name].[chunkhash].js",
publicPath: "/",
},
resolve: {
extensions: [".js", ".ts"],
},
module: {
rules: [
{
test: /\.ts$/,
use: "ts-loader",
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
new MiniCssExtractPlugin({
filename: "css/[name].[chunkhash].css",
}),
],
};
Приведенный выше файл определяет конфигурацию процесса компиляции
Webpack.
Свойство output переопределяет значение по умолчанию для dist .
Вместо этого пакет выводится в каталог wwwroot .
Массив resolve.extensions содержит элемент .js для импорта кода
JavaScript клиента SignalR.
6. Скопируйте каталог src из примера проекта
в корневой каталог проекта.
Каталог src содержит следующие файлы:
index.html , который определяет стереотипную разметку главной
страницы.
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>ASP.NET Core SignalR with TypeScript and
Webpack</title>
</head>
<body>
<div id="divMessages" class="messages"></div>
<div class="input-zone">
<label id="lblMessage" for="tbMessage">Message:</label>
<input id="tbMessage" class="input-zone-input" type="text"
/>
<button id="btnSend">Send</button>
</div>
</body>
</html>
css/main.css , который предоставляет стили CSS для главной страницы:
css
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
}
.input-zone {
align-items: center;
display: flex;
flex-direction: row;
margin: 10px;
}
.input-zone-input {
flex: 1;
margin-right: 10px;
}
.message-author {
font-weight: bold;
}
.messages {
border: 1px solid #000;
margin: 10px;
max-height: 300px;
min-height: 300px;
overflow-y: auto;
padding: 5px;
}
tsconfig.json , который определяет конфигурацию компилятора
TypeScript для получения совместимого с ECMAScript
JSON
{
"compilerOptions": {
"target": "es5"
}
}
index.ts :
TypeScript
import * as signalR from "@microsoft/signalr";
import "./css/main.css";
const divMessages: HTMLDivElement =
document.querySelector("#divMessages");
5 кода JavaScript.
const tbMessage: HTMLInputElement =
document.querySelector("#tbMessage");
const btnSend: HTMLButtonElement =
document.querySelector("#btnSend");
const username = new Date().getTime();
const connection = new signalR.HubConnectionBuilder()
.withUrl("/hub")
.build();
connection.on("messageReceived", (username: string, message:
string) => {
const m = document.createElement("div");
m.innerHTML = `<div class="message-author">${username}</div>
<div>${message}</div>`;
divMessages.appendChild(m);
divMessages.scrollTop = divMessages.scrollHeight;
});
connection.start().catch((err) => document.write(err));
tbMessage.addEventListener("keyup", (e: KeyboardEvent) => {
if (e.key === "Enter") {
send();
}
});
btnSend.addEventListener("click", send);
function send() {
connection.send("newMessage", username, tbMessage.value)
.then(() => (tbMessage.value = ""));
}
Приведенный выше код извлекает ссылки на элементы модели DOM и
присоединяет два обработчика событий:
keyup : срабатывает, когда пользователь вводит данные в текстовое
поле tbMessage , и вызывает функцию send при нажатии
пользователем клавиши Enter.
click : срабатывает, когда пользователь нажимает кнопку Отправить и
вызывается функцию send .
Класс HubConnectionBuilder создает новый построитель для настройки
подключения к серверу. Функция withUrl настраивает URL-адрес
концентратора.
SignalR обеспечивает обмен сообщениями между клиентом и сервером.
Каждому сообщению присваивается определенное имя. Например,
сообщения с именем messageReceived могут выполнять логику
отображения новых сообщений в соответствующей зоне.
Прослушивание определенных сообщений реализуется с помощью
функции on . Возможно прослушивание любого числа имен сообщений.
Кроме того, можно передавать параметры сообщения, например имя его
автора или содержимое полученного сообщения. После того как клиент
получает сообщение, создается новый элемент div , в атрибуте innerHTML
которого содержатся имя автора и содержимое сообщения. Этот
элемент добавляется в основной элемент div , который используется для
отображения сообщений.
Для отправки сообщения через соединение WebSockets необходимо
вызвать метод send . Первый параметр этого метода содержит имя
сообщения. Другие параметры заполняются данными сообщения. В этом
примере на сервер отправляется сообщение, идентифицированное как
newMessage . Это сообщение содержит имя пользователя, а также данные,
введенные этим пользователем в текстовое поле. Если отправка
выполняется успешно, значение текстового поля очищается.
7. Выполните следующую команду в корневом элементе проекта:
Консоль
npm i @microsoft/signalr @types/node
Предыдущая команда устанавливает:
Клиент TypeScript SignalR
, который позволяет клиенту отправлять
сообщения на сервер.
Определения типа TypeScript для Node.js, обеспечивающие проверку
типов Node.js во время компиляции.
Тестирование приложения
Чтобы проверить работоспособность приложения, выполните следующие шаги.
Visual Studio
1. Запустите средство Webpack в режиме release . Выполните следующие
команды в окне Консоль диспетчера пакетов в корневом элементе
проекта. Если вы не в корневом каталоге проекта, введите перед этой
командой cd SignalRWebpack .
Консоль
npm run release
Эта команда создает ресурсы на стороне клиента, которые будут
обслуживаться при выполнении приложения. Эти ресурсы помещаются в
папку wwwroot .
Веб-пакет выполнил следующие задачи:
Очистка содержимого каталога wwwroot .
Преобразование TypeScript в JavaScript (этот процесс называется
транспилированием).
Корректировка созданного кода JavaScript в целях уменьшения
размера файла (этот процесс называется минификацией).
Копирование обработанных файлов JavaScript, CSS и HTML из
каталога src в wwwroot .
Внедрение следующих элементов в файл wwwroot/index.html :
Тег <link> , ссылающийся на файл wwwroot/main.<hash>.css . Этот
тег размещается непосредственно после закрывающего тега
</head> .
Тег <script> , ссылающийся на минифицированный файл
wwwroot/main.<hash>.js . Этот тег размещается непосредственно
после закрывающего тега </body> .
2. Выберите Отладка>Запуск без отладки, чтобы запустить приложение в
браузере, не присоединяя отладчик. Файл wwwroot/index.html
обрабатывается по адресу https://localhost:<port> .
Если во время компиляции возникают ошибки, попробуйте закрыть и
снова открыть решение.
3. Откройте другой экземпляр браузера (любого) и вставьте URL-адрес в
адресную строку.
4. Выберите любой браузер, введите произвольный текст в поле
Сообщение и нажмите кнопку Отправить. На обеих страницах мгновенно
отображаются имя пользователя и сообщение.
Дополнительные ресурсы
Клиент JavaScript SignalR ASP.NET Core
Использование концентраторов в ASP.NET Core SignalR
Использование SignalR для ASP.NET
Core с Blazor
Статья • 28.11.2022 • Чтение занимает 57 мин
В этом руководстве описаны основы того, как с помощью SignalR и Blazor создать
приложение, работающее в режиме реального времени.
Вы узнаете, как выполнять следующие задачи:
" создавать проекты Blazor;
" Добавление клиентской библиотеки SignalR
" добавлять концентратор SignalR;
" добавлять службы и конечную точку SignalR для концентратора SignalR;
" добавлять код компонента Razor для чата.
Когда вы выполните задачи из этого руководства, у вас будет работающее
приложение чата.
Предварительные требования
Visual Studio
Visual Studio 2022 или более поздней версии с рабочей нагрузкой ASP.NET
и разработка веб-приложений
Пакет SDK для .NET 6.0
Пример приложения
Для работы с этим руководством загружать пример приложения чата не
обязательно. Пример приложения — это готовое рабочее приложение, созданное
в результате выполнения действий, описанных в этом учебнике.
Просмотреть или скачать образец кода
Создание приложения Blazor Server
Следуйте указаниям по выбору инструментов:
Visual Studio
7 Примечание
Требуются Visual Studio 2022 или более поздней версии и пакет SDK для
.NET Core 6.0.0 или более поздней версии.
1. Создайте новый проект.
2. Выберите шаблон Blazor ServerПриложение. Выберите Далее.
3. Введите BlazorServerSignalRApp в поле Имя проекта. Убедитесь, что для
проекта правильно указано существующее расположение или укажите
новое. Выберите Далее.
4. Нажмите кнопку создания.
Добавление клиентской библиотеки SignalR
Visual Studio
1. В обозревателе решений щелкните проект BlazorServerSignalRApp
правой кнопкой мыши и выберите пункт Управление пакетами NuGet.
2. Убедитесь, что в диалоговом окне Управление пакетами NuGet для
параметра Источник пакета установлено значение nuget.org .
3. Нажав кнопку Обзор, введите Microsoft.AspNetCore.SignalR.Client в поле
поиска.
4. В результатах поиска выберите пакет
Microsoft.AspNetCore.SignalR.Client . Задайте версию в соответствии с
общей платформой приложения. Выберите пункт Установить.
5. Если откроется диалоговое окно Просмотр изменений, нажмите кнопку
ОК.
6. Если откроется диалоговое окно Принятие условий лицензионного
соглашения, выберите Я принимаю, если принимаете условия.
добавлять концентратор SignalR;
Создайте папку Hubs (plural) и добавьте следующий класс ChatHub ( Hubs/ChatHub.cs ):
C#
using Microsoft.AspNetCore.SignalR;
namespace BlazorServerSignalRApp.Server.Hubs
{
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}
Добавление служб и конечной точки для
концентратора SignalR
1. Откройте файл Program.cs .
2. Добавьте пространства имен для Microsoft.AspNetCore.ResponseCompression и
класс ChatHub в начало файла:
C#
using Microsoft.AspNetCore.ResponseCompression;
using BlazorServerSignalRApp.Server.Hubs;
3. Добавьте службы промежуточного ПО для сжатия ответа в Program.cs :
C#
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
4. В Program.cs :
Используйте ПО промежуточного слоя для сжатия ответа в верхней
части конфигурации конвейера обработки.
Между конечными точками для сопоставления концентратора Blazor и
отката на стороне клиента добавьте конечную точку для концентратора.
C#
app.UseResponseCompression();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapBlazorHub();
app.MapHub<ChatHub>("/chathub");
app.MapFallbackToPage("/_Host");
app.Run();
добавлять код компонента Razor для чата.
1. Откройте файл Pages/Index.razor .
2. Замените разметку следующим кодом:
razor
@page "/"
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
@implements IAsyncDisposable
<PageTitle>Index</PageTitle>
<div class="form-group">
<label>
User:
<input @bind="userInput" />
</label>
</div>
<div class="form-group">
<label>
Message:
<input @bind="messageInput" size="50" />
</label>
</div>
<button @onclick="Send" disabled="@(!IsConnected)">Send</button>
<hr>
<ul id="messagesList">
@foreach (var message in messages)
{
<li>@message</li>
}
</ul>
@code {
private
private
private
private
HubConnection? hubConnection;
List<string> messages = new List<string>();
string? userInput;
string? messageInput;
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.On<string, string>("ReceiveMessage", (user,
message) =>
{
var encodedMsg = $"{user}: {message}";
messages.Add(encodedMsg);
InvokeAsync(StateHasChanged);
});
await hubConnection.StartAsync();
}
private async Task Send()
{
if (hubConnection is not null)
{
await hubConnection.SendAsync("SendMessage", userInput,
messageInput);
}
}
public bool IsConnected =>
hubConnection?.State == HubConnectionState.Connected;
public async ValueTask DisposeAsync()
{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}
7 Примечание
Отключите ПО промежуточного слоя для сжатия ответов в среде Development
при использовании Горячей перегрузки. Дополнительные сведения см. в
статье Руководство по ASP.NET Core BlazorSignalR.
Запуск приложения
Следуйте указаниям по выбору инструментов:
Visual Studio
1. Нажмите клавишу
+
F5
F5
(Windows) либо
, чтобы запустить приложение с отладкой, или
⌘
+
F5
CTRL
(macOS), чтобы запустить его без отладки.
2. Скопируйте URL-адрес из адресной строки, откройте другой экземпляр
или вкладку браузера и вставьте URL-адрес в адресную строку.
3. Выберите любой браузер, введите имя и сообщение и нажмите кнопку
для отправки сообщения. Имя и сообщение отображаются на обеих
страницах мгновенно:
Цитаты: Звездный путь VI. Неоткрытая страна ©1991 Paramount
Дальнейшие действия
В этом руководстве вы узнали, как выполнять следующие задачи:
" создавать проекты Blazor;
" Добавление клиентской библиотеки SignalR
" добавлять концентратор SignalR;
" добавлять службы и конечную точку SignalR для концентратора SignalR;
" добавлять код компонента Razor для чата.
Дополнительные сведения о создании приложений Blazor см. в документации по
Blazor:
Проверка подлинности Blazor
маркера носителя с помощью сервера Identity, WebSockets и отправляемыми
сервером событиями
Дополнительные ресурсы
Безопасные концентраторы SignalR для размещенных приложений Blazor
WebAssembly
Общие сведения об ASP.NET CoreSignalR
Согласование независимо от источника для проверки подлинности для
SignalR
Конфигурация SignalR
Отладка в ASP.NET Core Blazor WebAssembly
Руководство по предотвращению угроз для ASP.NET Core Blazor Server
Репозиторий GitHub с примерами для Blazor (dotnet/blazor-samples)
Учебник. Создание клиента и сервера
gRPC в ASP.NET Core
Статья • 05.10.2022 • Чтение занимает 28 мин
В этом руководстве показано, как создать клиент gRPC в .NET Core и сервер gRPC
ASP.NET Core. В итоге вы получите клиент gRPC, который взаимодействует со
службой Greeter gRPC.
В этом учебнике рассмотрены следующие задачи.
" Создание сервера gRPC.
" Создание клиента gRPC.
" Тестирование клиента gRPC с помощью службы Greeter gRPC.
Предварительные требования
Visual Studio
Visual Studio 2022
с рабочей нагрузкой ASP.NET и веб-разработка.
Создание службы gRPC
Visual Studio
Запустите Visual Studio 2022 и нажмите Создать проект.
В диалоговом окне Создание нового проекта найдите пункт gRPC .
Выберите Служба ASP.NET Core gRPC и нажмите Далее.
В диалоговом окне Настроить новый проект введите GrpcGreeter в поле
Имя проекта. Для проекта необходимо установить имя GrpcGreeter, чтобы
при копировании и вставке кода совпадали пространства имен.
Выберите Далее.
В диалоговом окне Дополнительные сведения выберите .NET 6.0
(долгосрочная поддержка) и щелкните Создать.
Запуск службы
Visual Studio
Нажмите клавиши CTRL+F5, чтобы выполнить запуск без отладчика.
Visual Studio отображает следующее диалоговое окно, если проект еще не
настроен для использования SSL:
Выберите Да, чтобы сделать SSL-сертификат IIS Express доверенным.
Отобразится следующее диалоговое окно.
Выберите Да, если согласны доверять сертификату разработки.
Сведения о доверии к браузеру Firefox см. в разделе Ошибка сертификата
браузера Firefox SEC_ERROR_INADEQUATE_KEY_USAGE.
Visual Studio:
Запускает сервер Kestrel.
запускает браузер;
переходит к адресу http://localhost:port , например
http://localhost:7042 .
port: это номер порта, назначенный для приложения случайным
образом.
localhost : это стандартное имя узла для локального компьютера.
Localhost обслуживает только веб-запросы с локального
компьютера.
В журналах показана служба, ожидающая передачи данных на https://localhost:
<port> , где <port> — это номер порта localhost, назначаемый случайным образом
при создании проекта и задаваемый в Properties/launchSettings.json .
Консоль
info: Microsoft.Hosting.Lifetime[0]
Now listening on: https://localhost:<port>
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
7 Примечание
Шаблон gRPC настроен для использования протокола TLS . Клиенты gRPC
должны использовать протокол HTTPS для обращения к серверу. Номер порта
localhost службы gRPC назначается случайным образом при создании проекта
и задается в файле Properties\launchSettings.json проекта службы gRPC.
macOS не поддерживает ASP.NET Core gRPC с TLS. Для успешного запуска
служб gRPC в macOS требуется дополнительная настройка. Дополнительные
сведения см. в статье Не удается запустить приложение ASP.NET Core gRPC в
macOS.
Анализ файлов проекта
Файлы проекта GrpcGreeter:
Protos/greet.proto : определяет службу gRPC Greeter и используется для
создания ресурсов сервера gRPC. Дополнительные сведения см. в разделе
Введение в gRPC.
Папка Services : содержит реализацию службы Greeter .
appSettings.json : содержит данные конфигурации, включая протокол,
используемый в Kestrel. Дополнительные сведения см. в разделе
Конфигурация в ASP.NET Core.
Program.cs содержит следующее:
Точку входа для службы gRPC. Дополнительные сведения см. в статье
Универсальный узел .NET в ASP.NET Core.
Код, задающий поведение приложения. Дополнительные сведения: Запуск
приложения.
Создание клиента gRPC в консольном
приложении .NET
Visual Studio
Откройте второй экземпляр Visual Studio и щелкните Создать проект.
В диалоговом окне Создание проекта выберите Консольное приложение
и нажмите Далее.
В текстовое поле Имя проекта введите GrpcGreeterClient и нажмите
Далее.
В диалоговом окне Дополнительные сведения выберите .NET 6.0
(долгосрочная поддержка) и щелкните Создать.
Добавьте необходимые пакеты NuGet
Для клиентского проекта gRPC требуются следующие пакеты NuGet:
Grpc.Net.Client , который содержит клиент .NET Core.
Google.Protobuf , который содержит API сообщений protobuf для C#;
Grpc.Tools , который содержит поддержку инструментов C# для
файлов protobuf. Пакет инструментов не требуется во время выполнения,
поэтому зависимость помечается PrivateAssets="All" .
Visual Studio
Установка пакетов с помощью консоли диспетчера пакетов (PMC) или
управления пакетами NuGet.
Установка пакетов с помощью консоли диспетчера пакетов
В Visual Studio выберите пункты меню Сервис>Диспетчер пакетов
NuGet>Консоль диспетчера пакетов.
В консоли диспетчера пакетов выполните команду cd GrpcGreeterClient ,
чтобы перейти в папку с файлами GrpcGreeterClient.csproj .
Выполните следующие команды:
PowerShell
Install-Package Grpc.Net.Client
Install-Package Google.Protobuf
Install-Package Grpc.Tools
Установка пакетов с помощью раздела управления
пакетами NuGet
Щелкните правой кнопкой мыши проект в обозревателе
решений>Управление пакетами NuGet.
Выберите вкладку Обзор.
В поле поиска введите Grpc.Net.Client.
Выберите пакет Grpc.Net.Client на вкладке Обзор и нажмите кнопку
Установить.
Повторите все шаги для Google.Protobuf и Grpc.Tools .
Добавление greet.proto
Создайте папку Protos в клиентском проекте gRPC.
Скопируйте файл Protos\greet.proto из службы Greeter gRPC в папку Protos
проекта клиента gRPC.
Измените пространство имен в файле greet.proto на пространство имен
проекта:
option csharp_namespace = "GrpcGreeterClient";
Измените файл проекта GrpcGreeterClient.csproj :
Visual Studio
Щелкните проект правой кнопкой мыши и выберите Изменить файл проекта.
Добавьте группу элементов с элементом <Protobuf> , ссылающимся на файл
greet.proto:
XML
<ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>
Создание клиента Greeter
Скомпилируйте клиентский проект, чтобы создать типы в пространстве имен
GrpcGreeterClient .
7 Примечание
Типы GrpcGreeterClient создаются автоматически в процессе сборки. Пакет
инструментов Grpc.Tools
создает следующие файлы на основе файла
greet.proto:
GrpcGreeterClient\obj\Debug\[TARGET_FRAMEWORK]\Protos\Greet.cs : код для
буфера протокола, который заполняет, сериализует и извлекает типы
сообщений для запроса и ответа;
GrpcGreeterClient\obj\Debug\[TARGET_FRAMEWORK]\Protos\GreetGrpc.cs :
содержит созданные клиентские классы.
Дополнительные сведения о ресурсах C#, автоматически создаваемых
Grpc.Tools , см. в разделе Службы gRPC и C#: создаваемые ресурсы C#.
Добавьте в файл Program.cs клиента gRPC следующий код.
C#
using System.Threading.Tasks;
using Grpc.Net.Client;
using GrpcGreeterClient;
// The port number must match the port of the gRPC server.
using var channel = GrpcChannel.ForAddress("https://localhost:7042");
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(
new HelloRequest { Name = "GreeterClient" });
Console.WriteLine("Greeting: " + reply.Message);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
В выделенном коде выше замените номер порта localhost 7042 на номер
порта HTTPS , указанный в файле Properties/launchSettings.json проекта
службы GrpcGreeter .
файл Program.cs содержит точку входа и логику для клиента gRPC.
Клиент Greeter создается следующим образом:
Создание экземпляра GrpcChannel со сведениями для создания подключения к
службе gRPC.
Использование GrpcChannel для создания клиента Greeter:
C#
// The port number must match the port of the gRPC server.
using var channel = GrpcChannel.ForAddress("https://localhost:7042");
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(
new HelloRequest { Name = "GreeterClient" });
Console.WriteLine("Greeting: " + reply.Message);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
Клиент Greeter вызывает асинхронный метод SayHello . Отображается результат
вызова SayHello :
C#
// The port number must match the port of the gRPC server.
using var channel = GrpcChannel.ForAddress("https://localhost:7042");
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(
new HelloRequest { Name = "GreeterClient" });
Console.WriteLine("Greeting: " + reply.Message);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
Тестирование клиента gRPC с помощью
службы Greeter gRPC
Visual Studio
В службе Greeter нажмите Ctrl+F5 для запуска сервера без отладчика.
В проекте GrpcGreeterClient нажмите Ctrl+F5 для запуска клиента без
отладчика.
Клиент отправляет приветствие в службу с сообщением, в котором содержится его
имя GreeterClient. Служба отправляет сообщение "Hello GreeterClient" в качестве
ответа. Ответ "Hello GreeterClient" отображается в командной строке:
Консоль
Greeting: Hello GreeterClient
Press any key to exit...
Служба gRPC записывает сведения об успешном вызове в журналы, что
отображается в командной строке:
Консоль
info: Microsoft.Hosting.Lifetime[0]
Now listening on: https://localhost:<port>
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path:
C:\GH\aspnet\docs\4\Docs\aspnetcore\tutorials\grpc\grpcstart\sample\GrpcGreeter
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/2 POST https://localhost:
<port>/Greet.Greeter/SayHello application/grpc
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'gRPC - /Greet.Greeter/SayHello'
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'gRPC - /Greet.Greeter/SayHello'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished in 78.32260000000001ms 200 application/grpc
Обновите файл appsettings.Development.json , добавив следующие строки:
"Microsoft.AspNetCore.Hosting": "Information",
"Microsoft.AspNetCore.Routing.EndpointMiddleware": "Information"
7 Примечание
Чтобы применить код, описанный в этой статье, требуется сертификат
разработки HTTPS ASP.NET Core для защиты службы gRPC. Если .NET gRPC
возвращает сообщение The remote certificate is invalid according to the
validation procedure. или The SSL connection could not be established. , это
значит, что сертификат разработки не является доверенным. Чтобы исправить
эту проблему, см. Вызов службы gRPC с использованием ненадежного или
недействительного сертификата.
2 Предупреждение
ASP.NET Core gRPC предусматривает дополнительные требования в
отношении использования со Службой приложений Azure или службами IIS.
Дополнительные сведения о том, где можно использовать gRPC, см. в статье
Использование gRPC на поддерживаемых платформах .NET.
Следующие шаги
Просмотрите или скачайте готовый пример кода для этого учебника
(инструкции по скачиванию).
Общие сведения о gRPC на .NET
Службы gRPC на языке C#
Перенос gRPC с C-core на gRPC для .NET
Использование Razor Pages с Entity
Framework Core в ASP.NET Core:
руководство 1 из 8
Статья • 28.01.2023 • Чтение занимает 52 мин
Авторы: Том Дайкстра
П. Смит
(Tom Dykstra), Джереми Ликнесс
(Jeremy Likness) и Йон
(Jon P Smith)
Это первое руководство из серии, посвященной использованию Entity Framework
(EF) Core в приложении ASP.NET Core Razor Pages. В учебниках создается веб-сайт
для вымышленного университета Contoso. На сайте предусмотрены различные
функции, в том числе прием учащихся, создание курсов и назначение
преподавателей. В этом руководстве используется подход Code First. См. сведения
о работе с этим руководством при использовании подхода Database First в этой
проблеме GitHub .
Скачайте или ознакомьтесь с готовым приложением. Инструкции по скачиванию.
Предварительные требования
Если у вас нет опыта работы с Razor Pages, ознакомьтесь с серией руководств
Начало работы с Razor Pages, прежде чем приступать к изучению этого
руководства.
Visual Studio
Visual Studio 2022
с рабочей нагрузкой ASP.NET и веб-разработка.
Ядра СУБД
В инструкциях для Visual Studio используется SQL Server LocalDB, версия
SQL Server Express, которая работает только в Windows.
Устранение неполадок
Если вы столкнулись с проблемой, которую не можете решить, сравните свой код с
кодом готового проекта . Хорошим способом получения справки является
публикация вопроса в StackOverflow.com с помощью тега ASP.NET Core
тегаEF Core
или
.
Пример приложения
Приложение, создаваемое в этих руководствах, является простым веб-сайтом
университета. Пользователи приложения могут просматривать и обновлять
сведения об учащихся, курсах и преподавателях. Здесь приведено несколько
экранов, создаваемых в руководстве.
Стиль пользовательского интерфейса для этого сайта основан на встроенных
шаблонах проектов. В руководстве основное внимание уделяется использованию
EF Core с ASP.NET Core, а не настройке пользовательского интерфейса.
Необязательно: сборка примера для скачивания
Это необязательный шаг. Создание готового приложения рекомендуется в случае,
если возникли проблемы, которые не удается решить. Если вы столкнулись с
проблемой, которую не можете решить, сравните свой код с кодом готового
проекта . Указания по скачиванию.
Visual Studio
Выберите ContosoUniversity.csproj , чтобы открыть проект.
Выполните построение проекта.
В консоли диспетчера пакетов (PMC) выполните следующую команду:
PowerShell
Update-Database
Запустите проект, чтобы заполнить базу данных.
Создание проекта веб-приложения
Visual Studio
1. Запустите Visual Studio 2022 и нажмите Создать проект.
2. В диалоговом окне Создать проект выберите Веб-приложение
ASP.NET Core и нажмите Далее.
3. В диалоговом окне Настроить новый проект введите ContosoUniversity в
поле Имя проекта. Важно присвоить проекту имя ContosoUniversity,
включая сопоставление регистра букв, чтобы пространство имени
соответствовало при копировании и вставке примера кода.
4. Выберите Далее.
5. В диалоговом окне Дополнительные сведения выберите .NET 6.0
(долгосрочная поддержка) и щелкните Создать.
Настройка стиля сайта
Скопируйте и вставьте в файл Pages/Shared/_Layout.cshtml следующий код:
CSHTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Contoso University</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"
/>
<link rel="stylesheet" href="~/ContosoUniversity.styles.css" asp-appendversion="true" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbarlight bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asppage="/Index">Contoso University</a>
<button class="navbar-toggler" type="button" data-bstoggle="collapse" data-bs-target=".navbar-collapse" ariacontrols="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle
navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex
justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asppage="/About">About</a>
</li>
<li class="nav-item">
<a class="nav-link
page="/Students/Index">Students</a>
</li>
<li class="nav-item">
<a class="nav-link
page="/Courses/Index">Courses</a>
</li>
<li class="nav-item">
<a class="nav-link
page="/Instructors/Index">Instructors</a>
</li>
<li class="nav-item">
<a class="nav-link
page="/Departments/Index">Departments</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
text-dark" asp-area="" asp-
text-dark" asp-area="" asp-
text-dark" asp-area="" asp-
text-dark" asp-area="" asp-
<footer class="border-top footer text-muted">
<div class="container">
© 2021 - Contoso University - <a asp-area="" asppage="/Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
Файл макета задает заголовок, нижний колонтитул и меню для сайта. Приведенный
выше код вносит следующие изменения:
Заменяет все вхождения "ContosoUniversity" на "Contoso University". Таких
элементов будет три.
Пункты меню Home и Privacy будут удалены.
Добавляются пункты меню About (Сведения), Students (Учащиеся), Courses
(Курсы), Instructors (Преподаватели) и Departments (Кафедры).
Замените все содержимое файла Pages/Index.cshtml следующим кодом:
CSHTML
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="row mb-auto">
<div class="col-md-4">
<div class="row no-gutters border mb-4">
<div class="col p-4 mb-4 ">
<p class="card-text">
Contoso University is a sample application that
demonstrates how to use Entity Framework Core in an
ASP.NET Core Razor Pages web app.
</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="row no-gutters border mb-4">
<div class="col p-4 d-flex flex-column position-static">
<p class="card-text mb-auto">
You can build the application by following the steps in
a series of tutorials.
</p>
<p>
@*
<a
href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro"
class="stretched-link">See the tutorial</a>
*@
</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="row no-gutters border mb-4">
<div class="col p-4 d-flex flex-column">
<p class="card-text mb-auto">
You can download the completed project from GitHub.
</p>
<p>
@*
<a
href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef
-rp/intro/samples" class="stretched-link">See project source code</a>
*@
</p>
</div>
</div>
</div>
</div>
Приведенный выше код заменяет текст об ASP.NET Core текстом об этом
приложении.
Запустите приложение, чтобы проверить правильность отображения домашней
страницы.
Модель данных
В следующих разделах создается модель данных.
Учащийся может зарегистрироваться в любом количестве курсов, а в отдельном
курсе может быть зарегистрировано любое количество учащихся.
Сущность Student
Создайте папку Models (Модели) в папке проекта.
Создайте Models/Student.cs , используя следующий код:
C#
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Свойство ID используется в качестве столбца первичного ключа в таблице базы
данных, соответствующей этому классу. По умолчанию интерпретирует свойство с
EF Core именем ID или classnameID как первичный ключ. Поэтому альтернативным
автоматически распознаваемым именем для первичного ключа класса Student
является StudentID . Дополнительные сведения см. в разделе EF Core Ключи.
Свойство Enrollments является свойством навигации. Свойства навигации
содержат другие сущности, связанные с этой сущностью. В этом случае свойство
Enrollments сущности Student содержит все сущности Enrollment , которые связаны
с этим учащимся. Например, если строка Student (Учащийся) в базе данных имеет
две связанные строки Enrollment (Регистрация), свойство навигации Enrollments
содержит две эти сущности Enrollment.
В базе данных строка Enrollment связана со строкой Student, если ее столбец
StudentID содержит идентификатор учащегося. Например, предположим, что
строка Student содержит идентификатор 1. Связанные строки Enrollment будут
содержать значение StudentID (идентификатор учащегося), равное 1. StudentID —
это внешний ключ в таблице Enrollment.
Свойство Enrollments определено как ICollection<Enrollment> , так как может быть
несколько связанных сущностей Enrollment. Можно использовать и другие типы
коллекций, например List<Enrollment> или HashSet<Enrollment> . При
ICollection<Enrollment> использовании по EF Core умолчанию создает
HashSet<Enrollment> коллекцию.
Сущность Enrollment
Создайте Models/Enrollment.cs , используя следующий код:
C#
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
public class Enrollment
{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
[DisplayFormat(NullDisplayText = "No grade")]
public Grade? Grade { get; set; }
public Course Course { get; set; }
public Student Student { get; set; }
}
}
Свойство EnrollmentID является первичным ключом. В этой сущности используется
шаблон classnameID вместо ID . Для рабочей модели данных многие разработчики
выбирают один шаблон и используют только его. В этом учебнике используются
оба шаблона, чтобы проиллюстрировать их работу. Использование ID без
classname упрощает внесение некоторых изменений в модель данных.
Свойство Grade имеет тип enum . Знак вопроса после объявления типа Grade
указывает, что свойство Grade допускает значение NULL. Оценка со значением null
отличается от нулевой оценки тем, что при таком значении оценка еще не
известна или не назначена.
Свойство StudentID представляет собой внешний ключ. Ему соответствует свойство
навигации Student . Сущность Enrollment связана с одной сущностью Student ,
поэтому свойство содержит отдельную сущность Student .
Свойство CourseID представляет собой внешний ключ. Ему соответствует свойство
навигации Course . Сущность Enrollment связана с одной сущностью Course .
EF Core интерпретирует свойство как внешний ключ, если оно называется
<navigation property name><primary key property name> . Например, StudentID
является внешним ключом для свойства навигации Student , так как сущность
Student имеет первичный ключ ID . Свойства внешнего ключа также могут
называться <primary key property name> . Например, CourseID , так как сущность
Course имеет первичный ключ CourseID .
Сущность Course
Создайте Models/Course.cs , используя следующий код:
C#
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Свойство Enrollments является свойством навигации. Сущность Course может быть
связана с любым числом сущностей Enrollment .
Атрибут DatabaseGenerated позволяет приложению указать первичный ключ, а не
использовать созданный базой данных.
Построение приложения. Компилятор создает несколько предупреждений о том,
как обрабатываются значения null . Дополнительные сведения см. в сведениях об
этой проблеме GitHub , а также в статьях Ссылочные типы, допускающие
значение NULL и Руководство по более четкому выражению проектного замысла с
помощью ссылочных типов, допускающих и не допускающих значение NULL.
Чтобы исключить предупреждения из ссылочных типов, допускающих значения
NULL, удалите из файла ContosoUniversity.csproj следующую строку:
XML
<Nullable>enable</Nullable>
В настоящее время подсистема формирования шаблонов не поддерживает
ссылочные типы, допускающие значения NULL, поэтому модели, используемые в
формировании шаблонов, также не поддерживают их.
Удалите заметку о ссылочном типе ? , допускающем значение NULL, из public
string? RequestId { get; set; } в файле Pages/Error.cshtml.cs , чтобы сборка
проекта выполнялась без предупреждений компилятора.
Формирование шаблона для страниц
Student
В этом разделе для создания указанных ниже компонентов используется средство
формирования шаблонов ASP.NET Core.
Класс EF Core DbContext . Контекст —это основной класс, который
координирует функциональные возможности Entity Framework для
определенной модели данных. Он является производным от класса
Microsoft.EntityFrameworkCore.DbContext.
Razor Pages с поддержкой операций создания, чтения, обновления и удаления
(CRUD) для сущности Student .
Visual Studio
Создайте папку Pages/Students.
В обозревателе решений щелкните правой кнопкой мыши папку
Pages/Students и выберите пункты Добавить>Создать шаблонный
элемент.
В диалоговом окне Добавление нового элемента шаблона выполните
указанные ниже действия.
На вкладке слева выберите Установленные > Общие >Razor Pages
Выберите Razor Pages на основе Entity Framework (CRUD)>Добавить.
В диалоговом окне Добавление Razor Pages на основе Entity Framework
(CRUD) сделайте следующее:
В раскрывающемся списке Класс модели выберите Student
(ContosoUniversity.Models) .
В строке Класс контекста данных щелкните знак плюса (+).
Измените имя контекста данных так, чтобы оно заканчивалось на
SchoolContext , а не на ContosoUniversityContext . Новое имя
контекста: ContosoUniversity.Data.SchoolContext .
Выберите Добавить, чтобы завершить добавление класса контекста
данных.
Выберите Добавить, чтобы закрыть диалоговое окно Добавление
Razor Pages.
Следующие пакеты устанавливаются автоматически:
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
Microsoft.VisualStudio.Web.CodeGeneration.Design
Если предыдущий шаг завершился сбоем, выполните сборку проекта и повторите
шаг формирования шаблона.
В процессе формирования шаблона выполняются следующие действия:
создаются страницы Razor в папке Pages/Students:
Create.cshtml и Create.cshtml.cs
Delete.cshtml и Delete.cshtml.cs
Details.cshtml и Details.cshtml.cs
Edit.cshtml и Edit.cshtml.cs
Index.cshtml и Index.cshtml.cs
Создает Data/SchoolContext.cs .
добавляется контекст для внедрения зависимостей в файле Program.cs ;
добавляет строку подключения к базе данных в файл appsettings.json .
Строка подключения к базе данных
Средство формирования шаблонов создает строку подключения в файле
appsettings.json .
Visual Studio
Строка подключения указывает базу данных SQL Server LocalDB.
JSON
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SchoolContext": "Server=
(localdb)\\mssqllocaldb;Database=SchoolContext0e9;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
LocalDB — это упрощенная версия ядра СУБД SQL Server Express,
предназначенная для разработки приложений и не ориентированная на
использование в рабочей среде. По умолчанию LocalDB создает файлы MDF в
каталоге C:/Users/<user> .
Обновление класса контекста базы данных
Основным классом, координирующий функциональные EF Core возможности
данной модели данных, является класс контекста базы данных. Контекст является
производным от Microsoft.EntityFrameworkCore.DbContext. Контекст указывает
сущности, которые включаются в модель данных. В этом проекте соответствующий
класс называется SchoolContext .
Обновите Data/SchoolContext.cs , включив в него следующий код.
C#
using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext (DbContextOptions<SchoolContext> options)
: base(options)
{
}
public DbSet<Student> Students { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}
Приведенный выше код изменяет форму слова DbSet<Student> Student на
множественную: DbSet<Student> Students . Чтобы код Razor Pages соответствовал
новому имени DBSet , произведите глобальное изменение _context.Student. на
_context.Students. .
Всего это имя встречается 8 раз.
Так как набор сущностей содержит несколько сущностей, многие разработчики
предпочитают имена свойств DBSet во множественном числе.
Выделенный код:
Создает свойство DbSet<TEntity> для каждого набора сущностей. В EF Core
терминологии:
Набор сущностей обычно соответствует таблице базы данных.
Сущность соответствует строке в таблице.
Вызывает OnModelCreating. OnModelCreating :
вызывается, когда контекст SchoolContext был инициализирован, но
прежде чем модель была заблокирована и использована для
инициализации контекста;
является обязательным, так как далее в руководстве сущность Student
будет содержать ссылки на другие сущности.
Мы планируем исправить эту проблему
в будущем выпуске.
Program.cs
ASP.NET Core поддерживает внедрение зависимостей. С помощью внедрения
зависимостей службы, например SchoolContext , регистрируются во время запуска
приложения. Затем компоненты, которые используют эти службы, например Razor
Pages, обращаются к ним через параметры конструктора. Код конструктора,
который получает экземпляр контекста базы данных, приведен далее в этом
руководстве.
Средство формирования шаблонов автоматически зарегистрировало класс
контекста в контейнере внедрения зависимостей.
Visual Studio
Следующие выделенные строки были добавлены средством формирования
шаблонов:
C#
using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolCo
ntext")));
Имя строки подключения передается в контекст путем вызова метода для объекта
DbContextOptions. При локальной разработке система конфигурации ASP.NET Core
считывает строку подключения из файла appsettings.json или
appsettings.Development.json .
Добавление фильтра исключений базы данных
Добавьте AddDatabaseDeveloperPageExceptionFilter и UseMigrationsEndPoint, как
показано в следующем коде:
Visual Studio
C#
using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolCo
ntext")));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
else
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}
Добавьте пакет NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore .
Введите в консоли диспетчера пакетов следующие команды, чтобы добавить
пакет NuGet:
PowerShell
Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Пакет NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore предоставляет
ПО промежуточного слоя ASP.NET Core для страниц ошибок Entity Framework Core.
Это ПО промежуточного слоя помогает обнаруживать и диагностировать ошибки с
помощью миграций Entity Framework Core.
AddDatabaseDeveloperPageExceptionFilter предоставляет полезные сведения об
ошибках EF в среде разработки для их устранения.
Создание базы данных
Обновите файл Program.cs , чтобы создать базу данных, если она не существует.
Visual Studio
C#
using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolCo
ntext")));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
else
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<SchoolContext>();
context.Database.EnsureCreated();
// DbInitializer.Initialize(context);
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Метод EnsureCreated не выполняет никаких действий, если база данных для
контекста существует. Если база данных не существует, она создается вместе со
схемой. EnsureCreated обеспечивает описанный ниже рабочий процесс для
обработки изменений модели данных.
База данных удаляется. Все существующие данные теряются.
Модель данных изменяется. Например, добавляется поле EmailAddress .
Запустите приложение.
Метод EnsureCreated создает базу данных с новой схемой.
Этот рабочий процесс хорошо подходит для ранних стадий разработки, когда
схема часто меняется, если данные сохранять не требуется. Однако если данные,
введенные в базу данных, необходимо сохранять, ситуация будет иной. В таком
случае используйте перенос.
Далее в этой серии учебников вы удалите базу данных, созданную методом
EnsureCreated , и используете вместо этого перенос. Созданную методом
EnsureCreated базу данных нельзя обновить, используя перенос.
Тестирование приложения
Запустите приложение.
Щелкните ссылку Students и выберите Создать.
Протестируйте ссылки Edit, Details и Delete.
Заполнение базы данных
Метод EnsureCreated создает пустую базу данных. В этом разделе добавляется код,
который заполняет базу данных тестовыми данными.
Создайте Data/DbInitializer.cs , используя следующий код:
C#
using ContosoUniversity.Models;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
// Look for any students.
if (context.Students.Any())
{
return;
// DB has been seeded
}
var students = new Student[]
{
new
Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.P
arse("2019-09-01")},
new
Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Pa
rse("2017-09-01")},
new
Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse
("2018-09-01")},
new
Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Pa
rse("2017-09-01")},
new
Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017
-09-01")},
new
Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Pars
e("2016-09-01")},
new
Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse
("2018-09-01")},
new
Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Pars
e("2019-09-01")}
};
context.Students.AddRange(students);
context.SaveChanges();
var courses = new Course[]
{
new Course{CourseID=1050,Title="Chemistry",Credits=3},
new Course{CourseID=4022,Title="Microeconomics",Credits=3},
new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
new Course{CourseID=1045,Title="Calculus",Credits=4},
new Course{CourseID=3141,Title="Trigonometry",Credits=4},
new Course{CourseID=2021,Title="Composition",Credits=3},
new Course{CourseID=2042,Title="Literature",Credits=4}
};
context.Courses.AddRange(courses);
context.SaveChanges();
var enrollments = new Enrollment[]
{
new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
new Enrollment{StudentID=3,CourseID=1050},
new Enrollment{StudentID=4,CourseID=1050},
new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
new Enrollment{StudentID=6,CourseID=1045},
new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
};
context.Enrollments.AddRange(enrollments);
context.SaveChanges();
}
}
}
Этот код проверяет наличие учащихся в базе данных. Если учащихся нет, в базу
данных добавляются тестовые данные. Для повышения производительности
тестовые данные создаются массивами, а не коллекциями List<T> .
В Program.cs удалите // из строки DbInitializer.Initialize :
C#
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<SchoolContext>();
context.Database.EnsureCreated();
DbInitializer.Initialize(context);
}
Visual Studio
Завершите работу приложения, если оно запущено, и выполните
следующую команду в консоли диспетчера пакетов (PMC):
PowerShell
Drop-Database -Confirm
Выберите Y , чтобы удалить базу данных.
Перезапустите приложение.
Выберите страницу учащихся, чтобы увидеть заполненные данные.
Просмотр базы данных
Visual Studio
Откройте обозреватель объектов SQL Server (SSOX) из меню Вид в Visual
Studio.
В SSOX щелкните (localdb)\MSSQLLocalDB > Базы данных >
SchoolContext-{GUID}. Имя базы данных создается на основе имени
контекста, которое вы указали ранее, а также включает дефис и GUID.
Разверните узел Таблицы.
Щелкните правой кнопкой мыши таблицу Student (Учащийся) и выберите
пункт Просмотр данных, чтобы просмотреть созданные столбцы и
строки, вставленные в таблицу.
Щелкните правой кнопкой мыши таблицу Student (Учащийся) и выберите
пункт Просмотреть код, чтобы увидеть, как модель Student соотносится
со схемой таблицы Student .
Асинхронные методы EF в веб-приложениях
ASP.NET Core
Асинхронное программирование — это режим по умолчанию для ASP.NET Core и
EF Core.
Веб-сервер имеет ограниченное число потоков, поэтому при высокой загрузке
могут использоваться все доступные потоки. В таких случаях сервер не может
обрабатывать новые запросы до тех пор, пока не будут высвобождены потоки. В
синхронном коде многие потоки могут быть заняты, не выполняя при этом какиелибо операции и ожидая завершения ввода-вывода. В асинхронном коде в то
время, когда процесс ожидает завершения ввода-вывода, его поток
высвобождается и может использоваться сервером для обработки других
запросов. Таким образом, асинхронный код позволяет более эффективно
использовать ресурсы сервера, который может обрабатывать больше трафика без
задержек.
Во время выполнения асинхронный код использует немного больше служебных
ресурсов. Однако при низком объеме трафика этим можно пренебречь. Тем не
менее в случае большого объема трафика это дает существенный выигрыш в
производительности.
В следующем коде для асинхронного выполнения используются ключевое слово
async, возвращаемое значение Task , ключевое слово await и метод ToListAsync .
C#
public async Task OnGetAsync()
{
Students = await _context.Students.ToListAsync();
}
Ключевое слово async указывает компилятору:
создавать обратные вызовы для частей тела метода;
создавать возвращаемый объект Task.
Тип возвращаемого значения Task представляет текущую операцию.
Ключевое слово await предписывает компилятору разделить метод на две
части. Первая часть завершается операцией, которая запускается в
асинхронном режиме. Вторая часть помещается в метод обратного вызова,
который вызывается при завершении операции.
ToListAsync является асинхронной версией метода расширения ToList .
Некоторые моменты, которые следует учитывать при написании асинхронного
кода, использующего EF Core:
Асинхронно выполняются только те инструкции, в результате которых в базу
данных отправляются запросы или команды. К ним относятся ToListAsync ,
SingleOrDefaultAsync , FirstOrDefaultAsync и SaveChangesAsync . В их число не
входят операторы, которые просто изменяют IQueryable , такие как var
students = context.Students.Where(s => s.LastName == "Davolio") .
Контекст EF Core не является потокобезопасным: не пытайтесь выполнять
несколько операций параллельно.
Чтобы воспользоваться преимуществами производительности асинхронного
кода, убедитесь, что пакеты библиотеки (например, для разбиения по
страницам) используют асинхронные методы, которые вызывают EF Core
методы, отправляющие запросы в базу данных.
Дополнительные сведения об асинхронном программировании см. в разделах
Обзор асинхронной модели и Асинхронное программирование с использованием
ключевых слов Async и Await.
2 Предупреждение
Асинхронная реализация Майкрософт. Data.SqlClient
известные проблемы (No 593
, No 601
имеет некоторые
и другие). Если возникают
непредвиденные проблемы с производительностью, попробуйте использовать
выполнение команд синхронизации, особенно при работе с большим текстом
или двоичными значениями.
Вопросы производительности
Как правило, веб-страница не должна загружать произвольное число строк. Запрос
должен использовать разбиение на страницы или применять ограничение.
Например, предыдущий запрос может использовать Take для ограничения
количества возвращаемых строк:
C#
public async Task OnGetAsync()
{
Student = await _context.Students.Take(10).ToListAsync();
}
Если в процессе перечисления большой таблицы в представлении возникло
исключение базы данных, может быть возвращен ответ HTTP 200 с сообщением о
частично сформированных данных.
Разбиение на страницы рассматривается далее в этом руководстве.
Дополнительные сведения см. в разделе Особенности производительности (EF).
Дальнейшие действия
Использование SQLite для среды разработки и SQL Server для рабочей среды
Следующий учебник
Часть 2. Razor Страницы с EF Core
ASP.NET Core — CRUD
Статья • 28.01.2023 • Чтение занимает 28 мин
Авторы: Том Дайкстра
П. Смит
(Tom Dykstra), Джереми Ликнесс
(Jeremy Likness) и Йон
(Jon P Smith)
Веб-приложение Университета Contoso демонстрирует создание Razor вебприложений Pages с помощью EF Core Visual Studio. Сведения о серии руководств
см. в первом руководстве серии.
При возникновении проблем, которые вам не удается устранить, скачайте готовое
приложение
и сравните его код с тем, который вы создали в процессе работы с
этим руководством.
В этом учебнике описывается проверка и настройка шаблонного кода операций
CRUD (создание, чтение, обновление и удаление).
Репозиторий отсутствует.
Некоторые разработчики используют уровень служб или шаблон репозитория,
чтобы создать уровень абстракции между пользовательским интерфейсом (Razor
Pages) и уровнем доступа к данным. В данном учебнике этого не делается. Чтобы
свести к минимуму сложность и сосредоточиться на EF Coreучебнике, EF Core код
добавляется непосредственно в классы модели страниц.
Обновление страницы Details (сведения)
Шаблонный код для страниц учащихся не включает в себя сведения о регистрации.
В этом разделе показано, как регистрации добавляются на страницу Details .
Считывание сведений о регистрации
Чтобы отобразить сведения о регистрации учащегося на странице, эти сведения
необходимо считать. Шаблонный код в файле Pages/Students/Details.cshtml.cs
считывает только сведения Student , но не сведения Enrollment :
C#
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Student = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);
if (Student == null)
{
return NotFound();
}
return Page();
}
Замените метод OnGetAsync приведенным ниже кодом для считывания сведений о
регистрации для выбранного учащегося. Изменения выделены.
C#
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Student = await _context.Students
.Include(s => s.Enrollments)
.ThenInclude(e => e.Course)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (Student == null)
{
return NotFound();
}
return Page();
}
Методы Include и ThenInclude инструктируют контекст для загрузки свойства
навигации Student.Enrollments , а также свойства навигации Enrollment.Course в
пределах каждой регистрации. Эти методы более подробно рассматриваются в
руководстве, посвященном чтению связанных данных.
Метод AsNoTracking повышает производительность в тех сценариях, где
возвращаемые сущности не обновляются в текущем контексте. AsNoTracking
рассматривается позднее в этом учебнике.
Отображение регистраций
Замените код в файле Pages/Students/Details.cshtml приведенным ниже кодом для
отображения списка регистраций. Изменения выделены.
CSHTML
@page
@model ContosoUniversity.Pages.Students.DetailsModel
@{
ViewData["Title"] = "Details";
}
<h1>Details</h1>
<div>
<h4>Student</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.LastName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.LastName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.FirstMidName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.FirstMidName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.EnrollmentDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.EnrollmentDate)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.Enrollments)
</dt>
<dd class="col-sm-10">
<table class="table">
<tr>
<th>Course Title</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.Student.Enrollments)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Course.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
</dd>
</dl>
</div>
<div>
<a asp-page="./Edit" asp-route-id="@Model.Student.ID">Edit</a> |
<a asp-page="./Index">Back to List</a>
</div>
Приведенный выше код циклически обрабатывает сущности в свойстве навигации
Enrollments . Для каждой регистрации он отображает название курса и оценку.
Название курса извлекается из сущности Course , которая хранится в свойстве
навигации Course сущности Enrollments.
Запустите приложение, выберите вкладку Students (Учащиеся) и щелкните ссылку
Details (Сведения) для учащегося. Отобразится список курсов и оценок для
выбранного учащегося.
Способы чтения одной сущности
В созданном коде для чтения одной сущности используется метод
FirstOrDefaultAsync. Этот метод возвращает значение NULL, если ничего не
найдено. В противном случае возвращается первая найденная строка,
удовлетворяющая условиям фильтра запросов. FirstOrDefaultAsync обычно
предпочтительнее, чем следующие варианты:
SingleOrDefaultAsync — вызывает исключение при наличии нескольких
сущностей, соответствующих фильтру запросов. Для определения того, может
ли запрос вернуть более одной строки, SingleOrDefaultAsync пытается
получить несколько строк. Это дополнительное действие не требуется, если
запрос может вернуть только одну сущность, как при поиске по уникальному
ключу.
FindAsync — находит сущность с первичным ключом. Если сущность с
первичным ключом отслеживается контекстом, она возвращается без запроса
к базе данных. Этот метод оптимизирован для поиска одной сущности, однако
вызвать Include с FindAsync невозможно. Поэтому если требуются связанные
данные, лучше использовать FirstOrDefaultAsync .
Данные маршрута или строка запроса
URL-адрес страницы сведений — https://localhost:<port>/Students/Details?id=1 .
Значение первичного ключа сущности содержится в строке запроса. Некоторые
разработчики предпочитают передавать значение ключа в данных маршрута:
https://localhost:<port>/Students/Details/1 . Дополнительные сведения см. в
разделе Обновление созданного кода.
Обновление страницы Create
Шаблонный код OnPostAsync для страницы создания уязвим к чрезмерной
передаче данных. Замените метод OnPostAsync в файле
Pages/Students/Create.cshtml.cs следующим кодом:
C#
public async Task<IActionResult> OnPostAsync()
{
var emptyStudent = new Student();
if (await TryUpdateModelAsync<Student>(
emptyStudent,
"student",
// Prefix for form value.
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
_context.Students.Add(emptyStudent);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return Page();
}
TryUpdateModelAsync
В приведенном выше коде создается объект Student, после чего опубликованные
поля формы используются для обновления свойств этого объекта. Метод
TryUpdateModelAsync выполняет указанные ниже действия.
Использует опубликованные значения формы из свойства PageContext в
PageModel.
Обновляет только перечисленные свойства ( s => s.FirstMidName, s =>
s.LastName, s => s.EnrollmentDate ).
Ищет поля формы с префиксом "student". Например, Student.FirstMidName .
Задается без учета регистра символов.
Использует систему привязки модели для преобразования значений формы
из строк в типы модели Student . Например, EnrollmentDate преобразуется в
DateTime .
Запустите приложение и создайте сущность учащегося для тестирования страницы
создания.
Чрезмерная передача данных
В целях повышения безопасности рекомендуется использовать TryUpdateModel для
обновления полей на основе отправленных значений, поскольку в этом случае
исключается чрезмерная передача данных. Например, сущность Student включает
свойство Secret , которое веб-страница не должна обновлять или добавлять:
C#
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public string Secret { get; set; }
}
Даже если приложение не имеет поля Secret на странице создания или
обновления Razor, злоумышленник может установить значение Secret
посредством чрезмерной передачи данных. Злоумышленник может использовать
такие средства, как Fiddler, или собственный код JavaScript для отправки значения
формы Secret . В исходном коде не ограничиваются поля, которые используются
при создании экземпляра Student связывателем модели.
Какое бы значение ни задал злоумышленник для поля формы Secret , оно будет
обновлено в базе данных. На следующем рисунке показано средство Fiddler, с
помощью которого в отправленные значения формы добавляется поле Secret со
значением "OverPost".
Значение "OverPost" успешно добавлено в свойство Secret вставленной строки.
Это происходит несмотря на то, что разработчик приложения не планировал, что
свойство Secret будет устанавливаться на странице создания.
Модель представления
Модели представления реализуют альтернативный подход к защите от чрезмерной
передачи данных.
Модель приложения часто называют моделью домена. Модель предметной
области обычно содержит все свойства, необходимые для соответствующей
сущности в базе данных. Модель представления содержит только те свойства,
которые необходимы для страницы пользовательского интерфейса, например
страницы Create.
Помимо модели представления в некоторых приложениях используется модель
привязки или модель ввода для передачи данных между классом модели страницы
Razor Pages и браузером.
Рассмотрим следующую модель представления StudentVM :
C#
public class StudentVM
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
}
В следующем коде модель представления используется StudentVM для создания
нового учащегося:
C#
[BindProperty]
public StudentVM StudentVM { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var entry = _context.Add(new Student());
entry.CurrentValues.SetValues(StudentVM);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Метод SetValues устанавливает значения этого объекта, считывая значения из
другого объекта PropertyValues. SetValues использует сопоставление имен свойств.
Тип модели представления:
не обязательно должен быть связан с типом модели;
должен иметь соответствующие свойства.
При использовании StudentVM требуется, чтобы страница Create использовала
StudentVM , а не Student :
CSHTML
@page
@model CreateVMModel
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Student</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger">
</div>
<div class="form-group">
<label asp-for="StudentVM.LastName" class="control-label">
</label>
<input asp-for="StudentVM.LastName" class="form-control" />
<span asp-validation-for="StudentVM.LastName" class="textdanger"></span>
</div>
<div class="form-group">
<label asp-for="StudentVM.FirstMidName" class="controllabel"></label>
<input asp-for="StudentVM.FirstMidName" class="form-control"
/>
<span asp-validation-for="StudentVM.FirstMidName"
class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StudentVM.EnrollmentDate" class="controllabel"></label>
<input asp-for="StudentVM.EnrollmentDate" class="formcontrol" />
<span asp-validation-for="StudentVM.EnrollmentDate"
class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary"
/>
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Обновление страницы редактирования
В файле Pages/Students/Edit.cshtml.cs замените методы OnGetAsync и OnPostAsync
приведенным ниже кодом.
C#
public async Task<IActionResult> OnGetAsync(int? id)
{
if (id == null)
{
return NotFound();
}
Student = await _context.Students.FindAsync(id);
if (Student == null)
{
return NotFound();
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int id)
{
var studentToUpdate = await _context.Students.FindAsync(id);
if (studentToUpdate == null)
{
return NotFound();
}
if (await TryUpdateModelAsync<Student>(
studentToUpdate,
"student",
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return Page();
}
Изменения в коде аналогичны странице Create за некоторыми исключениями:
FirstOrDefaultAsync заменен на FindAsync; Если включать связанные данные
не требуется, более эффективным будет метод FindAsync .
OnPostAsync имеет параметр id .
Текущий учащийся извлекается из базы данных вместо того, чтобы создавать
нового учащегося.
Запустите приложение и протестируйте его, создав и изменив учащегося.
Состояния сущностей
Контекст базы данных отслеживает синхронизацию сущностей в памяти с
соответствующими им строками в базе данных. Данные отслеживания определяют
поведение при вызове метода SaveChangesAsync. Например, при передаче новой
сущности в метод AddAsync ей присваивается состояние Added. При вызове
метода SaveChangesAsync контекст базы данных выполняет команду SQL INSERT .
Возможны следующие состояния сущности:
Added . Сущность еще не существует в базе данных. Метод SaveChanges
выполняет инструкцию INSERT .
Unchanged . изменения сущности не сохраняются. Сущность находится в этом
состоянии при считывании из базы данных.
Modified . Были изменены значения некоторых или всех свойств сущности.
Метод SaveChanges выполняет инструкцию UPDATE .
Deleted . Сущность отмечена для удаления. Метод SaveChanges выполняет
инструкцию DELETE .
Detached . Сущность не отслеживается контекстом базы данных.
В классическом приложении изменения состояния обычно осуществляются
автоматически. После считывания сущности и ее изменения ей автоматически
присваивается состояние Modified . При вызове метода SaveChanges создается
инструкция SQL UPDATE , которая обновляет только измененные свойства.
В веб-приложении объект DbContext , который считывает сущность и отображает ее
данные, ликвидируется после отрисовки страницы. При вызове метода страницы
OnPostAsync выполняется новый веб-запрос с новым экземпляром DbContext . Если
повторно считать сущность в этот новый контекст, будет смоделирована обработка
в классическом приложении.
Обновление страницы удаления
В этом разделе реализуется пользовательское сообщение об ошибке на случай
сбоя при вызове SaveChanges .
Замените код в Pages/Students/Delete.cshtml.cs следующим:
C#
using
using
using
using
using
using
using
ContosoUniversity.Models;
Microsoft.AspNetCore.Mvc;
Microsoft.AspNetCore.Mvc.RazorPages;
Microsoft.EntityFrameworkCore;
Microsoft.Extensions.Logging;
System;
System.Threading.Tasks;
namespace ContosoUniversity.Pages.Students
{
public class DeleteModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
private readonly ILogger<DeleteModel> _logger;
public DeleteModel(ContosoUniversity.Data.SchoolContext context,
ILogger<DeleteModel> logger)
{
_context = context;
_logger = logger;
}
[BindProperty]
public Student Student { get; set; }
public string ErrorMessage { get; set; }
public async Task<IActionResult> OnGetAsync(int? id, bool?
saveChangesError = false)
{
if (id == null)
{
return NotFound();
}
Student = await _context.Students
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (Student == null)
{
return NotFound();
}
if (saveChangesError.GetValueOrDefault())
{
ErrorMessage = String.Format("Delete {ID} failed. Try
again", id);
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int? id)
{
if (id == null)
{
return NotFound();
}
var student = await _context.Students.FindAsync(id);
if (student == null)
{
return NotFound();
}
try
{
_context.Students.Remove(student);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateException ex)
{
_logger.LogError(ex, ErrorMessage);
return RedirectToAction("./Delete",
new { id, saveChangesError = true });
}
}
}
}
Предыдущий код:
Добавляет возможность ведения журнала.
Добавляет необязательный параметр saveChangesError в сигнатуру метода
OnGetAsync . saveChangesError указывает, был ли метод вызван после того, как
произошел сбой при удалении объекта учащегося.
Операция удаления может завершиться сбоем из-за временных проблем с сетью.
Вероятность возникновения временных проблем с сетью выше, когда база данных
размещается в облаке. Параметр saveChangesError имеет значение false при
вызове метода OnGetAsync страницы удаления из пользовательского интерфейса.
Если OnGetAsync вызывается методом OnPostAsync из-за сбоя операции удаления,
параметру saveChangesError присваивается значение true .
Метод OnPostAsync извлекает выбранную сущность и вызывает метод Remove,
чтобы присвоить ей состояние Deleted . При вызове метода SaveChanges создается
инструкция SQL DELETE . В случае сбоя Remove :
Возникает исключение базы данных.
Вызывается метод OnGetAsync страницы Delete с параметром
saveChangesError=true .
Добавьте сообщение об ошибке в Pages/Students/Delete.cshtml :
CSHTML
@page
@model ContosoUniversity.Pages.Students.DeleteModel
@{
ViewData["Title"] = "Delete";
}
<h1>Delete</h1>
<p class="text-danger">@Model.ErrorMessage</p>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Student</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.LastName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.LastName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.FirstMidName)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.FirstMidName)
</dd>
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Student.EnrollmentDate)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Student.EnrollmentDate)
</dd>
</dl>
<form method="post">
<input type="hidden" asp-for="Student.ID" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-page="./Index">Back to List</a>
</form>
</div>
Запустите приложение и удалите учащегося, чтобы протестировать страницу
удаления.
Следующие шаги
Предыдущий учебник
Следующий учебник
Часть 3. Razor Страницы с EF Core
ASP.NET Core — сортировка,
фильтрация, разбиение на страницы
Статья • 28.01.2023 • Чтение занимает 26 мин
Авторы: Том Дайкстра
П. Смит
(Tom Dykstra), Джереми Ликнесс
(Jeremy Likness) и Йон
(Jon P Smith)
Веб-приложение Университета Contoso демонстрирует создание Razor вебприложений Pages с помощью EF Core Visual Studio. Сведения о серии руководств
см. в первом руководстве серии.
При возникновении проблем, которые вам не удается устранить, скачайте готовое
приложение
и сравните его код с тем, который вы создали в процессе работы с
этим руководством.
В этом учебнике вы добавите на страницу учащихся функции сортировки,
фильтрации и разбиения на страницы.
На следующем рисунке показана готовая страница. Заголовки столбцов являются
ссылками, щелкнув которые, можно отсортировать столбец. Щелкайте заголовок
столбца для переключения между сортировкой по возрастанию и убыванию.
Добавление сортировки
Чтобы добавить сортировку, замените код в файле Pages/Students/Index.cshtml.cs
на приведенный ниже.
C#
public class IndexModel : PageModel
{
private readonly SchoolContext _context;
public IndexModel(SchoolContext context)
{
_context = context;
}
public
public
public
public
string
string
string
string
NameSort { get; set; }
DateSort { get; set; }
CurrentFilter { get; set; }
CurrentSort { get; set; }
public IList<Student> Students { get; set; }
public async
{
// using
NameSort
DateSort
Task OnGetAsync(string sortOrder)
System;
= String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
= sortOrder == "Date" ? "date_desc" : "Date";
IQueryable<Student> studentsIQ = from s in _context.Students
select s;
switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s =>
s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}
Students = await studentsIQ.AsNoTracking().ToListAsync();
}
}
Предыдущий код:
Требует добавления using System; .
добавляет свойства, которые будут содержать параметры сортировки;
изменяет имя свойства Student на Students ;
заменяет код в методе OnGetAsync .
Метод OnGetAsync принимает параметр sortOrder из строки запроса в URL-адресе.
URL-адрес и строка запроса формируются вспомогательной функцией тегов
привязки.
Параметр sortOrder имеет значение Name или Date . После параметра sortOrder
может стоять _desc , чтобы указать порядок по убыванию. По умолчанию
используется порядок сортировки по возрастанию.
При запросе страницы "Index" по ссылке Students строка запроса отсутствует.
Учащиеся отображаются по фамилии в порядке возрастания. В операторе default
сортировка по фамилии в порядке возрастания используется по умолчанию
( switch ). Когда пользователь щелкает ссылку заголовка столбца, в строке запроса
указывается соответствующее значение sortOrder .
Для формирования гиперссылок в заголовках столбцов страница Razor использует
NameSort и DateSort с соответствующими значениями строки запроса:
C#
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
В коде используется условный оператор C# ?:. Оператор ?: является тернарным
(принимает три операнда). Первая строка указывает, что, когда sortOrder равен
null или пуст, NameSort имеет значение name_desc . Если sortOrder не является NULL
или пустым, для NameSort задается пустая строка.
Следующие два оператора устанавливают гиперссылки в заголовках столбцов на
странице следующим образом:
Текущий порядок сортировки
Гиперссылка "Last Name"
(Фамилия)
Гиперссылка "Date"
(Дата)
"Last Name" (Фамилия) по
descending
ascending
"Last Name" (Фамилия) по
убыванию
ascending
ascending
"Date" (Дата) по возрастанию
ascending
descending
"Date" (Дата) по убыванию
ascending
ascending
возрастанию
Для указания столбца, по которому выполняется сортировка, этот метод использует
LINQ to Entities. Код инициализирует IQueryable<Student> до оператора switch и
изменяет его в этом операторе:
C#
IQueryable<Student> studentsIQ = from s in _context.Students
select s;
switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}
Students = await studentsIQ.AsNoTracking().ToListAsync();
При создании или изменении IQueryable запрос в базу данных не отправляется.
Запрос не выполнится, пока объект IQueryable не будет преобразован в
коллекцию. IQueryable преобразуются в коллекцию путем вызова метода, такого
как ToListAsync . Таким образом, код IQueryable создает одиночный запрос,
который не выполняется до следующего оператора:
C#
Students = await studentsIQ.AsNoTracking().ToListAsync();
OnGetAsync можно расширить на случай большого числа сортируемых столбцов.
Сведения об альтернативном способе программирования этой функциональности
см. в статье Использование динамических запросов LINQ для упрощения кода в
версии этой серии учебников для MVC.
Добавление гиперссылок для заголовков столбцов на
странице индексов учащихся
Замените код в Students/Index.cshtml следующим: Изменения выделены.
CSHTML
@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Students";
}
<h2>Students</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort">
@Html.DisplayNameFor(model =>
model.Students[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model =>
model.Students[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort">
@Html.DisplayNameFor(model =>
model.Students[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Students)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-routeid="@item.ID">Details</a> |
<a asp-page="./Delete" asp-routeid="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Предыдущий код:
Добавляет гиперссылки в заголовки столбцов LastName и EnrollmentDate .
Использует эти сведения в NameSort и DateSort для настройки гиперссылок с
использованием текущих значений порядка сортировки.
Изменяет заголовок страницы с Index (Индекс) на Students (Учащиеся).
Изменяет Model.Student на Model.Students .
Чтобы проверить работу сортировки, сделайте следующее:
Запустите приложение и откройте вкладку Students (Учащиеся).
Щелкните заголовки столбцов.
Добавление фильтрации
Для добавления фильтрации на страницу индекса учащихся:
На страницу Razor добавляется текстовое поле и кнопка отправки. Текстовое
поле предоставляет строку поиска для имени или фамилии.
Страничная модель обновляется для использования значения из текстового
поля.
Обновление метода OnGetAsync
Чтобы добавить фильтрацию, замените код в файле Students/Index.cshtml.cs на
приведенный ниже.
C#
public class IndexModel : PageModel
{
private readonly SchoolContext _context;
public IndexModel(SchoolContext context)
{
_context = context;
}
public
public
public
public
string
string
string
string
NameSort { get; set; }
DateSort { get; set; }
CurrentFilter { get; set; }
CurrentSort { get; set; }
public IList<Student> Students { get; set; }
public async Task OnGetAsync(string sortOrder, string searchString)
{
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
CurrentFilter = searchString;
IQueryable<Student> studentsIQ = from s in _context.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
studentsIQ = studentsIQ.Where(s =>
s.LastName.Contains(searchString)
||
s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s => s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s =>
s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}
Students = await studentsIQ.AsNoTracking().ToListAsync();
}
}
Предыдущий код:
Добавляет параметр searchString в метод OnGetAsync и сохраняет значение
параметра в свойстве CurrentFilter . Значение строки поиска получается из
текстового поля, добавляемого в следующем разделе.
Добавляет в инструкцию LINQ предложение Where . Это предложение Where
отбирает только учащихся, чье имя или фамилия содержат строку поиска.
Оператор LINQ выполняется, только если задано значение для поиска.
IQueryable и IEnumerable
Код вызывает метод Where объекта IQueryable , при этом фильтр обрабатывается
на сервере. В некоторых случаях приложение может вызывать метод Where как
метод расширения для коллекции в памяти. Например, предположим, что
_context.Students изменения из EF Core DbSet метода репозитория,
возвращающего коллекцию IEnumerable . Обычно результат остается прежним, но
в некоторых случаях он может отличаться.
Например, реализация Contains в .NET Framework по умолчанию выполняет
сравнение с учетом регистра. В SQL Server Contains учет регистра определяется
параметрами сортировки у экземпляра SQL Server. По умолчанию SQL Server не
учитывает регистр. В SQLite по умолчанию регистр учитывается. Можно вызвать
ToUpper , чтобы явно велеть тесту не учитывать регистр.
C#
Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())`
Приведенный выше код гарантирует, что фильтр не учитывает регистр, даже если
метод Where вызывается для IEnumerable или выполняется в SQLite.
Когда Contains вызывается для коллекции IEnumerable , используется реализация
.NET Core. Когда Contains вызывается для объекта IQueryable , используется
реализация базы данных.
Вызов Contains для IQueryable обычно предпочтительнее по соображениям
производительности. При использовании IQueryable фильтрация выполняется
сервером базы данных. Если сначала создается IEnumerable , все строки должны
возвращаться с сервера базы данных.
Вызов ToUpper снижает производительность. Код ToUpper добавляет функцию в
предложение WHERE TSQL-оператора SELECT. Добавленная функция не позволяет
оптимизатору использовать индекс. Учитывая, что SQL устанавливается без учета
регистра, рекомендуется не использовать вызов ToUpper , когда он не требуется.
Дополнительные сведения см. в статье Использование запроса без учета регистра с
поставщиком SQLite .
Обновление страницы Razor
Замените код в файле Pages/Students/Index.cshtml , чтобы создать кнопку Search
(Поиск).
CSHTML
@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Students";
}
<h2>Students</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<form asp-page="./Index" method="get">
<div class="form-actions no-color">
<p>
Find by name:
<input type="text" name="SearchString"
value="@Model.CurrentFilter" />
<input type="submit" value="Search" class="btn btn-primary" /> |
<a asp-page="./Index">Back to full List</a>
</p>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort">
@Html.DisplayNameFor(model =>
model.Students[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model =>
model.Students[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort">
@Html.DisplayNameFor(model =>
model.Students[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Students)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-routeid="@item.ID">Details</a> |
<a asp-page="./Delete" asp-routeid="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Для добавления кнопки и поля поиска предыдущий код использует
вспомогательную функцию тегов <form> . По умолчанию вспомогательная функция
тегов <form> отправляет данные формы с помощью POST. При этом параметры
передаются в тексте сообщения HTTP, а не в URL-адресе. При использовании HTTP
GET данные формы передаются в виде строк запроса в URL-адресе. Передача
данных со строками запроса позволяет пользователям добавлять URL-адрес в
закладки. Руководства консорциума W3C
рекомендуют использовать GET, когда
действие не приводит к обновлению.
Проверьте работу приложения:
Выберите вкладку Students (Учащиеся) и введите строку поиска. При
использовании SQLite фильтр не учитывает регистр, только если вы
реализовали необязательный код ToUpper , приведенный ранее.
Выберите Search (Поиск).
Обратите внимание, что URL-адрес содержит строку поиска. Пример:
browser-address-bar
https://localhost:5001/Students?SearchString=an
Если страница добавлена в закладки, закладка содержит URL-адрес страницы и
строку запроса SearchString . Формирование строки запроса обеспечивает
method="get" в теге form .
Сейчас при выборе ссылки сортировки заголовка столбца теряется значение
фильтра в поле Search (Поиск). Потерянное значение фильтра исправляется в
следующем разделе.
Добавление разбиения по страницам
В этом разделе создается класс PaginatedList для поддержки разбиения на
страницы. Класс PaginatedList использует операторы Skip и Take для фильтрации
данных на сервере вместо того, чтобы извлекать все строки таблицы. На
следующем рисунке показаны кнопки перелистывания.
Создание класса PaginatedList
В папке проекта создайте файл PaginatedList.cs со следующим кодом:
C#
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
Microsoft.EntityFrameworkCore;
namespace ContosoUniversity
{
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }
public PaginatedList(List<T> items, int count, int pageIndex, int
pageSize)
{
PageIndex = pageIndex;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
this.AddRange(items);
}
public bool HasPreviousPage => PageIndex > 1;
public bool HasNextPage => PageIndex < TotalPages;
public static async Task<PaginatedList<T>> CreateAsync(
IQueryable<T> source, int pageIndex, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip(
(pageIndex - 1) * pageSize)
.Take(pageSize).ToListAsync();
return new PaginatedList<T>(items, count, pageIndex, pageSize);
}
}
}
В предыдущем коде метод CreateAsync принимает размер и номер страницы и
вызывает соответствующие методы Skip и Take объекта IQueryable . Метод
ToListAsync объекта IQueryable при вызове возвращает список, содержащий
только запрошенную страницу. Для включения и отключения кнопок
перелистывания страниц Previous (Назад) и Next (Далее) используются свойства
HasPreviousPage и HasNextPage .
Метод CreateAsync используется для создания PaginatedList<T> . Конструктор не
позволяет создать объект PaginatedList<T> , так как конструкторы не могут
выполнять асинхронный код.
Добавление размера страницы в конфигурацию
Добавьте PageSize в файл appsettings.json конфигурации:
JSON
{
"PageSize": 3,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=CU1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
Добавление разбиения по страницам в IndexModel
Чтобы добавить разбиение на страницы, замените код в файле
Students/Index.cshtml.cs .
C#
using
using
using
using
using
using
using
using
ContosoUniversity.Data;
ContosoUniversity.Models;
Microsoft.AspNetCore.Mvc.RazorPages;
Microsoft.EntityFrameworkCore;
Microsoft.Extensions.Configuration;
System;
System.Linq;
System.Threading.Tasks;
namespace ContosoUniversity.Pages.Students
{
public class IndexModel : PageModel
{
private readonly SchoolContext _context;
private readonly IConfiguration Configuration;
public IndexModel(SchoolContext context, IConfiguration
configuration)
{
_context = context;
Configuration = configuration;
}
public
public
public
public
string
string
string
string
NameSort { get; set; }
DateSort { get; set; }
CurrentFilter { get; set; }
CurrentSort { get; set; }
public PaginatedList<Student> Students { get; set; }
public async Task OnGetAsync(string sortOrder,
string currentFilter, string searchString, int? pageIndex)
{
CurrentSort = sortOrder;
NameSort = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
DateSort = sortOrder == "Date" ? "date_desc" : "Date";
if (searchString != null)
{
pageIndex = 1;
}
else
{
searchString = currentFilter;
}
CurrentFilter = searchString;
IQueryable<Student> studentsIQ = from s in _context.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
studentsIQ = studentsIQ.Where(s =>
s.LastName.Contains(searchString)
||
s.FirstMidName.Contains(searchString));
}
switch (sortOrder)
{
case "name_desc":
studentsIQ = studentsIQ.OrderByDescending(s =>
s.LastName);
break;
case "Date":
studentsIQ = studentsIQ.OrderBy(s => s.EnrollmentDate);
break;
case "date_desc":
studentsIQ = studentsIQ.OrderByDescending(s =>
s.EnrollmentDate);
break;
default:
studentsIQ = studentsIQ.OrderBy(s => s.LastName);
break;
}
var pageSize = Configuration.GetValue("PageSize", 4);
Students = await PaginatedList<Student>.CreateAsync(
studentsIQ.AsNoTracking(), pageIndex ?? 1, pageSize);
}
}
}
Предыдущий код:
изменяет тип свойства Students с IList<Student> на PaginatedList<Student> ;
добавляет индекс страницы, текущий порядок sortOrder и фильтр
currentFilter в сигнатуру метода OnGetAsync ;
сохраняет порядок сортировки в свойстве CurrentSort ;
сбрасывает индекс страницы в значение 1 при получении новой строки
поиска;
использует класс PaginatedList для получения сущностей Student.
Задает для pageSize значение 3 из файла конфигурации или 4, если настройка
завершается сбоем.
Все параметры, получаемые методом OnGetAsync , равны NULL, когда:
Страница вызывается по ссылке Students (Учащиеся).
Пользователь не открывал ссылку перелистывания или сортировки.
При выборе ссылки перелистывания переменная индекса страницы содержит
номер страницы для отображения.
Свойство CurrentSort предоставляет странице Razor текущий порядок сортировки.
Текущий порядок сортировки нужно включить в ссылки перелистывания, чтобы
сохранить его при смене страницы.
Свойство CurrentFilter предоставляет странице Razor текущую строку фильтра.
Значение CurrentFilter :
Нужно включить в ссылки для перелистывания, чтобы сохранить параметры
фильтра при смене страницы.
Нужно восстановить в текстовом поле после обновления страницы.
Если строка поиска изменяется во время перелистывания, номер страницы
сбрасывается в значение 1. Номер страницы должен быть сброшен на 1, так как с
новым фильтром может измениться состав отображаемых данных. Если введено
значение для поиска и нажата кнопка Submit (Отправить):
Строка поиска изменяется.
Значение параметра searchString отличается от null.
Метод PaginatedList.CreateAsync преобразует результат запроса учащихся в
отдельную страницу коллекции, поддерживающую разбиение на страницы. Эта
страница с учащимися передается на страницу Razor.
Два вопросительных знака после pageIndex в вызове PaginatedList.CreateAsync
являются оператором объединения с NULL. Оператор объединения с null
определяет значение по умолчанию для типа, допускающего значение null.
Выражение pageIndex ?? 1 возвращает значение свойства pageIndex , если оно есть.
В противном случае возвращается значение 1.
Добавление ссылок для разбиения по страницам
Замените код в Students/Index.cshtml следующим: Изменения выделены:
CSHTML
@page
@model ContosoUniversity.Pages.Students.IndexModel
@{
ViewData["Title"] = "Students";
}
<h2>Students</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<form asp-page="./Index" method="get">
<div class="form-actions no-color">
<p>
Find by name:
<input type="text" name="SearchString"
value="@Model.CurrentFilter" />
<input type="submit" value="Search" class="btn btn-primary" /> |
<a asp-page="./Index">Back to full List</a>
</p>
</div>
</form>
<table class="table">
<thead>
<tr>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model =>
model.Students[0].LastName)
</a>
</th>
<th>
@Html.DisplayNameFor(model =>
model.Students[0].FirstMidName)
</th>
<th>
<a asp-page="./Index" asp-route-sortOrder="@Model.DateSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model =>
model.Students[0].EnrollmentDate)
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Students)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-routeid="@item.ID">Details</a> |
<a asp-page="./Delete" asp-routeid="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
@{
var prevDisabled = !Model.Students.HasPreviousPage ? "disabled" : "";
var nextDisabled = !Model.Students.HasNextPage ? "disabled" : "";
}
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex - 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @nextDisabled">
Next
</a>
Ссылки в заголовках столбцов передают текущую строку поиска в метод
OnGetAsync с помощью строки запроса:
CSHTML
<a asp-page="./Index" asp-route-sortOrder="@Model.NameSort"
asp-route-currentFilter="@Model.CurrentFilter">
@Html.DisplayNameFor(model => model.Students[0].LastName)
</a>
Кнопки перелистывания отображаются вспомогательными функциями тегов:
CSHTML
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex - 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @prevDisabled">
Previous
</a>
<a asp-page="./Index"
asp-route-sortOrder="@Model.CurrentSort"
asp-route-pageIndex="@(Model.Students.PageIndex + 1)"
asp-route-currentFilter="@Model.CurrentFilter"
class="btn btn-primary @nextDisabled">
Next
</a>
Запустите приложение и перейдите на страницу учащихся.
Чтобы убедиться, что разбиение на страницы работает, нажимайте кнопки
перелистывания при различном порядке сортировки.
Чтобы убедиться, что разбиение на страницы работает корректно вместе с
сортировкой и фильтрацией, введите строку поиска и попробуйте
перелистнуть страницу.
Группирование
В этом разделе показано, как создать страницу общих сведений About , на которой
показано количество учащихся, зарегистрированных на каждую дату. Это
изменение использует группирование и включает следующие шаги:
Создание модели представления для данных, используемых страницей About
(Сведения).
Обновите страницу About для использования модели представления.
Создание модели представления
Создайте папку Models/SchoolViewModels.
Создайте SchoolViewModels/EnrollmentDateGroup.cs , используя следующий код:
C#
using System;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class EnrollmentDateGroup
{
[DataType(DataType.Date)]
public DateTime? EnrollmentDate { get; set; }
public int StudentCount { get; set; }
}
}
Создание Razor страницы
Создайте файл Pages/About.cshtml со следующим кодом:
CSHTML
@page
@model ContosoUniversity.Pages.AboutModel
@{
ViewData["Title"] = "Student Body Statistics";
}
<h2>Student Body Statistics</h2>
<table>
<tr>
<th>
Enrollment Date
</th>
<th>
Students
</th>
</tr>
@foreach (var item in Model.Students)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
@item.StudentCount
</td>
</tr>
}
</table>
Создание модели страницы
Обновите файл Pages/About.cshtml.cs , используя следующий код:
C#
using
using
using
using
using
using
using
using
ContosoUniversity.Models.SchoolViewModels;
ContosoUniversity.Data;
Microsoft.AspNetCore.Mvc.RazorPages;
Microsoft.EntityFrameworkCore;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
ContosoUniversity.Models;
namespace ContosoUniversity.Pages
{
public class AboutModel : PageModel
{
private readonly SchoolContext _context;
public AboutModel(SchoolContext context)
{
_context = context;
}
public IList<EnrollmentDateGroup> Students { get; set; }
public async Task OnGetAsync()
{
IQueryable<EnrollmentDateGroup> data =
from student in _context.Students
group student by student.EnrollmentDate into dateGroup
select new EnrollmentDateGroup()
{
EnrollmentDate = dateGroup.Key,
StudentCount = dateGroup.Count()
};
Students = await data.AsNoTracking().ToListAsync();
}
}
}
Запрос LINQ группирует записи из таблицы студентов по дате зачисления,
вычисляет число записей в каждой группе и сохраняет результаты в коллекцию
объектов моделей представления EnrollmentDateGroup .
Запустите приложение и перейдите на страницу "About" (О программе).
Количество зачисленных студентов по дням отображается в таблице.
Дальнейшие действия
В следующем руководстве приложение использует миграции для обновления
модели данных.
Предыдущий учебник
Следующий учебник
Часть 4. Razor Страницы с EF Core
миграцией в ASP.NET Core
Статья • 28.01.2023 • Чтение занимает 10 мин
Авторы: Том Дайкстра
Андерсон
(Tom Dykstra), Йон П. Смит
(Jon P Smith) и Рик
(Rick Anderson)
Веб-приложение Университета Contoso демонстрирует создание Razor вебприложений Pages с помощью EF Core Visual Studio. Сведения о серии руководств
см. в первом руководстве серии.
При возникновении проблем, которые вам не удается устранить, скачайте готовое
приложение
и сравните его код с тем, который вы создали в процессе работы с
этим руководством.
В этом руководстве описана EF Core функция миграции для управления
изменениями модели данных.
При разработке нового приложения модель данных часто изменяется. При каждом
изменении нарушается синхронизация модели с базой данных. Вы начали работу с
этой серией учебников с настройки платформы Entity Framework для создания базы
данных, если она еще не существует. При каждом изменении модели данных базу
данных необходимо удалять. При следующем запуске приложения вызов
EnsureCreated повторно создает базу данных в соответствии с новой моделью
данных. Затем выполняется класс DbInitializer для заполнения новой базы
данных.
Такой подход к обеспечению синхронизации базы данных с использованием
модели данных хорошо работает, пока приложение не нужно развертывать в
рабочей среде. Приложение, выполняющееся в рабочей среде, обычно содержит
данные. Приложение не может запускаться с тестовой базой данных каждый раз
при внесении изменений (например, при добавлении столбца). Функция EF Core
миграции решает эту проблему, позволяя EF Core обновить схему базы данных
вместо создания новой базы данных.
Вместо удаления и повторного создания базы данных при изменении модели
функция миграций обновляет схему, сохраняя существующие данные.
7 Примечание
Ограничения SQLite
В этом руководстве используется функция миграции Entity Framework Core, где
это возможно. Во время миграции обновляется схема базы данных в
соответствии с изменениями в модели данных. Однако миграции могут
вносить только такие изменения, которые поддерживает ядро СУБД, а
возможности изменения схемы SQLite ограничены. Например, добавление
столбца поддерживается, но удаление столбца не поддерживается. Если
миграция создается для удаления столбца, команда ef migrations add
выполняется успешно, а команда ef database update — нет.
Обходной путь для ограничений SQLite — вручную написать код миграции для
перестроения таблицы в случае изменений. Код для миграции находится в
методах Up и Down и выполняет следующие действия:
Создание новой таблицы.
Копирование данных из старой таблицы в новую.
Удаление старой таблицы.
Переименование новой таблицы.
Написание кода такого рода для конкретной базы данных выходит за рамки
данного учебника. Вместо этого в данном учебнике база данных удаляется и
создается повторно, когда попытка применить миграцию завершается сбоем.
Дополнительные сведения см. в следующих ресурсах:
Ограничения поставщика базы данных SQLite EF Core
Настройка кода миграции
Присвоение начальных значений данных
Инструкция по ALTER TABLE SQLite
Удаление базы данных
Visual Studio
Используйте обозреватель объектов SQL Server, чтобы удалить базу данных,
или выполните следующую команду в консоли диспетчера пакетов (PMC):
PowerShell
Drop-Database
Создание первоначальной миграции
Visual Studio
Выполните следующие команды в PMC:
PowerShell
Add-Migration InitialCreate
Update-Database
Удаление EnsureCreated
Эта серия учебников началась с использования EnsureCreated. Метод EnsureCreated
не создает таблицу журнала миграций и поэтому не может использоваться с
функцией миграций. Он предназначен для тестирования или быстрого создания
прототипов, когда часто требуется удалять и повторно создавать базу данных.
Начиная с этого момента в учебниках будут использоваться миграции.
Удалите Program.cs следующую строку:
C#
context.Database.EnsureCreated();
Запустите приложение и убедитесь в том, что база данных заполняется.
Методы Up и Down
Команда EF Core migrations add сгенерирована кодом для создания базы данных.
Код миграций содержится в файле Migrations\<timestamp>_InitialCreate.cs . Метод
Up класса InitialCreate создает таблицы базы данных, соответствующие наборам
сущностей модели данных. Метод Down удаляет их, как показано в следующем
примере:
C#
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace ContosoUniversity.Migrations
{
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Course",
columns: table => new
{
CourseID = table.Column<int>(nullable: false),
Title = table.Column<string>(nullable: true),
Credits = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Course", x => x.CourseID);
});
migrationBuilder.CreateTable(
name: "Student",
columns: table => new
{
ID = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy",
SqlServerValueGenerationStrategy.IdentityColumn),
LastName = table.Column<string>(nullable: true),
FirstMidName = table.Column<string>(nullable: true),
EnrollmentDate = table.Column<DateTime>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Student", x => x.ID);
});
migrationBuilder.CreateTable(
name: "Enrollment",
columns: table => new
{
EnrollmentID = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy",
SqlServerValueGenerationStrategy.IdentityColumn),
CourseID = table.Column<int>(nullable: false),
StudentID = table.Column<int>(nullable: false),
Grade = table.Column<int>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Enrollment", x => x.EnrollmentID);
table.ForeignKey(
name: "FK_Enrollment_Course_CourseID",
column: x => x.CourseID,
principalTable: "Course",
principalColumn: "CourseID",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Enrollment_Student_StudentID",
column: x => x.StudentID,
principalTable: "Student",
principalColumn: "ID",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Enrollment_CourseID",
table: "Enrollment",
column: "CourseID");
migrationBuilder.CreateIndex(
name: "IX_Enrollment_StudentID",
table: "Enrollment",
column: "StudentID");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Enrollment");
migrationBuilder.DropTable(
name: "Course");
migrationBuilder.DropTable(
name: "Student");
}
}
}
Приведенный выше код предназначен для первоначальной миграции. Код.
был создан командой migrations add InitialCreate ;
выполняется командой database update ;
создает базу данных для модели данных, заданной классом контекста базы
данных.
Параметр имени миграции (в примере это InitialCreate ) используется в качестве
имени файла. В качестве имени миграции можно использовать любое допустимое
имя файла. Рекомендуется выбрать слово или фразу, которые кратко описывают
назначение миграции. Например, миграция, обеспечивающая добавление таблицы
кафедр, может называться "AddDepartmentTable".
Таблица журнала миграции
Используйте SSOX или средство SQLite для просмотра базы данных.
Обратите внимание на добавление таблицы __EFMigrationsHistory . Таблица
__EFMigrationsHistory используется для отслеживания миграций, которые
были применены к базе данных.
Просмотрите данные в таблице __EFMigrationsHistory . Вы увидите одну строку
для первой миграции.
Моментальный снимок модели данных
Миграции создают моментальный снимок текущей модели данных в
Migrations/SchoolContextModelSnapshot.cs . После добавления миграции EF
определяет изменения, сравнивая текущую модель данных с файлом
моментального снимка.
Так как файл моментального снимка отслеживает состояние модели данных,
миграцию нельзя удалить, удалив файл <timestamp>_<migrationname>.cs . Чтобы
отменить последнюю миграцию, используйте команду migrations remove.
migrations remove удаляет миграцию и гарантирует корректный сброс
моментального снимка. Дополнительные сведения см. в разделе dotnet ef
migrations remove.
См. дополнительные сведения в статье Сброс всех миграций.
Применение миграций в рабочей среде
Для рабочих приложений не рекомендуется вызывать Database.Migrate при
запуске приложения. Migrate не следует вызывать из приложения, развернутого в
ферме серверов. Если приложение масштабируется в нескольких экземплярах
сервера, трудно гарантировать, что изменения схемы базы данных не будут
выполняться с нескольких серверов и не будут конфликтовать с обращениями для
чтения или записи.
Миграция базы данных должна выполняться контролируемым способом в рамках
развертывания. Подход к миграции рабочей базы данных включает следующее:
Использование миграций для создания сценариев SQL и их использования в
развертывании.
Выполнение dotnet ef database update из контролируемой среды.
Устранение неполадок
Если приложение использует SQL Server LocalDB и выводит следующее
исключение:
text
SqlException: Cannot open database "ContosoUniversity" requested by the
login.
The login failed.
Login failed for user 'user name'.
решением может быть выполнение dotnet ef database update из командной
строки.
Дополнительные ресурсы
EF Core CLI.
Команды CLI для миграций dotnet EF
Консоль диспетчера пакетов (Visual Studio)
Следующие шаги
В следующем руководстве продолжается построение модели данных путем
добавления свойств сущностей и новых сущностей.
Предыдущий учебник
Следующий учебник
Часть 5. Razor Страницы с EF Core
ASP.NET Core — модель данных
Статья • 28.01.2023 • Чтение занимает 73 мин
Авторы: Том Дайкстра
П. Смит
(Tom Dykstra), Джереми Ликнесс
(Jeremy Likness) и Йон
(Jon P Smith)
Веб-приложение Университета Contoso демонстрирует создание Razor вебприложений Pages с помощью EF Core Visual Studio. Сведения о серии руководств
см. в первом руководстве серии.
При возникновении проблем, которые вам не удается устранить, скачайте готовое
приложение
и сравните его код с тем, который вы создали в процессе работы с
этим руководством.
Предыдущие руководства работали с базовой моделью данных, состоящей из трех
сущностей. В этом руководстве:
Добавляются дополнительные сущности и связи.
Настраивается модель данных с помощью указания правил для
форматирования, проверки и сопоставления базы данных.
Готовая модель данных показана на следующем рисунке:
Следующая диаграмма базы данных создана с помощью средства Dataedo :
Чтобы создать диаграмму базы данных с помощью Dataedo, выполните следующие
действия:
Развертывание приложения в Azure
Скачайте и установите Dataedo
на своем компьютере.
Следуйте инструкциям руководства Создание документации для Базы данных
SQL Azure за 5 минут .
На предыдущей диаграмме Dataedo элемент CourseInstructor — это таблица
соединений, созданная Entity Framework. Дополнительные сведения см. в разделе
Многие ко многим.
Сущность Student
Замените код в Models/Student.cs следующим:
C#
using
using
using
using
System;
System.Collections.Generic;
System.ComponentModel.DataAnnotations;
System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than
50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
public ICollection<Enrollment> Enrollments { get; set; }
}
}
В приведенном выше коде добавляется свойство FullName и добавляются
следующие атрибуты к существующим свойствам:
[DataType]
[DisplayFormat]
[StringLength]
[Column]
[Required]
[Display]
Вычисляемое свойство FullName
FullName — это вычисляемое свойство, которое возвращает значение, созданное
путем объединения двух других свойств. FullName не может быть задано, поэтому
имеет только метод доступа get. В базе данных не создается столбец FullName .
Атрибут DataType
C#
[DataType(DataType.Date)]
Сейчас для дат зачисления учащихся на всех страницах отображаются время и
дата, хотя важна только дата. Используя атрибуты заметок к данным, вы можете
внести в код одно изменение, позволяющее исправить формат отображения на
каждой странице, где отображаются эти данные.
Атрибут DataType указывает тип данных более точно по сравнению со встроенным
типом базы данных. В этом случае следует отображать отобразить только дату, а не
дату и время. В перечислении DataType представлено множество типов данных,
таких как Date, Time, PhoneNumber, Currency, EmailAddress и других. Атрибут
DataType также обеспечивает автоматическое предоставление функций для
определенных типов в приложении. Пример:
Ссылка mailto: для DataType.EmailAddress создается автоматически.
Средство выбора даты предоставляется для DataType.Date в большинстве
браузеров.
Атрибут DataType создает атрибуты HTML 5 data- . Атрибуты DataType не
предназначены для проверки.
Атрибут DisplayFormat
C#
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode =
true)]
DataType.Date не задает формат отображаемой даты. По умолчанию поле даты
отображается с использованием форматов, установленных в CultureInfo сервера.
С помощью атрибута DisplayFormat можно явно указать формат даты: Параметр
ApplyFormatInEditMode указывает, что формат должен применяться к
пользовательскому интерфейсу редактирования. Некоторым полям не следует
использовать ApplyFormatInEditMode . Например, обозначение денежной единицы в
общем случае не должно отображаться в поле редактирования текста.
Атрибут DisplayFormat можно использовать отдельно. Однако чаще всего DataType
рекомендуется применять вместе с атрибутом DisplayFormat . Атрибут DataType
передает семантику данных (в отличие от способа их вывода на экран). Атрибут
DataType дает следующие преимущества, недоступные в DisplayFormat :
Поддержка функций HTML5 в браузере. Например, отображение элемента
управления календарем, соответствующего языковому стандарту обозначения
денежной единицы, ссылок электронной почты, проверки входных данных на
стороне клиента.
По умолчанию формат отображения данных в браузере определяется в
соответствии с установленным языковым стандартом.
Дополнительные сведения см. в документации по вспомогательной функции тегов
<input>.
Атрибут StringLength
C#
[StringLength(50, ErrorMessage = "First name cannot be longer than 50
characters.")]
С помощью атрибутов можно указать правила проверки данных и сообщения об
ошибках проверки. Атрибут StringLength указывает минимальную и максимальную
длину символов, разрешенных в поле данных. Представленный код задает
ограничение длины имен в 50 символов. Пример, в котором задается минимальная
длина строки, приводится далее.
Атрибут StringLength также обеспечивает проверку на стороне клиента и на
стороне сервера. Минимальное значение не оказывает влияния на схему базы
данных.
Атрибут StringLength не запрещает пользователю ввести пробел в качестве имени
пользователя. Атрибут RegularExpression можно использовать для применения
ограничений к входным данным. Например, следующий код требует, чтобы
первый символ был прописной буквой, а остальные символы были буквенными:
C#
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
Visual Studio
В обозревателе объектов SQL Server (SSOX) откройте конструктор таблиц
учащихся, дважды щелкнув таблицу Student (Учащийся).
На предыдущем изображении показана схемы для таблицы Student . Поля
имен имеют тип nvarchar(MAX) . Когда далее в этом учебнике будет создана и
применена миграция, поля имен станут nvarchar(50) из-за атрибутов длины
строки.
Атрибут Column
C#
[Column("FirstName")]
public string FirstMidName { get; set; }
Атрибуты могут управлять, как именно классы и свойства сопоставляются с базой
данных. В модели Student атрибут Column используется для сопоставления имени
свойства FirstMidName с "FirstName" в базе данных.
При создании базы данных имена свойств в модели используются для имен
столбцов (кроме случая, когда используется атрибут Column ). Модель Student
использует FirstMidName для поля имени, так как это поле также может содержать
отчество.
С атрибутом [Column] поле Student.FirstMidName в модели данных сопоставляется
со столбцом FirstName таблицы Student . Добавление атрибута Column изменяет
модель для поддержки SchoolContext . Модель, поддерживающая SchoolContext ,
больше не соответствует базе данных. Это несоответствие будет устранено путем
добавления миграции далее в этом учебнике.
Атрибут Required
C#
[Required]
Атрибут Required делает свойства имен обязательными полями. Атрибут Required
не нужен для типов, не допускающих значения NULL, например для типов
значений (таких как DateTime , int и double ). Типы, которые не могут принимать
значение null, автоматически обрабатываются как обязательные поля.
Для применения MinimumLength нужно использовать атрибут Required с
MinimumLength .
C#
[Display(Name = "Last Name")]
[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }
MinimumLength и Required разрешают использовать пробелы при проверке.
Используйте атрибут RegularExpression для полного контроля над строкой.
Атрибут Display
C#
[Display(Name = "Last Name")]
Атрибут Display указывает, что заголовки для текстовых полей должны иметь вид
"First Name" (Имя), "Last Name" (Фамилия), "Full Name" (Полное имя) и "Enrollment
Date" (Дата зачисления). По умолчанию в заголовках не использовался пробел для
разделения слов, например "Lastname".
Создание миграции
Запустите приложение и перейдите на страницу Students. Возникает исключение.
Атрибут [Column] приводит к тому, что EF ожидает столбец с именем FirstName , но
имя столбца в базе данных по-прежнему FirstMidName .
Visual Studio
Сообщение об ошибке подобно приведенному ниже.
SqlException: Invalid column name 'FirstName'.
There are pending model changes
Pending model changes are detected in the following:
SchoolContext
В PMC введите следующие команды для создания миграции и
обновления базы данных:
PowerShell
Add-Migration ColumnFirstName
Update-Database
Первая из этих команд выдает следующее предупреждающее сообщение:
text
An operation was scaffolded that may result in the loss of data.
Please review the migration for accuracy.
Это предупреждение вызвано тем, что поля имен теперь ограничены 50
символами. Если имя в базе данных имеет больше 50 символов, символы
с 51-го до последнего будут потеряны.
Откройте таблицу "Student" (Учащийся) в окне SSOX:
До применения миграции столбцы имен имели тип nvarchar(MAX). Теперь
столбцы имен имеют тип nvarchar(50) . Имя столбца изменилось с
FirstMidName на FirstName .
Запустите приложение и перейдите на страницу Students.
Обратите внимание, что значения времени не вводятся и не отображаются
вместе с датами.
Выберите Create New (Создать) и попробуйте ввести имя длиной более
50 символов.
7 Примечание
В следующих разделах сборка приложения на некоторых этапах приводит к
возникновению ошибок компилятора. В инструкциях указано, когда
производить сборку приложения.
Сущность Instructor
Создайте Models/Instructor.cs , используя следующий код:
C#
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Instructor
{
public int ID { get; set; }
[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }
[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get { return LastName + ", " + FirstMidName; }
}
public ICollection<Course> Courses { get; set; }
public OfficeAssignment OfficeAssignment { get; set; }
}
}
На одной строке могут находиться несколько атрибутов. Атрибуты HireDate можно
записать следующим образом:
C#
[DataType(DataType.Date),Display(Name = "Hire
Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
Свойства навигации
Courses и OfficeAssignment — это свойства навигации.
Преподаватель может проводить любое количество курсов, поэтому Courses
определен как коллекция.
C#
public ICollection<Course> Courses { get; set; }
Преподаватель может иметь не более одного кабинета, поэтому свойство
OfficeAssignment содержит одну сущность OfficeAssignment . OfficeAssignment имеет
значение null, если кабинет не назначен.
C#
public OfficeAssignment OfficeAssignment { get; set; }
Сущность OfficeAssignment
Создайте Models/OfficeAssignment.cs , используя следующий код:
C#
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class OfficeAssignment
{
[Key]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }
public Instructor Instructor { get; set; }
}
}
Атрибут Key
Атрибут [Key] используется для идентификации свойства в качестве первичного
ключа (PK), когда имя свойства отличается от classnameID или ID .
Между сущностями Instructor и OfficeAssignment действует связь один к нулю или
к одному. Назначение кабинета существует только в связи с преподавателем,
которому оно назначено. Первичный ключ OfficeAssignment также является
внешним ключом (FK) для сущности Instructor . Связь "один к нулю или один к
одному" возникает, когда PK в одной таблице является как PK, так и FK для другой
таблицы.
EF Core не может автоматически распознать InstructorID как PK OfficeAssignment ,
так как InstructorID не соответствует соглашению об именовании ID или
classnameID. Таким образом, атрибут Key используется для определения
InstructorID в качестве первичного ключа:
C#
[Key]
public int InstructorID { get; set; }
По умолчанию EF Core ключ считается не базой данных, так как столбец
предназначен для идентификации связи. Дополнительные сведения см. в статье о
ключах EF.
Свойство навигации Instructor
Свойство навигации Instructor.OfficeAssignment может иметь значение NULL, так
как строка OfficeAssignment для преподавателя может отсутствовать.
Преподавателю может быть не назначен кабинет.
Свойство навигации OfficeAssignment.Instructor всегда будет иметь сущность
Instructor, так как тип внешнего ключа InstructorID — это тип значения int , не
допускающий значения NULL. Назначение кабинета не может существовать без
преподавателя.
Когда сущность Instructor имеет связанную сущность OfficeAssignment , каждая из
них имеет ссылку на другую в своем свойстве навигации.
Сущность Course
Обновите Models/Course.cs , включив в него следующий код.
C#
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Title { get; set; }
[Range(0, 5)]
public int Credits { get; set; }
public int DepartmentID { get; set; }
public Department Department { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
public ICollection<Instructor> Instructors { get; set; }
}
}
Сущность Course имеет свойство внешнего ключа (FK) DepartmentID . DepartmentID
указывает на связанную сущность Department . Сущность Course имеет свойство
навигации Department .
EF Core не требуется свойство внешнего ключа для модели данных, если модель
имеет свойство навигации для связанной сущности. EF Core автоматически создает
FK в базе данных, где бы они ни находились. EF Core создает теневые свойства для
автоматически созданных FK. Однако явное включение внешнего ключа в модель
данных позволяет сделать обновления проще и эффективнее. Например,
рассмотрим модель, где свойство внешнего ключа DepartmentID не включено. При
получении сущности курса для редактирования:
свойство Department имеет значение null , если оно не загружено явно;
для обновления сущности курса нужно сначала получить сущность
Department .
Если свойство внешнего ключа DepartmentID включено в модель данных, получать
сущность Department перед обновлением не нужно.
Атрибут DatabaseGenerated
Атрибут [DatabaseGenerated(DatabaseGeneratedOption.None)] указывает, что
первичный ключ предоставляется приложением, а не создается базой данных.
C#
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
По умолчанию предполагается, EF Core что значения PK создаются базой данных.
Обычно лучше всего использовать значения, созданные базой данных. Для
сущностей Course пользователь указывает первичный ключ. Например, номер
курса, такой как серия 1000 для кафедры математики и серия 2000 для кафедры
английского языка.
Атрибут DatabaseGenerated также может использоваться для создания значений по
умолчанию. Например, база данных может автоматически создать поле даты для
записи даты, когда строка была создана или изменена. Дополнительные сведения
см. в разделе Созданные свойства.
Свойства внешнего ключа и навигации
Свойства внешнего ключа (FK) и свойства навигации в сущности Course отражают
следующие связи:
Курс назначается одной кафедре, поэтому имеется внешний ключ DepartmentID и
свойство навигации Department .
C#
public int DepartmentID { get; set; }
public Department Department { get; set; }
На курс может быть зачислено любое количество учащихся, поэтому свойство
навигации Enrollments является коллекцией:
C#
public ICollection<Enrollment> Enrollments { get; set; }
Курс могут вести несколько преподавателей, поэтому свойство навигации
Instructors является коллекцией:
C#
public ICollection<Instructor> Instructors { get; set; }
Сущность Department
Создайте Models/Department.cs , используя следующий код:
C#
using
using
using
using
System;
System.Collections.Generic;
System.ComponentModel.DataAnnotations;
System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Name { get; set; }
[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
public ICollection<Course> Courses { get; set; }
}
}
Атрибут Column
Ранее атрибут Column использовался, чтобы изменить сопоставление имени
столбца. В коде для сущности Department атрибут Column можно использовать,
чтобы изменить сопоставление типов данных SQL. Столбец Budget определяется с
помощью типа money SQL Server в базе данных:
C#
[Column(TypeName="money")]
public decimal Budget { get; set; }
Сопоставление столбцов обычно не требуется. EF Coreвыбирает соответствующий
SQL Server тип данных на основе типа CLR для свойства. Тип decimal среды CLR
сопоставляется с типом decimal SQL Server. Budget используется для денежных
единиц, хотя для этого лучше подходит тип данных money.
Свойства внешнего ключа и навигации
Свойства внешнего ключа и навигации отражают следующие связи:
Кафедра может иметь или не иметь администратора.
Администратор всегда является преподавателем. Поэтому свойство
InstructorID включается в качестве внешнего ключа в сущность Instructor .
Свойство навигации называется Administrator , но содержит сущность Instructor :
C#
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
Вопросительный знак ? в приведенном выше коде указывает, что свойство
допускает значение NULL.
Кафедра может иметь несколько курсов, поэтому доступно свойство навигации
Courses:
C#
public ICollection<Course> Courses { get; set; }
По соглашению EF Core включает каскадное удаление для не допускающих
значения NULL FK и для связей "многие ко многим". Это поведение по умолчанию
может привести к циклическим правилам каскадного удаления. Такие правила
вызывают исключение при добавлении миграции.
Например, если Department.InstructorID свойство было определено как не
допускаемое значение NULL, EF Core настройте правило каскадного удаления. В
этом случае кафедра будет удалена, если будет удален преподаватель,
назначенный ее администратором. В такой ситуации правило ограничения будет
более целесообразным. Приведенный ниже текучий API задает правило
ограничения и отключает правило каскадного удаления.
C#
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
Свойства внешнего ключа и навигации сущности
Enrollment
Запись зачисления обозначает один курс, который проходит один учащийся.
Обновите Models/Enrollment.cs , включив в него следующий код.
C#
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
public class Enrollment
{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
[DisplayFormat(NullDisplayText = "No grade")]
public Grade? Grade { get; set; }
public Course Course { get; set; }
public Student Student { get; set; }
}
}
Свойства внешнего ключа и навигации отражают следующие связи:
Запись зачисления предназначена для одного курса, поэтому доступно свойство
первичного ключа CourseID и свойство навигации Course :
C#
public int CourseID { get; set; }
public Course Course { get; set; }
Запись зачисления предназначена для одного учащегося, поэтому доступно
свойство первичного ключа StudentID и свойство навигации Student :
C#
public int StudentID { get; set; }
public Student Student { get; set; }
Связи многие ко многим
Между сущностями Student и Course действует связь многие ко многим. Сущности
Enrollment выступают в качестве таблицы соединения "многие ко многим" с
полезными данными в базе данных. Фраза с полезными данными означает, что
таблица Enrollment содержит дополнительные данные, кроме внешних ключей для
присоединяемых таблиц. В сущности Enrollment дополнительными данными
помимо внешних ключей являются PK и Grade .
На следующем рисунке показано, как выглядят эти связи на схеме сущностей. (Эта
схема была создана с помощью EF Power Tools
не входит в этот учебник.)
для EF 6.x. Процедура ее создания
Каждая линия связи имеет 1 на одном конце и звездочку (*) на другом, указывая
характер один ко многим.
Если таблица Enrollment не включала в себя сведения об оценках, потребуется,
чтобы она содержала всего два внешних ключа: CourseID и StudentID . Таблицу
соединения многие ко многим без полезных данных иногда называют чистой
таблицей соединения (PJT).
Сущности Instructor и Course имеют связь "многие ко многим" с использованием
PJT.
Обновление контекста базы данных
Обновите Data/SchoolContext.cs , включив в него следующий код.
C#
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) :
base(options)
{
}
public
public
public
public
public
public
DbSet<Course> Courses { get; set; }
DbSet<Enrollment> Enrollments { get; set;
DbSet<Student> Students { get; set; }
DbSet<Department> Departments { get; set;
DbSet<Instructor> Instructors { get; set;
DbSet<OfficeAssignment> OfficeAssignments
}
}
}
{ get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable(nameof(Course))
.HasMany(c => c.Instructors)
.WithMany(i => i.Courses);
modelBuilder.Entity<Student>().ToTable(nameof(Student));
modelBuilder.Entity<Instructor>().ToTable(nameof(Instructor));
}
}
}
Приведенный выше код добавляет новые сущности и настраивает связь "многие ко
многим" между сущностями Instructor и Course .
Текучий API вместо атрибутов
Метод OnModelCreating в предыдущем коде использует текучий API для настройки
EF Core поведения. Этот API называется "текучим", так как часто используется для
объединения серии вызовов методов в один оператор. В следующем коде показан
пример текучего API:
C#
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
В этом учебнике текучий API используется только для сопоставления базы данных,
которое невозможно выполнить с помощью атрибутов. Однако текучий API
позволяет задать большинство правил форматирования, проверки и
сопоставления, которые можно указать с помощью атрибутов.
Некоторые атрибуты, такие как MinimumLength , невозможно применить с текучим
API. MinimumLength не изменяет схему, он лишь применяет правило проверки
минимальной длины.
Некоторые разработчики предпочитают использовать текучий API монопольно,
чтобы оставить свои классы сущностей чистыми. Атрибуты и текучий API можно
смешивать. Существует несколько конфигураций, которые можно реализовать
только с помощью текучего API, например указание составного первичного ключа.
Существует несколько конфигураций, которые можно реализовать только с
помощью атрибутов ( MinimumLength ). Рекомендации по использованию текучего
API и атрибутов:
Выберите один из двух этих подходов.
Используйте выбранный подход максимально согласованно.
Некоторые атрибуты из этого руководства используются:
только для проверки (например, MinimumLength );
EF Core только конфигурация (например, HasKey ).
Проверка и EF Core настройка (например, [StringLength(50)] ).
Дополнительные сведения о сравнении атрибутов и текучего API см. в разделе
Методы конфигурации.
Заполнение базы данных
Обновите код в Data/DbInitializer.cs :
C#
using
using
using
using
ContosoUniversity.Models;
System;
System.Collections.Generic;
System.Linq;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
// Look for any students.
if (context.Students.Any())
{
return;
// DB has been seeded
}
var alexander = new Student
{
FirstMidName = "Carson",
LastName = "Alexander",
EnrollmentDate = DateTime.Parse("2016-09-01")
};
var alonso = new Student
{
FirstMidName = "Meredith",
LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2018-09-01")
};
var anand = new Student
{
FirstMidName = "Arturo",
LastName = "Anand",
EnrollmentDate = DateTime.Parse("2019-09-01")
};
var barzdukas = new Student
{
FirstMidName = "Gytis",
LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2018-09-01")
};
var li = new Student
{
FirstMidName = "Yan",
LastName = "Li",
EnrollmentDate = DateTime.Parse("2018-09-01")
};
var justice = new Student
{
FirstMidName = "Peggy",
LastName = "Justice",
EnrollmentDate = DateTime.Parse("2017-09-01")
};
var norman = new Student
{
FirstMidName = "Laura",
LastName = "Norman",
EnrollmentDate = DateTime.Parse("2019-09-01")
};
var olivetto = new Student
{
FirstMidName = "Nino",
LastName = "Olivetto",
EnrollmentDate = DateTime.Parse("2011-09-01")
};
var students = new Student[]
{
alexander,
alonso,
anand,
barzdukas,
li,
justice,
norman,
olivetto
};
context.AddRange(students);
var abercrombie = new Instructor
{
FirstMidName = "Kim",
LastName = "Abercrombie",
HireDate = DateTime.Parse("1995-03-11")
};
var fakhouri = new Instructor
{
FirstMidName = "Fadi",
LastName = "Fakhouri",
HireDate = DateTime.Parse("2002-07-06")
};
var harui = new Instructor
{
FirstMidName = "Roger",
LastName = "Harui",
HireDate = DateTime.Parse("1998-07-01")
};
var kapoor = new Instructor
{
FirstMidName = "Candace",
LastName = "Kapoor",
HireDate = DateTime.Parse("2001-01-15")
};
var zheng = new Instructor
{
FirstMidName = "Roger",
LastName = "Zheng",
HireDate = DateTime.Parse("2004-02-12")
};
var instructors = new Instructor[]
{
abercrombie,
fakhouri,
harui,
kapoor,
zheng
};
context.AddRange(instructors);
var officeAssignments = new OfficeAssignment[]
{
new OfficeAssignment {
Instructor = fakhouri,
Location = "Smith 17" },
new OfficeAssignment {
Instructor = harui,
Location = "Gowan 27" },
new OfficeAssignment {
Instructor = kapoor,
Location = "Thompson 304" }
};
context.AddRange(officeAssignments);
var english = new Department
{
Name = "English",
Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = abercrombie
};
var mathematics = new Department
{
Name = "Mathematics",
Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = fakhouri
};
var engineering = new Department
{
Name = "Engineering",
Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = harui
};
var economics = new Department
{
Name = "Economics",
Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = kapoor
};
var departments = new Department[]
{
english,
mathematics,
engineering,
economics
};
context.AddRange(departments);
var chemistry = new Course
{
CourseID = 1050,
Title = "Chemistry",
Credits = 3,
Department = engineering,
Instructors = new List<Instructor> { kapoor, harui }
};
var microeconomics = new Course
{
CourseID = 4022,
Title = "Microeconomics",
Credits = 3,
Department = economics,
Instructors = new List<Instructor> { zheng }
};
var macroeconmics = new Course
{
CourseID = 4041,
Title = "Macroeconomics",
Credits = 3,
Department = economics,
Instructors = new List<Instructor> { zheng }
};
var calculus = new Course
{
CourseID = 1045,
Title = "Calculus",
Credits = 4,
Department = mathematics,
Instructors = new List<Instructor> { fakhouri }
};
var trigonometry = new Course
{
CourseID = 3141,
Title = "Trigonometry",
Credits = 4,
Department = mathematics,
Instructors = new List<Instructor> { harui }
};
var composition = new Course
{
CourseID = 2021,
Title = "Composition",
Credits = 3,
Department = english,
Instructors = new List<Instructor> { abercrombie }
};
var literature = new Course
{
CourseID = 2042,
Title = "Literature",
Credits = 4,
Department = english,
Instructors = new List<Instructor> { abercrombie }
};
var courses = new Course[]
{
chemistry,
microeconomics,
macroeconmics,
calculus,
trigonometry,
composition,
literature
};
context.AddRange(courses);
var enrollments = new Enrollment[]
{
new Enrollment {
Student = alexander,
Course = chemistry,
Grade = Grade.A
},
new Enrollment {
Student = alexander,
Course = microeconomics,
Grade = Grade.C
},
new Enrollment {
Student = alexander,
Course = macroeconmics,
Grade = Grade.B
},
new Enrollment {
Student = alonso,
Course = calculus,
Grade = Grade.B
},
new Enrollment {
Student = alonso,
Course = trigonometry,
Grade = Grade.B
},
new Enrollment {
Student = alonso,
Course = composition,
Grade = Grade.B
},
new Enrollment {
Student = anand,
Course = chemistry
},
new Enrollment {
Student = anand,
Course = microeconomics,
Grade = Grade.B
},
new Enrollment {
Student = barzdukas,
Course = chemistry,
Grade = Grade.B
},
new Enrollment {
Student = li,
Course = composition,
Grade = Grade.B
},
new Enrollment {
Student = justice,
Course = literature,
Grade = Grade.B
}
};
context.AddRange(enrollments);
context.SaveChanges();
}
}
}
Предыдущий код предоставляет начальные данные для новых сущностей.
Основная часть кода создает объекты сущностей и загружает демонстрационные
данные. Демонстрационные данные используются для проверки.
Применение миграции либо удаление и
повторное создание
Существующую базу данных можно изменить, использую один из следующих двух
подходов.
Удаление и повторное создание базы данных. Выберите этот раздел, при
использовании SQLite.
Применение миграции к существующей базе данных. Инструкции в этом
разделе подходят только для SQL Server, но не для SQLite.
Для SQL Server применимы оба подхода. Хотя метод с применением миграции
является более сложным и трудоемким, в реальной рабочей среде лучше
использовать именно его.
Удаление и повторное создание базы
данных
Чтобы принудительно EF Core создать новую базу данных, удалите и обновите ее:
Visual Studio
Удалите папку Migrations.
Выполните следующие команды в консоли диспетчера пакетов (PMC):
PowerShell
Drop-Database
Add-Migration InitialCreate
Update-Database
Запустите приложение. При запуске приложения выполняется метод
DbInitializer.Initialize . DbInitializer.Initialize заполняет новую базу данных.
Visual Studio
Откройте базу данных в SSOX.
Если SSOX был открыт ранее, нажмите кнопку Обновить.
Разверните узел Таблицы. Отображаются созданные таблицы.
Следующие шаги
В следующих двух учебниках рассказывается о том, как считывать и обновлять
связанные данные.
Предыдущий учебник
Следующий учебник
Часть 6. Razor Страницы с EF Core
ASP.NET Core — чтение связанных
данных
Статья • 28.01.2023 • Чтение занимает 36 мин
Авторы: Том Дайкстра
Андерсон
(Tom Dykstra), Йон П. Смит
(Jon P Smith) и Рик
(Rick Anderson)
Веб-приложение Университета Contoso демонстрирует создание Razor вебприложений Pages с помощью EF Core Visual Studio. Сведения о серии руководств
см. в первом руководстве серии.
При возникновении проблем, которые вам не удается устранить, скачайте готовое
приложение
и сравните его код с тем, который вы создали в процессе работы с
этим руководством.
Этот учебник посвящен чтению и отображению связанных данных. Связанные
данные — это данные, EF Core которые загружаются в свойства навигации.
На следующих рисунках изображены готовые страницы для этого руководства:
Безотложная, явная и отложенная загрузка
Существует несколько способов загрузки EF Core связанных данных в свойства
навигации сущности:
Безотложная загрузка. Безотложной является загрузка, когда запрос для
одного типа сущности также загружает связанные сущности. При чтении
сущности извлекаются ее связанные данные. Обычно такая загрузка
представляет собой одиночный запрос с соединением, который получает все
необходимые данные. EF Core будет выдавать несколько запросов для
некоторых типов безотложной загрузки. Отправка нескольких запросов
может оказаться эффективнее, чем выполнение одного большого запроса.
Безотложная загрузка указывается с помощью методов Include и ThenInclude.
Безотложная загрузка отправляет несколько запросов, когда включена
навигация коллекции:
Один запрос для основного запроса
Один запрос для каждого "края" коллекции в дереве загрузки.
Отдельные запросы с Load помощью: данные можно получить в отдельных
запросах и EF Core "исправляет" свойства навигации. "Исправления" означает,
что EF Core автоматически заполняет свойства навигации. Отдельные запросы
с Load больше похожи на явную загрузку, чем на безотложную.
Примечание:EF Core автоматически исправляет свойства навигации для
любых других сущностей, которые ранее были загружены в экземпляр
контекста. Даже если данные для свойства навигации не включены явно,
свойство все равно можно заполнить при условии, что ранее были загружены
некоторые или все связанные сущности.
Явная загрузка. При первом чтении сущности связанные данные не
извлекаются. Нужно написать код, извлекающий связанные данные, когда они
необходимы. Явная загрузка с отдельными запросами приводит к отправке
нескольких запросов к базе данных. При явной загрузке код указывает, какие
свойства навигации нужно загрузить. Для выполнения явной загрузки
используется метод Load . Пример:
Отложенная загрузка. При первом чтении сущности связанные данные не
извлекаются. При первом обращении к свойству навигации необходимые для
этого свойства данные извлекаются автоматически. Запрос к базе данных
отправляется при каждом первом обращении к свойству навигации.
Отложенная загрузка может снизить производительность, например когда
разработчики используют запросы N+1 , загружая родительский объект и
перечисляя дочерние объекты.
Создание страниц курсов
Сущность Course включает в себя свойство навигации, содержащее связанную
сущность Department .
Чтобы отобразить имя кафедры, которой назначен курс, выполните указанные
ниже действия.
Загрузите связанную сущность Department в свойство навигации
Course.Department .
Получите имя из свойства Name сущности Department .
Формирование шаблона для страниц курсов
Visual Studio
Следуйте инструкциям в разделе Формирование шаблона для страниц
Student, за исключением следующего:
Создайте папку Pages/Courses.
Используйте класс модели Course .
Используйте существующий класс контекста вместо создания нового.
Откройте Pages/Courses/Index.cshtml.cs и проверьте метод OnGetAsync .
Подсистема формирования шаблонов указала безотложную загрузку для
свойства навигации Department . Метод Include задает безотложную загрузку.
Запустите приложение и выберите ссылку Courses (Курсы). В столбце кафедры
отображается бесполезный DepartmentID .
Отображение названия кафедры
Измените файл Pages/Courses/Index.cshtml.cs, используя следующий код:
C#
using
using
using
using
using
ContosoUniversity.Models;
Microsoft.AspNetCore.Mvc.RazorPages;
Microsoft.EntityFrameworkCore;
System.Collections.Generic;
System.Threading.Tasks;
namespace ContosoUniversity.Pages.Courses
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public IndexModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
public IList<Course> Courses { get; set; }
public async Task OnGetAsync()
{
Courses = await _context.Courses
.Include(c => c.Department)
.AsNoTracking()
.ToListAsync();
}
}
}
Приведенный выше код изменяет свойство Course на Courses и добавляет
AsNoTracking . AsNoTracking повышает производительность, так как возвращаемые
сущности не отслеживаются. Отслеживать сущности не нужно, так как они не
изменяются в текущем контексте.
Обновите Pages/Courses/Index.cshtml , включив в него следующий код.
CSHTML
@page
@model ContosoUniversity.Pages.Courses.IndexModel
@{
ViewData["Title"] = "Courses";
}
<h1>Courses</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model
</th>
<th>
@Html.DisplayNameFor(model
</th>
<th>
@Html.DisplayNameFor(model
</th>
<th>
@Html.DisplayNameFor(model
</th>
=> model.Courses[0].CourseID)
=> model.Courses[0].Title)
=> model.Courses[0].Credits)
=> model.Courses[0].Department)
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Courses)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.CourseID)
</td>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.Credits)
</td>
<td>
@Html.DisplayFor(modelItem => item.Department.Name)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.CourseID">Edit</a>
|
<a asp-page="./Details" asp-routeid="@item.CourseID">Details</a> |
<a asp-page="./Delete" asp-routeid="@item.CourseID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
В шаблонный код были внесены следующие изменения:
Имя свойства Course изменилось на Courses .
Добавлен столбец Number (Номер), отображающий значение свойства
CourseID . По умолчанию в шаблоне отсутствуют первичные ключи, поскольку
для конечных пользователей они не имеют смысла. Однако в нашем случае
первичный ключ имеет смысл.
Изменен столбец Department (Кафедра) для отображения названия кафедры.
Код отображает свойство Name сущности Department , которая загружена в
свойство навигации Department :
HTML
@Html.DisplayFor(modelItem => item.Department.Name)
Для просмотра списка с названиями кафедр запустите приложение и выберите
вкладку Courses (Курсы).
Загрузка связанных данных с помощью "Select"
Метод OnGetAsync загружает связанные данные с помощью метода Include . Метод
Select является альтернативным вариантом, который загружает только
необходимые связанные данные. Для отдельных элементов, таких как
Department.Name , используется SQL INNER JOIN . Для коллекций используется доступ к
другой базе данных, но это же делает и оператор Include в коллекциях.
Следующий код загружает связанные данные с помощью метода Select :
C#
public IList<CourseViewModel> CourseVM { get; set; }
public async Task OnGetAsync()
{
CourseVM = await _context.Courses
.Select(p => new CourseViewModel
{
CourseID = p.CourseID,
Title = p.Title,
Credits = p.Credits,
DepartmentName = p.Department.Name
}).ToListAsync();
}
Приведенный выше код не возвращает никаких типов сущностей, поэтому
отслеживание не выполняется. Дополнительные сведения об отслеживании EF см.
в разделе Отслеживание или Отключение отслеживания запросов.
CourseViewModel :
C#
public class CourseViewModel
{
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public string DepartmentName { get; set; }
}
Полное решение Razor Pages см. здесь .
Создание страниц преподавателей
В этом разделе формируется шаблон для страниц преподавателей, а затем на
страницу указателя преподавателей добавляются связанные курсы и регистрации.
Эта страница считывает и отображает связанные данные следующим образом:
Список преподавателей отображает связанные данные из сущности
OfficeAssignment ("Office" (Кабинет) на предыдущем изображении). Между
сущностями Instructor и OfficeAssignment действует связь один к нулю или к
одному. Безотложная загрузка используется для сущностей OfficeAssignment .
Безотложная загрузка обычно более эффективна, когда требуется отобразить
связанные данные. В этом случае отображается принадлежность к кабинету
для каждого преподавателя.
Когда пользователь выбирает преподавателя, отображаются связанные
сущности Course . Между сущностями Instructor и Course действует связь
многие ко многим. Используется безотложная загрузка для сущностей Course
и связанных сущностей Department . В этом случае отдельные запросы могут
оказаться эффективнее, так как требуются только курсы для выбранного
преподавателя. Этот пример показывает, как использовать безотложную
загрузку для свойств навигации в сущностях, находящихся в свойствах
навигации.
Когда пользователь выбирает курс, отображаются связанные данные из
сущности Enrollments . На предыдущем изображении отображается имя и
оценка учащегося. Между сущностями Course и Enrollment действует связь
один ко многим.
Создание модели представления
На странице "Instructors" (Преподаватели) отображаются данные из трех различных
таблиц. Требуется модель представления, которая включает в себя три свойства,
представляющие три таблицы.
Создайте Models/SchoolViewModels/InstructorIndexData.cs , используя следующий
код:
C#
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
namespace ContosoUniversity.Models.SchoolViewModels
{
public class InstructorIndexData
{
public IEnumerable<Instructor> Instructors { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<Enrollment> Enrollments { get; set; }
}
}
Формирование шаблона для страниц преподавателей
Visual Studio
Следуйте инструкциям в разделе Формирование шаблона для страниц
Student, за исключением следующего:
Создайте папку Pages/Instructors.
Используйте класс модели Instructor .
Используйте существующий класс контекста вместо создания нового.
Запустите приложение и перейдите на страницу Instructors.
Обновите Pages/Instructors/Index.cshtml.cs , включив в него следующий код.
C#
using
using
using
using
using
using
using
ContosoUniversity.Models;
ContosoUniversity.Models.SchoolViewModels;
Microsoft.AspNetCore.Mvc.RazorPages;
Microsoft.EntityFrameworkCore;
System.Collections.Generic;
System.Linq;
System.Threading.Tasks;
// Add VM
namespace ContosoUniversity.Pages.Instructors
{
public class IndexModel : PageModel
{
private readonly ContosoUniversity.Data.SchoolContext _context;
public IndexModel(ContosoUniversity.Data.SchoolContext context)
{
_context = context;
}
public InstructorIndexData InstructorData { get; set; }
public int InstructorID { get; set; }
public int CourseID { get; set; }
public async Task OnGetAsync(int? id, int? courseID)
{
InstructorData = new InstructorIndexData();
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.ThenInclude(c => c.Department)
.OrderBy(i => i.LastName)
.ToListAsync();
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors
.Where(i => i.ID == id.Value).Single();
InstructorData.Courses = instructor.Courses;
}
if (courseID != null)
{
CourseID = courseID.Value;
IEnumerable<Enrollment> Enrollments = await
_context.Enrollments
.Where(x => x.CourseID == CourseID)
.Include(i=>i.Student)
.ToListAsync();
InstructorData.Enrollments = Enrollments;
}
}
}
}
Метод OnGetAsync принимает необязательные данные маршрутизации для
идентификатора выбранного преподавателя.
Изучите запрос в файле Pages/Instructors/Index.cshtml.cs :
C#
InstructorData = new InstructorIndexData();
InstructorData.Instructors = await _context.Instructors
.Include(i => i.OfficeAssignment)
.Include(i => i.Courses)
.ThenInclude(c => c.Department)
.OrderBy(i => i.LastName)
.ToListAsync();
В коде задается безотложная загрузка для следующих свойств навигации:
Instructor.OfficeAssignment
Instructor.Courses
Course.Department
Приведенный ниже код выполняется при выборе преподавателя ( id != null ).
C#
if (id != null)
{
InstructorID = id.Value;
Instructor instructor = InstructorData.Instructors
.Where(i => i.ID == id.Value).Single();
InstructorData.Courses = instructor.Courses;
}
Выбранный преподаватель извлекается из списка преподавателей в модели
представления. Свойство модели представления Courses вместе с сущностями
Course загружается из свойства навигации Courses выбранного преподавателя.
Метод Where возвращает коллекцию. В этом случае фильтр выберет одну сущность,
поэтому вызывается метод Single для преобразования коллекции в одну сущность
Instructor . Сущность Instructor предоставляет доступ к свойству навигации
Course .
Метод Single используется для коллекции, когда она содержит всего один элемент.
Метод Single вызывает исключение, если коллекция пуста или содержит больше
одного элемента. Альтернативой является SingleOrDefault с возвратом который
значения по умолчанию, если коллекция пустая. Для этого запроса null
возвращается по умолчанию.
Следующий код заполняет свойство Enrollments модели представления при
выборе курса:
C#
if (courseID != null)
{
CourseID = courseID.Value;
IEnumerable<Enrollment> Enrollments = await _context.Enrollments
.Where(x => x.CourseID == CourseID)
.Include(i=>i.Student)
.ToListAsync();
InstructorData.Enrollments = Enrollments;
}
Изменение страницы индекса преподавателей
Обновите Pages/Instructors/Index.cshtml , включив в него следующий код.
CSHTML
@page "{id:int?}"
@model ContosoUniversity.Pages.Instructors.IndexModel
@{
ViewData["Title"] = "Instructors";
}
<h2>Instructors</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>Last Name</th>
<th>First Name</th>
<th>Hire Date</th>
<th>Office</th>
<th>Courses</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.InstructorData.Instructors)
{
string selectedRow = "";
if (item.ID == Model.InstructorID)
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.HireDate)
</td>
<td>
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
</td>
<td>
@{
foreach (var course in item.Courses)
{
@course.CourseID @: @course.Title <br />
}
}
</td>
<td>
<a asp-page="./Index" asp-route-id="@item.ID">Select</a>
|
<a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-page="./Details" asp-routeid="@item.ID">Details</a> |
<a asp-page="./Delete" asp-routeid="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
@if (Model.InstructorData.Courses != null)
{
<h3>Courses Taught by Selected Instructor</h3>
<table class="table">
<tr>
<th></th>
<th>Number</th>
<th>Title</th>
<th>Department</th>
</tr>
@foreach (var item in Model.InstructorData.Courses)
{
string selectedRow = "";
if (item.CourseID == Model.CourseID)
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
<td>
<a asp-page="./Index" asp-routecourseID="@item.CourseID">Select</a>
</td>
<td>
@item.CourseID
</td>
<td>
@item.Title
</td>
<td>
@item.Department.Name
</td>
</tr>
}
</table>
}
@if (Model.InstructorData.Enrollments != null)
{
<h3>
Students Enrolled in Selected Course
</h3>
<table class="table">
<tr>
<th>Name</th>
<th>Grade</th>
</tr>
@foreach (var item in Model.InstructorData.Enrollments)
{
<tr>
<td>
@item.Student.FullName
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
</tr>
}
</table>
}
Приведенный выше код вносит следующие изменения:
Изменяет директиву page на @page "{id:int?}" . "{id:int?}" является
шаблоном маршрута. Шаблон маршрута изменяет целочисленные строки
запроса в URL-адресе для маршрутизации данных. Например, при выборе
ссылки Select для преподавателя только с директивой @page формируется
URL-адрес следующего вида:
https://localhost:5001/Instructors?id=2
Когда используется директива страницы @page "{id:int?}" , URL-адрес имеет
следующее значение: https://localhost:5001/Instructors/2 .
Добавляет столбец Office, в котором отображается
item.OfficeAssignment.Location только тогда, когда item.OfficeAssignment не
равно NULL. Так как это связь один к нулю или одному, то связанная сущность
OfficeAssignment может отсутствовать.
HTML
@if (item.OfficeAssignment != null)
{
@item.OfficeAssignment.Location
}
Добавляет столбец Courses, содержащий курсы, которые ведет конкретный
преподаватель. Подробные сведения об использовании синтаксиса Razor см.
в разделе Явный перенос строки.
Добавляет код, который динамически добавляет class="table-success" к
элементу tr выбранного преподавателя и курса. Этот параметр задает цвет
фона для выделенных строк c помощью класса Bootstrap.
HTML
string selectedRow = "";
if (item.CourseID == Model.CourseID)
{
selectedRow = "table-success";
}
<tr class="@selectedRow">
Добавляет новую гиперссылку с меткой Select (Выбрать). Она отправляет
идентификатор выбранного преподавателя в метод Index и задает цвет фона.
HTML
<a asp-action="Index" asp-route-id="@item.ID">Select</a> |
Добавляет таблицу курсов для выбранного преподавателя.
Добавляет таблицу регистраций учащихся для выбранного курса.
Запустите приложение и выберите вкладку Instructors. На странице отображается
Location (кабинет) из связанной сущности OfficeAssignment . Если OfficeAssignment
имеет значение NULL, отображается пустая ячейка таблицы.
Щелкните ссылку Select для преподавателя. Стиль строки изменится, и отобразятся
курсы, назначенные этому преподавателю.
Выберите курс, чтобы увидеть список зачисленных учащихся и их оценки.
Дальнейшие действия
Следующее руководство посвящено обновлению связанных данных.
Предыдущий учебник
Следующий учебник
Часть 7. Razor Страницы с EF Core
ASP.NET Core — обновление
связанных данных
Статья • 28.01.2023 • Чтение занимает 46 мин
Авторы: Том Дайкстра
Андерсон
(Tom Dykstra), Йон П. Смит
(Jon P Smith) и Рик
(Rick Anderson)
Веб-приложение Университета Contoso демонстрирует создание Razor вебприложений Pages с помощью