代码之家  ›  专栏  ›  技术社区  ›  Alexander Nedelchev

为什么[FromBody]会导致React和ASP之间的CORS和AmbiguousMatchException异常。NET核心8 Web API?

  •  0
  • Alexander Nedelchev  · 技术社区  · 10 月前

    我有一个以React为前端的web应用程序。NET核心8作为后端。 该应用程序具有导航功能 Meals 按钮。单击它后,会出现一个子导航,其中还有两个按钮。其中之一是 All meals 另一个是 Add meal 。在我决定前端在取所有餐点时发送当前日期之前,两者都运行得很好。当我在后端控制器中添加时 [FromBody] string date 它开始抛出两个尚未抛出的错误,直到我删除 [FromBody]字符串日期 并重建项目。

    以下是错误,第一个来自后端,第二个来自前端: 1-`微软。AspNetCore。诊断。开发者例外页面中间件[1] 执行请求时发生未经处理的异常。 微软。AspNetCore。路由。匹配。AmbiguousMatchException:请求匹配了多个端点。比赛:

      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;
    

    这是我的后端控制器(不工作时):

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

    我尝试了一下,基本上我添加了什么作为参数来捕捉,比如 [FromQuery] string date ,简单 string date 等等——错误发生了。 我似乎无法弄清楚问题是什么,因为我收到的错误与实际问题无关,否则一切都很好。

    我原本希望简单地做 [FromBody]字符串日期 并捕获从前端发送的日期,但它进入了噩梦模式。

    1 回复  |  直到 10 月前
        1
  •  1
  •   Stian Standahl    10 月前

    我建议为All操作创建一个模型,如下所示:

    [HttpPost("all")]
    public async Task<IActionResult> AllMeals([FromBody] AllMealsRequest allMealsRequest)
    {
      [...]
    }
    
    // With a class similar to this: 
    public class AllMealsRequest
    {
       public string Date { get; set; }
    }
    

    原因是json已经成为事实上的标准,默认情况下,api在发送有效载荷时需要一个json。制作一个对象只会让它与api一起很好地发挥作用。此外,以后更容易在不破坏api的情况下向请求体添加属性。

    如果这是一个纯粹的 获取信息 api调用时,您应该将您的api更改为HttpGet,并使用 FromQuery 参数上的属性。当你调用端点时,你应该在url中使用一个查询参数,比如这样: 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)
    {
         [...]
    }