Валидация данных на стороне сервера с HTMX 2.0. Разбираемся с валидацией модели данных на основе платформы ASP.NET Core.
В статье мы освоим базовую реализацию валидации данных на стороне сервера. На стороне сервера мы используем самый простой способ коммуникации - HTTP.
Знакомство с гипермедийными системами на ASP.NET Core 8.0.
Введение
Данная статья полностью посвящена теме валидации модели данных с использованием гипермедийных систем. Эта статья опирается на материалы ознакомительной статьи по гипермедийным системам, ссылка на которую приводится выше. В ней вы сможете познакомится с разработкой приложения, ориентированного на гипермедиа, на основе платформы ASP.NET Core. Материал статьи рассчитан на читателя, который уже знаком с HTMX.
В этой обучающей статье Вы сможете освоить валидацию моделей при вводе данных новым более простым способом. Этот новый способ нам становятся доступным благодаря библиотеке Htmx.js. Мы не будем рассматривать обычные способы валидации, а сосредоточится исключительно на новом. Так как вы уже имеете опыт создания гипермедийных веб-приложений, то материал статьи будет сконцентрирован только на теме валидации.
Как я писал выше, эта статья рассчитана на читателей, уже знакомых и желающих расширить свои знания технологии HTMX. Всех читателей, которым незнакомы гипермедийные системы я попрошу перейти к моей ознакомительную статье на Хабр. Ссылку на нее я дал выше по тексту. Ну а всех остальных, кто желает освоить новый способ валидации моделей, приглашаю в свою опытно-экспериментальную лабораторию.
Валидация моделей
Валидация данных - это важный элемент любого современного веб приложения. Пользователи такого приложения могут часто вводить данные, с которыми приложение не в состоянии работать. Например, приложение не может обработать пустое значение имени пользователя. Необходимо не допускать ввод пользователем некорректных данных. Это подводит нас к теме этой статьи - валидация моделей.
Валидация моделей - это подтверждение того факта, что полученные данные от пользователя пригодны для связывания с моделью. В ином случае необходимо показать пользователю сообщение с описанием проблемы. Первая часть процесса - проверка полученных данных - позволяет сохранить целостность доменной области нашего приложения. Отбрасывая непригодные для использования данные, мы предупреждаем возникновение нежелательных ошибок. Ну а вторая часть процесса - помощь пользователю в исправлении его ошибки. Когда пользователю не предоставляют полезную для него информацию о причине сбоя, то он становится недоволен. Мы же все хотим, чтобы пользователь был доволен, потому приступим к созданию проекта для примеров.
Создание проекта для примера
Прежде чем начать, мы создадим простое приложение на основе стандартного шаблона MVC, к которому будем применять различные техники валидации моделей. Создайте новый проект с названием HypermediaValidation на основе шаблона “Веб-приложение ASP.NET Core (модель-представление-контроллер)”.
Подготовка проекта
Откройте проект и измените содержимое файла с настройками запуска launchSettings.json в папке Properties. Исправьте порты в строках, которые отмечены комментариями, так, как показано в листинге ниже.
Листинг: Исправленное содержимое файла launchSettings.json в папке Properties
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:48233",
"sslPort": 44391
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5000", // Исправить
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
// Исправить следующую строку
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
В этом файле мы настраиваем адреса и порты, на которых будет запускаться наше веб-приложение в режиме отладки.
Полностью замените содержимое файла контроллера Home содержимым, представленным в листинге ниже. Удалите ненужный конструктор и неиспользуемые методы действия, оставьте только начальную точку Index. Пусть при начальном запуске приложения будет показано только визуальное представление по-умолчанию.
Листинг: Замена содержимого файла HomeController.cs
using Microsoft.AspNetCore.Mvc;
namespace HypermediaValidation.Controllers;
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
Нужно изменить это визуальное представление. На листинге ниже представлено содержимое файла Index.cshtml в папке Views/Home. Необходимо привести этот в файл к виду, представленному в этом листинге. Пусть пока отображает базовую информацию о этом опытном приложении.
И в завершении подготовительного этапа немного поправим шаблон визуальных представлений, как на следующем листинге.
Листинг: Шаблон визуальных представлений Views/Shared/_Layout.cshtml
<!DOCTYPE html>
<html lang="en">
<head>
…
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light
bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
@* Изменить название в следующей строке *@
<a class="navbar-brand" asp-area="" asp-controller="Home"
asp-action="Index">Опытное приложение</a>
<button class="navbar-toggler" type="button"
data-bs-toggle="collapse" data-bs-target=".navbar-collapse"
aria-controls="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=""
asp-controller="Home" asp-action="Index">Главная</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">
© 2024 - Опытное приложение
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
Для краткости содержимое блока <head> не показано. В этой статье мы по максимуму используем уже готовый шаблон MVC приложения, предоставляемый платформой ASP.NET Core.
Неиспользуемые визуальные представления Views/Home/Privacy.cshtml и Views/Shared/Error.cshtml следует удалить.
Поддержка гипермедийных систем
Пришло время добавить в приложение клиентские библиотеки и пакеты поддержки гипермедийных систем. Добавьте клиентскую библиотеку htmx.js в наше приложение с помощью пакетного менеджера клиентских библиотек libman. Нажмите правой кнопкой мыши на проекте - “Добавить” - “Клиентская библиотека”. В открывшемся диалоговом окне наберите в строке поиска htmx и выберите последнюю версию библиотеки. После нажатия кнопки “Установить” необходимые файлы будут автоматически добавлены в папку wwwroot/lib/htmx.
Далее добавьте Nuget-пакеты поддержки работы гипермедийных систем Htmx и Htmx.TagHelpers. Они изображены на скриншоте ниже.
Добавьте поддержку таг-хелперов поддержки работы гипермедийных систем в файл Views/_ViewImports.cshtml. Добавьте отмеченную комментарием строку в этот файл.
Листинг: Добавление поддержки новых таг-хелперов в Views/_ViewImports.cshtml
@using ProgrammersClub
@using ProgrammersClub.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Htmx.TagHelpers // Добавить
В шаблон визуальных представлений нужно добавить использование клиентской библиотеки Htmx.js. Отредактируйте файл шаблона Views/Shared/_Layout.cshrml так, как показано на следующем листинге. Тоже добавьте отмеченную комментарием строку в файл.
Листинг: Шаблон страниц Views/Shared/_Layout.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"] - HypermediaValidation</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<script src="~/lib/htmx/htmx.js"></script> // Добавить
<link rel="stylesheet" href="~/HypermediaValidation.styles.css"
asp-append-version="true" />
</head>
<body>
…
</body>
</html>
Для поддержки работы гипермедийных систем необходимо добавить только одну строку в блок <head> шаблона визуальных представлений. Для краткости содержимое блока <body> не показано.
Базовые типы
Вне зависимости от используемого способа валидации в приложение нужно добавить примитивы для приема и передачи данных между визуальным представлением и методом действия контроллера. Как правило, в методе действия контроллера производится проверка корректности заполнения модели. А уже на основании этой проверки принимается решение о том, что возвращать в качестве результата выполнения метода действия контроллера. Нужно создать базовый класс передачи/приема данных пользователя с поддержкой валидации данных.
Создайте новый класс в папке Models с названием UserWebModel и наполните его содержимым следующего листинга.
Листинг: Веб модель пользователя UserWebModel
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace HypermediaValidation.Models;
public class UserWebModel
{
[Display(Name = "Имя")]
[Required(ErrorMessage = "Пожалуйста, введите свое имя")]
[StringLength(10, MinimumLength = 3, ErrorMessage = "Длинна имени должна быть от 3 до 10 символов")]
public string? Name { get; init; }
[Display(Name = "Дата рождения")]
[Required(ErrorMessage = "Пожалуйста, ведите дату рождения")]
[DataType(DataType.Date)]
public DateTime? BirthDay { get; init; }
[Display(Name = "Согласен с правилами")]
public bool IsTermsAccepted { get; init; }
public void Validate(ModelStateDictionary state)
{
if (IsTermsAccepted == false)
{
state.AddModelError(nameof(IsTermsAccepted),
"Необходимо подтвердить согласие с правилами");
}
if (BirthDay >= DateTime.Now)
{
state.AddModelError(nameof(BirthDay),
"Дата рождения должна быть в прошлом");
}
}
}
Этот новый класс будет ответственен за хранение данных зарегистрированного пользователя. В этом классе сразу установлены атрибуты валидации. Атрибут Required - для проверки, что имя вообще было введено, StringLength - для проверки длины введенного имени пользователя, а DataType - для проверки корректного ввода даты рождения. Метод Validate() будет выполнять дополнительную валидацию на стороне сервера. Удалите из этой папки неиспользуемый файл с классом ErrorViewModel.
Простейшая гипермедийная валидация
Этот вариант характеризуется тем, что вся валидация данных производится на стороне сервера. Конечно, проверка на стороне клиента может помочь исправить проблему еще до отправки запроса на сервер, но как правило, такая проверка недостаточна для полной проверки состояния. Я могу в качестве типичного примера привести форму регистрации пользователей. Хотя имя пользователя может соответствовать критериям проверки, таким как длина имени пользователя, уникальность имени может быть проверена исключительно на стороне сервера путем выполнения проверки уникальности имени пользователя среди всех существующих пользователей.
Мы можем использовать HTMX для отправки формы на сервер и использовать механизмы платформы ASP.NET Core для выделения и отображения проблем. Это позволяет избежать проблемы как создания неполных проверок на стороне клиента, так и устранения дублирования проверок на двух сторонах - на стороне клиента и на стороне сервера. Весь код правил проверки может быть сконцентрирован в одном классе, на сервере, который будет ответственен за передачу данных из визуального представления. Вне зависимости на каком этапе происходит валидация - заполнение формы или отправка заполненной формы с данными - производится вызов того-же самого кода. Такое решение немного повысит производительность разработки приложения, облегчит сопровождение приложения в будущем и застрахует от глупых ошибок. А все оттого, что валидация данных нигде не дублируется и находится в одном месте.
Простейшая валидация будет показана на примере формы регистрации пользователя. Первым делом добавьте новый контроллер с названием SampleController и наполните его содержимым следующего листинга.
Листинг: Контроллер с простейшим вариантом валидации SampleController
using Htmx;
using HypermediaValidation.Models;
using Microsoft.AspNetCore.Mvc;
namespace HypermediaValidation.Controllers;
public class SimpleController : Controller
{
public IActionResult Registration()
{
return View();
}
[HttpPost]
public IActionResult Registration(UserWebModel model)
{
model.Validate(ModelState);
if (Request.IsHtmx())
return PartialView("Partial/RegistrationPartial", model);
if (!ModelState.IsValid) return View(model);
return View("Complete");
}
}
В этом контроллере первый метод действия с названием Registration() возвращает в качестве результата выполнения визуальное представление с формой регистрации участника. Второй метод в этом контроллере принимает в параметрах модель данных из формы регистрации. Если был произведен запрос от гипермедийной системы - то во втором методе производится валидация данных и возвращение частичного представления с результатами валидации. И только если была произведена отправка формы данных от нажатия кнопки “Отправить” выполняется стандартная ветка метода действия. В этой ветке при обнаружении проблем во время валидации происходит отображение проблем пользователю. А в случае успешного прохождения валидации - пользователю показывается визуальное представление с информацией о успехе регистрации.
Добавьте новое визуальное представление регистрации пользователей по пути Views\Simple с названием Registration.cshtml. Наполните это визуальное представление следующим содержимым.
Листинг: Визуальное представление регистрации Views/Simple/Registration.cshtml
@model UserWebModel
@{
ViewBag.Title = "Простейшая гипермедийная валидация";
}
<div class="p-2 bg-light border rounded">
<h2>@ViewBag.Title</h2>
<partial name="Partial/RegistrationPartial" model="Model" />
</div>
В этом визуальном представлении необходимо только разместить элемент отображения частичного визуального представления. А уже в этом частичном визуальном представлении с названием RegistrationPartial.cshtml будут располагаться элементы формы регистрации пользователя. Добавьте его в папку Views\Simple\Partial. Кстати, в этом частичном визуальном представлении - его листинг показан ниже - все самое интересное.
Листинг: Частичное представление Views/Simple/Partial/RegistrationPartial.cshtml
@model UserWebModel
<form asp-action="Registration" method="post"
hx-target="this"
hx-swap="outerHTML">
<h5>Регистрация пользователя</h5>
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="my-1">
<label asp-for="Name" class="form-label"></label>
<input asp-for="Name" class="form-control"
hx-post hx-action="Registration"
hx-trigger="change, keyup delay:500ms changed"/>
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="my-1">
<label asp-for="BirthDay" class="form-label"></label>
<input asp-for="BirthDay" class="form-control"
hx-post hx-action="Registration"/>
<span asp-validation-for="BirthDay" class="text-danger"></span>
</div>
<div class="form-check my-1">
<input asp-for="IsTermsAccepted" class="form-check-input"
hx-post hx-action="Registration"/>
<label asp-for="IsTermsAccepted" class="form-check-label"></label>
<span asp-validation-for="IsTermsAccepted" class="text-danger"></span>
</div>
<div>
<input class="btn btn-primary my-2" type="submit"
value="Зарегистрироваться" />
</div>
</form>
В тег <form> нужно добавить необычное использование атрибутов hx-target и hx-swap. Атрибут hx-target указывает на саму форму, а атрибут hx-swap декларирует само замену формы. Все вместе они позволяют сократить код в элементах управления формы. В самом деле, в элементах формы теперь требуется только с помощью атрибутов hx-post и hx-action указать, на какой метод действия нужно отправить запрос при срабатывании триггера. Только у первого элемента формы, у поля ввода имени, необходимо заменить стандартный триггер новым значением. Значение change означает отправить запрос всякий раз при завершении редактирования. А значение keyup delay:500ms changed означает отправить запрос по прошествии 500 миллисекунд после отжатия кнопки на клавиатуре.
Такое необычное использование гипермедийной системы позволяет оснастить форму регистрации валидацией на стороне сервера. В ту же самую папку Views/Simple нужно добавить и новое визуальное представление с отображением информации об успешном прохождении валидации и регистрации.
Листинг: Частичное представление Views/Simple/Complete.cshtml
@{
ViewBag.Title = "Успешная регистрация";
}
<div class="p-2 bg-light border rounded">
<h2>@ViewBag.Title</h2>
<p>Валидация успешно пройдена.</p>
</div>
Осталось только добавить в главное меню опытного приложения ссылку на этот новый контроллер. Добавьте изменения в шаблон визуальных представлений Views/Shared/_Layout.cshtml, как в следующем листинге.
Листинг: Изменения в шаблоне Views/Shared/_Layout.cshtml
<!DOCTYPE html>
<html lang="en">
<head>
…
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light
bg-white border-bottom box-shadow mb-3">
<div class="container-fluid">
…
<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=""
asp-controller="Home"
asp-action="Index">Главная</a>
</li>
@* Добавить новый блок *@
<li class="nav-item">
<a class="nav-link text-dark" asp-area=""
asp-controller="Simple"
asp-action="Registration">
Простейшая гипермедийная валидация</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
…
</body>
</html>
Для краткости статьи остальные элементы не показаны. В этот шаблон добавляется только один элемент со ссылкой на страницу с формой регистрации. Запустите приложение на выполнение и проверьте работу валидации.
Достаточно ввести только один символ в поле ввода имени и будут отображены пользователю проблемы валидации. Только после ввода корректных данных сообщения валидации будут скрыты и станет доступна кнопка "Зарегистрироваться".
Заключение
В этой статье мы, на основе пробного проекта, разобрали на простом примере простейший вариант валидации модели данных с использованием гипермедийных систем. На стороне сервера мы использовали самый простой способ коммуникации - HTTP. Вы получили сведения о новом способе валидации данных на стороне сервера. Я считаю идею «отсутствия проверки на стороне клиента» очень перспективной. Перспективной в том смысле, что она может несколько повысить производительность и облегчить создание приложений. Но только при разработке веб приложений на основе платформы ASP.NET Core и библиотеки Htmx.js.
Я буду рад, если Вам пригодился материал этой статьи. Буду благодарен конструктивной критике и замечаниям в комментариях к данной статье. Желаю Вам всяческих успехов в Ваших проектах.
Приложение
К статье прилагаю ссылку на свой репозиторий с примером приложения из этой статьи.