Проблема с методом POST ([FromBody] dynamic …) на сервере API dotNet 8.0

Вопрос или проблема

Я решил преобразовать свое приложение 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 (долгосрочная поддержка)

  1. Запустите его. Вы должны увидеть окна для Демо и Swagger.

  2. Откройте файл …\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);
        }
    }
    
  3. Откройте файл …\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();
    }
    
  4. Перезапустите проект, чтобы увидеть добавленный POST в Swagger:

    введите описание изображения здесь

  5. Установите точку останова на строке сразу под именем метода в исходном файле. Выполните “Try it out” и убедитесь, что точка останова сработала и вы можете увидеть значение args.

  6. Вернитесь к App.jsx и установите точку останова внутри функции.

  7. Обновите страницу демонстрации. Убедитесь, что вышеупомянутая точка останова работает:

  8. Нажмите F5, чтобы продолжить, и убедитесь, что это также работает в “public IEnumerable<WeatherForecast> Get()”:
    введите описание изображения здесь

    На данном этапе мы можем видеть, что все работает, как ожидалось.

  9. Вернитесь в App.jsx и измените функцию App, чтобы она указывала на POST-запрос.
    введите описание изображения здесь

  10. Установите точку останова в функции “populateWeatherData_2” и еще одну в секции catch. Сохраните файл. Либо сразу после сохранения, либо после обновления убедитесь, что выполнение остановилось на точке останова:

  11. Продолжите (F5) [может потребоваться несколько F5], чтобы увидеть, что оно напрямую перешло к ошибке и не вызвало метод сервера!
    введите описание изображения здесь

  12. Вот полное определение контроллера:

    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. Если проблемы сохранятся, более подробная информация о сообщениях об ошибках и трассировках может помочь в дальнейшем анализе.

Оцените материал
Добавить комментарий

Капча загружается...