I have a web app that has React as front-end and .NET core 8 as back-end.
The app has a navigation with Meals
button. Upon clicking it a sub-navigation with two more buttons appears. One of them is All meals
and the other one is Add meal
. Everything was working perfectly fine with both until I decided that the front-end should send the current date when fetching for all the meals. When I then added in the back-end controller [FromBody] string date
it started throwing two errors that have not been thrown and it would not get fixed until I remove the [FromBody] string date
and rebuild the project.
Here are the errors, first is from the back-end and the second is from the front-end: 1 - `Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1] An unhandled exception has occurred while executing the request. Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints. Matches:
Fitness_Tracker.Controllers.MealController.AllMeals (Fitness-Tracker)
Fitness_Tracker.Controllers.MealController.AllMeals (Fitness-Tracker)
at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ReportAmbiguity(Span1 candidateState)
at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ProcessFinalCandidates(HttpContext httpContext, Span1 candidateState)
at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.Select(HttpContext httpContext, Span1 candidateState)
at Microsoft.AspNetCore.Routing.Matching.DfaMatcher.MatchAsync(HttpContext httpContext)
at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)`
2-
Access to fetch at 'https://localhost:7009/api/meal/all' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
import React, { useEffect, useState } from 'react';
import '../../css/AllMealsPage.css';
const AllMealsPage = () => {
const [meals, setMeals] = useState([]);
const [calories, setCalories] = useState(0);
const [errorMessage, setErrorMessage] = useState('');
const [selectedDate, setSelectedDate] = useState(new Date());
useEffect(() => {
const fetchMeals = async () => {
try {
const response = await fetch('https://localhost:7009/api/meal/all', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ date: selectedDate.toISOString() })
});
if (!response.ok) {
throw new Error('Displaying meals failed');
}
const data = await response.json();
setMeals(data);
setErrorMessage('');
} catch (err) {
setErrorMessage(err.message);
}
};
fetchMeals();
}, [selectedDate]);
useEffect(() => {
const fetchCalories = async () => {
try {
const response = await fetch('https://localhost:7009/api/meal/calories', {
method: 'POST',
credentials: 'include'
});
if (!response.ok) {
throw new Error('Displaying calories failed');
}
const data = await response.json();
setCalories(data);
setErrorMessage('');
} catch (err) {
setErrorMessage(err.message);
}
};
fetchCalories();
}, [selectedDate]);
const handlePreviousDay = () => {
setSelectedDate(prevDate => {
const newDate = new Date(prevDate);
newDate.setDate(prevDate.getDate() - 1);
return newDate;
});
};
const handleNextDay = () => {
setSelectedDate(prevDate => {
const newDate = new Date(prevDate);
const today = new Date();
if (newDate.toDateString() !== today.toDateString()) {
newDate.setDate(prevDate.getDate() + 1);
}
return newDate;
});
};
return (
<div className="all-meals-container">
{errorMessage && <p className="error-message">{errorMessage}</p>}
<div className="date-navigation">
<button onClick={handlePreviousDay}>←</button>
<span>{selectedDate.toDateString()}</span>
<button onClick={handleNextDay} disabled={selectedDate.toDateString() === new Date().toDateString()}>→</button>
</div>
<div className="table-wrapper">
<table className="meals-table">
<thead>
<tr>
<th>Meal Name</th>
<th>Meal of the Day</th>
<th>Calories</th>
</tr>
</thead>
<tbody>
{meals.map((meal) => (
<tr key={meal.id}>
<td>{meal.name}</td>
<td>{MealOfTheDayLabel(meal.mealOfTheDay)}</td>
<td>{meal.calories}</td>
</tr>
))}
</tbody>
</table>
</div>
<h1>Total calories: {calories}</h1>
</div>
);
};
const MealOfTheDayLabel = (mealOfTheDay) => {
switch (mealOfTheDay) {
case 0:
return 'Breakfast';
case 1:
return 'Lunch';
case 2:
return 'Dinner';
case 3:
return 'Snack';
default:
return 'Unknown';
}
};
export default AllMealsPage;
And here is my back-end controller(when not working):
namespace Fitness_Tracker.Controllers
{
using Fitness_Tracker.Data.Models;
using Fitness_Tracker.Models.Meals;
using Fitness_Tracker.Services.Meals;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
public class MealController : BaseApiController
{
private readonly IMealService _mealService;
public MealController(IMealService mealService)
{
this._mealService = mealService;
}
[HttpPost("add")]
public async Task<IActionResult> AddMeal([FromBody] AddMealModel model)
{
if(!ModelState.IsValid)
{
return BadRequest();
}
string userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if(userId == null) {
return BadRequest();
}
await _mealService.CreateMealAsync(userId, model);
return Ok();
}
[HttpPost("all")]
public async Task<IActionResult> AllMeals(string date)// Remove [FromBody] string date and it works
{
string userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if(userId == null)
{
return BadRequest();
}
List<Meal> result = await _mealService.GetAllUserMealsAsync(userId);
return Ok(result);
}
[HttpPost("calories")]
public async Task<IActionResult> AllMealsCalories()
{
string userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (userId == null)
{
return BadRequest();
}
int result = await _mealService.GetTotalUserMealCaloriesAsync(userId);
return Ok(result);
}
}
}
I played around with it and basically whatever I add to catch as a parameter, such as [FromQuery] string date
, simply string date
and etc. - the errors occur.
I can't seem to figure out what could the issue be, since the errors I receive have nothing to do with the actual issue, because otherwise everything is perfectly fine.
I expected to simply do [FromBody] string date
and catch the date being sent from the front-end, but it went into nightmare mode.
I recommend creating a model for the All action like this:
[HttpPost("all")]
public async Task<IActionResult> AllMeals([FromBody] AllMealsRequest allMealsRequest)
{
[...]
}
// With a class similar to this:
public class AllMealsRequest
{
public string Date { get; set; }
}
The reason for this is that json has become defacto standard, and the api by default expects a json when you send a payload. Making an object just makes it play nice together with the api. Furthermore, it is more easy at a later time to add properties to the request body without breaking the api in the future.
Elaborating a bit more, if it is a purely get information api call, you should change your api to be HttpGet with a FromQuery
attribute on the parameter. When you call the endpoint you should instead use a query parameter in the url such as this:
https://localhost:7009:/api/meal/all?date=[your date here]
// https://localhost:7009:/api/meal/all?date=[your date here]
[HttpGet("all")]
public async Task<IActionResult> AllMeals([FromQuery] string date)
{
[...]
}