JSON (JavaScript Object Notation) – это текстовый объектный формат для передачи небольших порций данных. JSON широко используется в веб-приложениях для обмена данными между сервером и клиентом посредством AJAX и Fetch API, что позволяет веб-страницам обновляться без перезагрузки.
Интерфейсы JavaScript AJAX и Fetch, при всех их различиях, оба используются для создания асинхронных запросов к серверу и не блокируют выполнение других задач, пока ожидается ответ. Оба прикладных интерфейса обеспечивают обмен различными типами данных: form data, JSON, plain text, images и другие медиа-форматы.
ASP.NET – это кроссплатформенная система для создания интерактивных веб-приложений. Она включает множество инструментов и библиотек, в том числе и для поддержки формата JSON. Используя клиентский JavaScript и серверную ASP.NET, можно легко организовать асинхронный обмен сообщениями между браузером и сервером.
Для предотвращения атак с межсайтовой подделкой запросов (XSRF/CSRF) в ASP.NET Core предусмотрена функциональность проверки подлинности запросов на основе токенов. Суть этого механизма: при каждой загрузке страницы сервер размещает на ней метку (токен) из случайных символов, в дальнейшем сервер, получая HTTP-запрос вместе с токеном, сверяет полученную метку с контрольной, и, если метки не совпадают, запрос отклоняется. Таким образом исключается прием запроса от чужого сайта.
Токен устанавливается на веб-страницу в любом удобном месте директивой @Html.AntiForgeryToken() и требуемый action-метод контроллера помечается атрибутом [ValidateAntiForgeryToken]. В случае создания JSON-сообщения метка может устанавливаться в любом месте страницы, при отправке данных HTML-форм – внутри формы. Клиентский код отправляет токен на сервер как отдельный заголовок с именем RequestVerificationToken.
Отправка токена безопасности на сервер посредством интерфейсов AJAX и Fetch:
// Отправка сообщение формата JSON используя Fetch API
jsonFetch() {
const url = '/application-json';
const options = {
method: 'POST',
headers: {
// Возврат токена на сервер для проверки на
// фальшивые запросы от стороннего сайта(XSRF или CSRF).
'RequestVerificationToken': this.getToken(),
'Content-Type': 'application/json',
},
body: JSON.stringify(this.sendJson("Fetch"))
};
fetch(url, options)
...
}
// Отправка сообщение формата JSON используя AJAX API
jsonAjax() {
// Для доступа к объекту текущего класса в теле другого объекта.
const myajax_this = this;
const url = '/application-json';
// Создаем объект AJAX XMLHttpRequest
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/json');
// Возврат токена на сервер для проверки на
// подделку запросов от стороннего сайта(XSRF или CSRF).
xhr.setRequestHeader('RequestVerificationToken', this.getToken());
...
}
Пример представления ASP.NET MVC с директивой генерации токена:
@{
ViewData["Title"] = "Hello World!";
}
<!doctype html>
<html>
<head>
...
<title>@ViewData["Title"]</title>
</head>
<body>
@* На веб-странице, в данном месте, будет метка безопасности *@
@Html.AntiForgeryToken()
<h1>@ViewData["Title"]</h1>
...
</body>
</html>
Action-метод контролёра с атрибутом проверки токена:
Код асинхронной отправки на сервер скомбинирован из классического XMLHttpRequest и более современного Fetch API. Каждое нажатие на кнопку отправки изменяет асинхронное API, чередуя работу функций XMLHttpRequest и Fetch. Поочередное комбинирование API отправок теоретически должно показать разницу в работе асинхронных JavaScript API, но вряд ли пользователь заметит различия в работе функций AJAX и Fetch.
Программный код на Typescript отправки и получение JSON данных (файл исходника, прикрепленный внизу страницы, содержит клиентский код на TypeScript и JavaScript):
class MyAjax {
counter: boolean = true;
constructor() {
this.initUIJson();
}
/**
* Подготовка интерфейса для отправки сообщений формата JSON
*/
initUIJson() {
// Регистрируем обработчик события отправки
// данных в формате JSON.
const btnJson = document.getElementById("btnJson");
btnJson.onclick = () => {
this.counter == true ? this.jsonFetch() : this.jsonAjax();
this.counter = !this.counter;
};
}
/**
* Отправка сообщение формата JSON
* используя Fetch API
*/
jsonFetch() {
const url = '/application-json';
const options = {
method: 'POST',
headers: {
// Возврат токена на сервер для проверки на
// фальшивые запросы от стороннего сайта(XSRF или CSRF).
'RequestVerificationToken': this.getToken(),
'Content-Type': 'application/json',
},
body: JSON.stringify(this.sendJson("Fetch"))
};
fetch(url, options)
//.then(response => response.json())
.then(response => response.text())
.then(data => {
// Обрабатываем ответ сервера
this.receiveJson(data);
//alert(data.api);
})
.catch(error => {
// Обрабатываем ошибку
console.error("error: " + error);
});
}
/**
* Отправка сообщение формата JSON
* используя Fetch API
*/
jsonAjax() {
// Для доступа к объекту текущего класса в теле другого объекта.
const myajax_this = this;
const url = '/application-json';
// Создаем объект AJAX XMLHttpRequest
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/json');
// Возврат токена на сервер для проверки на
// подделку запросов от стороннего сайта(XSRF или CSRF).
xhr.setRequestHeader('RequestVerificationToken', this.getToken());
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
// Получаем ответ сервера
myajax_this.receiveJson(xhr.responseText);
}
};
// Преобразуем данные в строку JSON перед отправкой
var jsonStr = JSON.stringify(this.sendJson("AJAX"));
xhr.send(jsonStr);
}
/**
* Получение токена безопасности со страницы
* @returns
*/
getToken() {
const token = document.getElementsByName("__RequestVerificationToken") as unknown as HTMLInputElement;
return token[0].value;
}
/**
* JSON отправка на сервер
* @param api
* @returns
*/
sendJson(api: string): any {
return {
"api": api,
"inputText": (document.getElementById("inputText") as HTMLInputElement)?.value,
"inputNumber": (document.getElementById("inputNumber") as HTMLInputElement)?.value,
"inputCheck": (document.getElementById("inputCheck") as HTMLInputElement)?.checked,
};
}
/**
* Вывод JSON-сообщений от сервера
*
* @param jsonStr
*/
receiveJson(jsonStr: string) {
const json_object = JSON.parse(jsonStr);
const resultApi = document.getElementById("resultApi");
resultApi.innerText = json_object.api;
const resultText = document.getElementById("resultText");
resultText.innerText = json_object.inputText;
const resultNumber = document.getElementById("resultNumber");
resultNumber.innerText = json_object.inputNumber;
const resultCheck = document.getElementById("resultCheck");
resultCheck.innerText = json_object.inputCheck;
}
}
const myAjax = new MyAjax();
Один из способов получения сообщения при AJAX обмене – это извлечение его из объекта класса HttpRequest, который заполняется данными в action-методах контроллера. Простой текст и формат JSON извлекаются из тела запроса HttpRequest.Body в виде последовательности байтов. Посредством статического класса JsonSerializer поток байтов преобразуется в объект, родственный отправленному: с одноименными и однотипными переменными.
Action-метод контроллера ASP.NET MVC с кодом получения JSON данных из тела HTTP-сообщения:
// веб-адрес для асинхронного запроса
[Route("/application-json")]
// Метод получения HTTP сообщения
[HttpPost]
// Проверка маркера защиты от подделки межсайтовых запросов
[ValidateAntiForgeryToken]
public async Task JsonTextAsync()
{
// Получение HTTP-сообщения
var httpRequest = HttpContext.Request;
// Извлечение JSON данных из тела сообщения
JsonObj? jsonObj = await JsonSerializer.DeserializeAsync(httpRequest.Body, _options);
return Content(JsonSerializer.Serialize(jsonObj));
}
// Класс с переменными однотипными и одноименными
// для извлекаемого JSON-объекта.
public class JsonObj
{
public string? api { get; set; }
public string? inputText { get; set; }
public string? inputNumber { get; set; }
public bool inputCheck { get; set; }
}
Более удобный способ получения JSON-данных из параметра action-метода. В отличие от предыдущего примера здесь не требуется создавать собственный код извлечения JSON объекта из HTTP запроса. Извлечение в таком случае будет происходить автоматически с помощью встроенных модулей форматирования, работающие на основе типа мультимедиа, таких как: text/plain, multipart/form-data, application/x-www-form-urlencoded, application/json и др.
Привязка параметра к входным JSON-данным осуществляется атрибутом [FromBody], указывающим что значение должно извлекаться из тела HTTP-запроса. Примечание: атрибут действует однократно и только на параметр, следующий за атрибутом. В качестве объектов для привязки используются отдельные классы (модели) с однотипными и одноименными свойствами по отношению к ожидаемому JSON-объекту.
Программный код получения JSON-данных из параметров action-метода контроллера:
// URL для AJAX отправки
[Route("/application-json")]
// Метод HTTP-запроса
[HttpPost]
// Проверка токена безопасности
[ValidateAntiForgeryToken]
// Привязка модели (объекта класса) к данным тела запроса.
public IActionResult JsonText([FromBody] JsonObj jsonObj)
{
// Отправка полученных данных клиенту
return Json(jsonObj, _options);
}
// Модель для извлекаемого JSON-объекта.
public class JsonObj
{
public string? api { get; set; }
public string? inputText { get; set; }
public string? inputNumber { get; set; }
public bool inputCheck { get; set; }
}
Исходник веб-приложения написан в Visual Studio 2022. Исходный код приложения содержит серверный код ASP.NET MVC на языке C# и клиентский код на TypeScript, JavaScript.
Работа веб-приложения: при нажатии на кнопку осуществляется отправка JSON данных и ожидается ответ сервера. При каждом нажатии на кнопку изменяется JavaScript API асинхронной работы.