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)
-
Start it. You should see windows for the Demo and Swagger.
-
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); } }
-
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(); }
-
Restart the project to see POST added in the Swagger:
-
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.
-
Go back to the App.jsx and put a break point inside the function.
-
Refresh the demo page. Verify that the above break point works:
-
Press F5 to continue and verify that it works in "public IEnumerable<WeatherForecast> Get()" as well:
At this point we can see that all works as expected.
-
Go back to App.jsx and change the App function to point to the post request.
-
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:
-
Continue (F5)[may require a couple of F5s] to see that it went directly to the error, and did not get the server method!
-
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
Here’s my test result based on your steps. Firstly, my react app hosts on
5173
port and API hosts on7176
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 ishttps://localhost:7176/WeatherForecast
but nothttp://localhost:5173/WeatherForecast/PostWeatherForecast
.If I used
const response = await fetch("weatherforecast" , requestOptions);
to send the API inpopulateWeatherData_2
I can call the API endpoint successfully. But since thebody: raw,
in defined in request body, we can’t get it indynamic args
as it’s defined to get data from request header. By the way, the request sent by fetch ishttps://localhost:5173/weatherforecast
, we can see it in browser console window. I just followed what the originpopulateWeatherData()
did.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 ashttps://localhost:7176/WeatherForecast/PostWeatherForecast
, I will get 405 error.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):