skip to Main Content

I’ve decided to convert my React + dotNet 3.1 API app to a dotNet 8.0 one. The old one is working fine, but I want to move to a Kube Container and 3.1 is no longer supported by MS. Nothing outstanding, a rather straightforward app. On the second day I ran into this completely unexpected problem with the POST method with dynamic args passed. Dynamic is rather a point of contention for me. It simplifies things tremendously and, honestly, I did not try to see if the explicit args would work. Why, because it works in dotNet 3.1!

Here’s how to replicate it:

Tool:
Microsoft Visual Studio Professional 2022
Version 17.10.5
VisualStudio.17.Release/17.10.5+35122.118
Microsoft .NET Framework
Version 4.8.09032

Create a new project: React and ASP.NET Core; A full stack application with React project and a backend ASP.NET Core. .NET8.0 (Long Term Support)

  1. Start it. You should see windows for the Demo and Swagger.

  2. Open up the file …reactapp1.clientsrcApp.jsx . Scroll down to the function "async function populateWeatherData()", add another function and save the file. Port is the one you see in your demo page:

    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. Open up the file …ReactApp1.ServerControllersWeatherForecastController.cs. Add the below function and save the file.

    [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. Restart the project to see POST added in the Swagger:

    enter image description here

  5. Put a break point on the line just below the method name in source file. Execute "Try it out" and verify that the break point worked and you can see the value of args.

  6. Go back to the App.jsx and put a break point inside the function.

  7. Refresh the demo page. Verify that the above break point works:

  8. Press F5 to continue and verify that it works in "public IEnumerable<WeatherForecast> Get()" as well:
    enter image description here

    At this point we can see that all works as expected.

  9. Go back to App.jsx and change the App function to point to the post request.
    enter image description here

  10. Put a break point in "populateWeatherData_2" function and another one in the catch section. Save the file. Either right after save or after refresh verify that execution stopped at the break point:

  11. Continue (F5)[may require a couple of F5s] to see that it went directly to the error, and did not get the server method!
    enter image description here

  12. This is the complete Controller definition:

    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();
            }
    
        }
    }
    
    

The server responds correctly to POST when the Postman is used. The Postman was useful for the POST function creation, but I do not trust it any more, because it looks like it uses CURL.

I used both ‘fetch’ and ‘Axios’ and tested the code with VSC – same result. If you try to dig debug deeper you can see that the request reaches the API and breaks in one of the methods. I am not sure if it is MS bug or I am missing something.

Did anybody ran into this? Any suggestions?

2

Answers


  1. Here’s my test result based on your steps. Firstly, my react app hosts on 5173 port and API hosts on 7176 port. No matter I use [FromHeader] dynamic args or [FromBody] dynamic args, I can call the POST endpoint successfully. And just like what you can see in screenshot below, the url for the post method is https://localhost:7176/WeatherForecast but not http://localhost:5173/WeatherForecast/PostWeatherForecast.

    enter image description here

    If I used const response = await fetch("weatherforecast" , requestOptions); to send the API in populateWeatherData_2 I can call the API endpoint successfully. But since the body: raw, in defined in request body, we can’t get it in dynamic args as it’s defined to get data from request header. By the way, the request sent by fetch is https://localhost:5173/weatherforecast, we can see it in browser console window. I just followed what the origin populateWeatherData() did.

    enter image description here

    If I want to use const response = await fetch("https://localhost:7176/WeatherForecast" , requestOptions);, I will get CORS error, if I set the CORS policy in the API project, I can call the API endpoint too. In the meantime, even if I set CORS policy, if I set the url as https://localhost:7176/WeatherForecast/PostWeatherForecast, I will get 405 error.

    enter image description here

    Login or Signup to reply.
  2. I found extremely problematic working with dynamic as endpoint method param in ASP.NET – only way to get information out of it was to use such code (example done with Minimal APIs, but can be used in controllers as well):

    app.MapPost("/dynamic", async ([FromBody] dynamic body) =>
    {
        var je = (JsonElement)body;
        foreach (var prop in je.EnumerateObject())
        {
            var propName = prop.Name;
            var propValue = prop.Value.GetString();
            // You could use other methods such as GetByte(), etc.
            // to get other values.
        }
    })
    .WithOpenApi();
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search