Вопрос или проблема
Я решил преобразовать свое приложение React + dotNet 3.1 API в приложение dotNet 8.0. Старое работает нормально, но я хочу перейти на Kube Container, а 3.1 больше не поддерживается Microsoft. Ничего выдающегося, довольно простое приложение. На второй день я столкнулся с этой совершенно неожиданной проблемой с методом POST с динамическими аргументами. Динамика вызывает у меня некоторые споры. Это значительно упрощает вещи, и, честно говоря, я не пытался проверить, сработают ли явные аргументы. Почему? Потому что в dotNet 3.1 это работает!
Вот как это воспроизвести:
Инструмент:
Microsoft Visual Studio Professional 2022
Версия 17.10.5
VisualStudio.17.Release/17.10.5+35122.118
Microsoft .NET Framework
Версия 4.8.09032
Создайте новый проект: React и ASP.NET Core; Полностековое приложение с проектом React и бэкендом ASP.NET Core. .NET8.0 (долгосрочная поддержка)
-
Запустите его. Вы должны увидеть окна для Демо и Swagger.
-
Откройте файл …\reactapp1.client\src\App.jsx. Прокрутите вниз до функции “async function populateWeatherData()”, добавьте другую функцию и сохраните файл. Порт – это тот, который вы видите на странице демонстрации:
async function populateWeatherData_2() { const myHeaders = new Headers(); myHeaders.append("Content-Type", "application/json"); const raw = JSON.stringify({ id: 0, name: "string", isComplete: true, }); const requestOptions = { method: "POST", headers: myHeaders, body: raw, redirect: "follow", }; try { const response = await fetch( "http://localhost:5173/WeatherForecast/PostWeatherForecast", requestOptions ); const result = await response.text(); console.log(result); } catch (error) { console.error(error); } }
-
Откройте файл …\ReactApp1.Server\Controllers\WeatherForecastController.cs. Добавьте нижеуказанную функцию и сохраните файл.
[HttpPost(Name = "PostWeatherForecast")] public IEnumerable<WeatherForecast> Post([FromHeader] dynamic args) { return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); }
-
Перезапустите проект, чтобы увидеть добавленный POST в Swagger:
-
Установите точку останова на строке сразу под именем метода в исходном файле. Выполните “Try it out” и убедитесь, что точка останова сработала и вы можете увидеть значение args.
-
Вернитесь к App.jsx и установите точку останова внутри функции.
-
Обновите страницу демонстрации. Убедитесь, что вышеупомянутая точка останова работает:
-
Нажмите F5, чтобы продолжить, и убедитесь, что это также работает в “public IEnumerable<WeatherForecast> Get()”:
введите описание изображения здесьНа данном этапе мы можем видеть, что все работает, как ожидалось.
-
Вернитесь в App.jsx и измените функцию App, чтобы она указывала на POST-запрос.
введите описание изображения здесь -
Установите точку останова в функции “populateWeatherData_2” и еще одну в секции catch. Сохраните файл. Либо сразу после сохранения, либо после обновления убедитесь, что выполнение остановилось на точке останова:
-
Продолжите (F5) [может потребоваться несколько F5], чтобы увидеть, что оно напрямую перешло к ошибке и не вызвало метод сервера!
введите описание изображения здесь -
Вот полное определение контроллера:
using Microsoft.AspNetCore.Mvc; namespace ReactApp1.Server.Controllers { [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; private readonly ILogger<WeatherForecastController> _logger; public WeatherForecastController(ILogger<WeatherForecastController> logger) { _logger = logger; } [HttpGet(Name = "GetWeatherForecast")] public IEnumerable<WeatherForecast> Get() { return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); } [HttpPost(Name = "PostWeatherForecast")] public IEnumerable<WeatherForecast> Post([FromHeader] dynamic args) { return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToArray(); } } }
Сервер корректно отвечает на POST, когда используется Postman. Postman был полезен для создания функции POST, но я больше ему не доверяю, потому что, похоже, он использует CURL.
Я использовал как ‘fetch’, так и ‘Axios’ и протестировал код с VSC – тот же результат. Если попробовать углубиться в отладку, можно увидеть, что запрос доходит до API и прерывается в одном из методов. Я не уверен, является ли это ошибкой Microsoft или я что-то упускаю.
Кто-нибудь сталкивался с этим? Есть предложения?
Ответ или решение
Проблема, с которой вы столкнулись при использовании метода POST в ASP.NET Core 8.0 с типом dynamic
, может быть связана с изменениями в обработки данных, передаваемых в теле запроса. Давайте рассмотрим несколько аспектов, которые могут помочь решить вашу проблему.
1. Изменение в dynamic
обработке в .NET 8.0
В .NET 8.0 изменился способ, которым ASP.NET Core обрабатывает динамические типы данных. Ваша текущая реализация использует [FromHeader]
для параметра dynamic args
, однако вы отправляете данные в теле запроса. Для работы с телом запроса необходимо использовать атрибут [FromBody]
, чтобы динамические данные были правильно десериализованы.
Пример исправленного метода POST
[HttpPost(Name = "PostWeatherForecast")]
public IEnumerable<WeatherForecast> Post([FromBody] dynamic args)
{
// Можно проверить данные, переданные в args
// Пример: Console.WriteLine(args.id);
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
2. Убедитесь, что клиентское приложение отправляет правильный запрос
Ваше клиентское приложение отправляет JSON-данные. Убедитесь, что структура передаваемых данных совпадает с тем, что ожидает ваш сервер. Измените метод populateWeatherData_2
на:
async function populateWeatherData_2() {
const myHeaders = new Headers({
"Content-Type": "application/json"
});
const raw = JSON.stringify({
id: 0,
name: "string",
isComplete: true,
});
const requestOptions = {
method: "POST",
headers: myHeaders,
body: raw,
redirect: "follow",
};
try {
const response = await fetch(
"http://localhost:5173/WeatherForecast/PostWeatherForecast",
requestOptions
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
console.log(result);
} catch (error) {
console.error('Fetch error: ', error);
}
}
Это изменит код, чтобы он также проверял наличие ошибок в ответе от сервера.
3. Проверка CORS и другие сетевые вопросы
Проверьте настройки CORS. Если ваше клиентское приложение работает на другом порту, убедитесь, что у вас правильно настроены CORS, чтобы разрешить запросы из различных источников.
Пример настройки CORS в Startup.cs
:
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("AllowAllOrigins",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseCors("AllowAllOrigins");
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Заключение
Попробуйте провести описанные выше изменения и протестируйте приложение снова. Это должно решить проблему с передачей данных через метод POST в ASP.NET Core 8.0. Если проблемы сохранятся, более подробная информация о сообщениях об ошибках и трассировках может помочь в дальнейшем анализе.