从 React 前端使用 API POST 到 Entity Framework 控制器时总是会出现空异常

Always get null exceptions when using API POST to an Entity Framework controller from a React front-end

我正在使用 .NetCore 3.1 Entity Framework 创建在线游戏系统。

我有一堆模型和控制器设置,每个模型在我的 MSSQL 数据库中代表一个 table,每个模型都有一个控制器。

这些模型的控制器工作正常。

但是现在,我有一个表单,用户可以在其中创建一个包含两个不同模型的新对象。

所以当用户提交表单时,需要在两者中创建一个新项目models/tables。

所以我创建了一个单独的模型 class 就像这样:

namespace My_Game.Models
{
    public partial class CreateGame
    {
        public virtual StarSystem StarSystem { get; set; }
        public virtual Ship Ship { get; set; }
    }
}

以下是上述模型使用的两个模型:

public partial class StarSystem
{
    public string Name { get; set; }
    public long Location { get; set; }
}

public partial class Ship
{
    public string Name { get; set; }
    public string Type { get; set; }
    public long MaxCrew { get; set; }
}

这是我的控制器,它应该处理 API 调用:

[HttpPost]
public async Task<ActionResult> ProcessForm([FromBody] CreateGame newGameEntry)
{
    // Read StarSystem data from form and add to DB via EF
    _context.StarSystem.Add(newGameEntry.StarSystem);
    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateException)
    {
        if (StarSystemExists(newGameEntry.StarSystem.Id))
        {
            return Conflict();
        }
        else
        {
            throw;
        }
    }

    // Read mentor data from form and add to DB via EF
    _context.Ship.Add(newGameEntry.Ship);
    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateException)
    {
        if (ShipExists(newGameEntry.Ship.Id))
        {
            return Conflict();
        }
        else
        {
            throw;
        }
    }

    return Ok();
}
private bool ShipExists(long id)
{
    return _context.Ship.Any(e => e.Id == id);
}

private bool StarSystemExists(long id)
{
    return _context.StarSystem.Any(e => e.Id == id);
}

这是用于将表单发送到 API 的前端 React 组件:

import React, { useState } from 'react';
import axios from 'axios';

const App = () => {

    const handleSubmit = (e) => {
        e.preventDefault()
        const { myForm } = e.target.elements.myForm
        axios.post('https://localhost:44376/api/formprocessor', { form: myForm })
    }

    return (
        <div id="newGameForm">
            <form id="myForm" onSubmit={handleSubmit}>
                <input type="text" name="starSystemName" placeholder="Enter star system name:" />
                <input type="text" name="starSystemLocation" placeholder="Enter star system location:" />
                <input type="text" name="shipName" placeholder="Enter ship name:" />
                <input type="text" name="shipType" placeholder="Enter ship type:" />
                <input type="text" name="shipMaxCrew" placeholder="Enter max crew:" />                  
                <button type="submit">Submit</button>
            </form>
        </div >
    )
}

但是每当我尝试从反应页面点击控制器时,我只会在调试模式下收到此错误:

System.ArgumentNullException: 值不能为空。 (参数'entity')

我也尝试使用 Postman 进行测试,并将虚拟测试值放入主体中,但我得到了同样的错误。

我错过了什么?

谢谢!

最有可能的问题是发送数据的格式与操作中使用的模型不匹配。模型活页夹无法根据发送的内容进行填充。

发送表单数据后,更新操作以期望来自表单的数据

[HttpPost]
public async Task<ActionResult> ProcessForm([FromForm] CreateGame newGameEntry) {
    //...
}

接下来更新客户端以发送适当的表单数据。

const qs = require('querystring')

//...

const handleSubmit = (e) => {
    e.preventDefault()
    const { myForm } = e.target.elements.myForm
    const form = new FormData();
    for ( const key in myForm) {
        form.append(key, myForm[key]);
    }

    axios({
        method: 'post',
        url: 'https://localhost:44376/api/formprocessor',
        data: qs.stringify(form),
        headers: {'Content-Type': 'application/x-www-form-urlencoded' }
    });
}

return (
    <div id="newGameForm">
        <form id="myForm" onSubmit={handleSubmit}>
            <input type="text" name="starSystem.Name" placeholder="Enter star system name:" />
            <input type="text" name="starSystem.Location" placeholder="Enter star system location:" />
            <input type="text" name="ship.Name" placeholder="Enter ship name:" />
            <input type="text" name="ship.Type" placeholder="Enter ship type:" />
            <input type="text" name="ship.MaxCrew" placeholder="Enter max crew:" />
            <button type="submit">Submit</button>
        </form>
    </div >
)

请注意名称更改以匹配所发送模型的结构。这样模型绑定器就会知道如何将发送的数据映射到预期的模型。

引用Model Binding in ASP.NET Core

基于 React Forms 参考我下面的演示 post 表单数据到控制器:

import React, { Component } from 'react';
import axios from 'axios';

export class FormTest extends Component {
    constructor(props) {
        super(props);

        this.state = {
            starSystemName: "",
            starSystemLocation:"",         
            shipName: "",
            shipType: "",
            shipMaxCrew:""
        };

        this.handleStarSystemName = this.handleStarSystemName.bind(this);
        this.handleStarSystemLocation = this.handleStarSystemLocation.bind(this);
        this.handleShipName = this.handleShipName.bind(this);
        this.handleShipType = this.handleShipType.bind(this);
        this.handleShipMaxCrew = this.handleShipMaxCrew.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }

    handleStarSystemName(event) {
        this.setState({              
                starSystemName: event.target.value 
        });

    }
    handleStarSystemLocation(event) {
        this.setState({
                starSystemLocation: event.target.value
        });
    }
    handleShipName(event) {
        this.setState({
                shipName: event.target.value
        });
    }  
    handleShipType(event) {
        this.setState({
                shipType: event.target.value
        });
    }
    handleShipMaxCrew(event) {
        this.setState({
                shipMaxCrew: event.target.value
        });
    }
    handleSubmit = (e) => {

        e.preventDefault();       
        const data = new FormData();
        data.append("starSystem.Name", this.state.starSystemName);
        data.append("starSystem.Location", this.state.starSystemLocation);
        data.append("ship.Name", this.state.shipName);
        data.append("ship.Type", this.state.shipType);
        data.append("ship.MaxCrew", this.state.shipMaxCrew);


        axios({
            method: 'post',
            url: 'https://localhost:44301/WeatherForecast',
            data: data,
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
        });
    }
    render() {
        return (
            <div id="newGameForm" >
                <form id="myForm" onSubmit={this.handleSubmit}>
                    <input type="text" value={this.state.starSystemName} placeholder="Enter star system name:" onChange={this.handleStarSystemName} />
                    <input type="text" value={this.state.starSystemLocation} placeholder="Enter star system location:" onChange={this.handleStarSystemLocation} />
                    <input type="text" value={this.state.shipName} placeholder="Enter ship name:" onChange={this.handleShipName} />
                    <input type="text" value={this.state.shipType} placeholder="Enter ship type:" onChange={this.handleShipType} />
                    <input type="text" value={this.state.shipMaxCrew} placeholder="Enter max crew:" onChange={this.handleShipMaxCrew}/>  
                    <button type="submit">Submit</button>
                </form>
            </div >
        )
    }
}

控制器:

[HttpPost]
public async Task<ActionResult> ProcessForm([FromForm] CreateGame newGameEntry)