如何修复 .Net Core POST 操作中的 400 Bad Request 错误?

Gol*_*ide 10 entity-framework postman asp.net-core

我有一个 .Net Core 2.1 API,它使用 EF 核心发布数据。当我从 Postman 向 http://localhost:3642/task/create 发出 POST 请求时,我收到一个 400 Bad Request 错误(由于语法错误,该请求无法完成)。经过仔细研究,我得到了一个建议来注释掉来自控制器的 ValidateAntiForgery 令牌。当我通过此更改传递来自邮递员的请求时,我收到 200 Ok 状态消息,但没有数据被提交到 Sql Server 中的表。有什么我应该在我的 API 中配置的东西,我还缺少什么?

我的控制器如下所示:

 [HttpPost]
 // [ValidateAntiForgeryToken]
 public async Task<IActionResult> 
Create([Bind("Assignee,Summary,Description")] TaskViewModel taskViewModel)
    {
if (ModelState.IsValid)
            {
                _context.Add(taskViewModel);
 await _context.SaveChangesAsync();
                return RedirectToAction("Index");
            }
            return View();
        }
Run Code Online (Sandbox Code Playgroud)

在 TaskViewModel.cs 我有:

 public class TaskViewModel 
{
    [Required]
    public long Id { get; set; }

    [Required(ErrorMessage = "Please provide Task Summary")]
    [Display(Name = "Summary")]
    public string Summary { get; set; }

    [Required(ErrorMessage = "Please enter task description")]
    [Display(Name = "Description")]
    public string Description { get; set; }

    [Required(ErrorMessage = "Please select Assignee")]
    [Display(Name = "Assign To")]
    public string Assignee { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

这是我在 Postman 中的有效载荷:

{
    "Assignee": "Ed tshuma",
    "Summary": "Finish reconciliations",
    "Description": "collate all the pending data"
}
Run Code Online (Sandbox Code Playgroud)

Chr*_*att 10

这里有很多问题。首先,为什么要将视图模型保存到数据库中。在这种情况下,这实际上是一个实体,而不是视图模型。您绝对应该使用视图模型,但您还应该有一个单独的实体类。然后,您的视图模型应该只包含您希望实际允许用户编辑的Bind属性,完全不需要属性,无论如何都应该避免。(请参阅:绑定是邪恶的)。

// added "Entity" to the name to prevent conflicts with `System.Threading.Task`
[Table("Tasks")]
public class TaskEntity
{
    [Key]
    public long Id { get; set; }

    [Required]
    public string Summary { get; set; }

    [Required]
    public string Description { get; set; }

    [Required]
    public string Assignee { get; set; }
}

public class TaskViewModel
{
    [Required(ErrorMessage = "Please provide Task Summary")]
    [Display(Name = "Summary")]
    public string Summary { get; set; }

    [Required(ErrorMessage = "Please enter task description")]
    [Display(Name = "Description")]
    public string Description { get; set; }

    [Required(ErrorMessage = "Please select Assignee")]
    [Display(Name = "Assign To")]
    public string Assignee { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

还要注意责任分工。实体只有对数据库重要的东西([Required]这里表示该列应该是不可为空的)。而视图模型只关心视图。没有Id属性,因为它不需要或不需要,并且显示名称和错误消息显示给用户只放在这里。

然后,您需要从您的视图模型映射到您的实体类:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(TaskViewModel model)
{
    if (!ModelState.IsValid)
        return View(model);

    var task = new TaskEntity
    {
        Assignee = model.Assignee,
        Summary = model.Summary,
        Description = model.Description
    };

    _context.Add(task);
    await _context.SaveChangesAsync();
    return RedirectToAction("Index");
}
Run Code Online (Sandbox Code Playgroud)

这里的映射是相当直接的,但你可能更愿意使用像AutoMapper库来处理这给你:_mapper.Map<TaskEntity>(model)

虽然这是专门针对创建操作的,但值得指出更新的细微差别。您需要首先从数据库中检索现有任务,然后将发布的值映射到该任务上。其余保持相对相同:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Update(long id, TaskViewModel model)
{
    if (!ModelState.IsValid)
        return View(model);

    var task = await _context.Tasks.FindAsync(id);
    if (task == null)
        return NotFound();

    task.Assignee = model.Assignee;
    task.Summary = model.Summary;
    task.Description = model.Description;

    await _context.SaveChangesAsync();
    return RedirectToAction("Index");
}
Run Code Online (Sandbox Code Playgroud)

最后,关于你的问题的主要问题,有两个问题。首先,此操作是为传统的 HTML 表单发布 ( x-www-form-urlencoded) 设计的。因此,将 JSON 发送给它是没有意义的,并且将 JSON 发送给它是行不通的。要在 Postman 中对其进行测试,您应该将请求作为x-www-form-urlencoded. 如果您不这样做,那么您的模型基本上将始终无效,因为帖子正文中没有任何内容绑定到您的模型。

为了接收 JSON,您的参数需要FromBody应用该属性 ( [FromBody]TaskViewModel model)。但是,如果这样做,您将无法再接收传统的表单帖子,在这种情况下,这就是将要发送的内容。如果您通过 AJAX 发送(您可以想象使用 JSON),那么您还应该返回JSON 或PartialView,但不是View或重定向。

最后,您需要包含请求验证令牌,它应该是 post body name 中的另一个键__RequestVerificationToken。要获取要发送的值,您首先需要加载视图的 GET 版本,然后检查源代码。将有一个带有该值的隐藏输入。


Rya*_*yan 7

克里斯·帕拉特是对的,你需要发送__RequestVerificationToken

如果你注释掉[ValidateAntiForgeryToken]属性,看起来你是从Body-raw-JSON发送数据,那么你需要使用[FromBody]来访问数据。

[HttpPost]
public async Task<IActionResult> Create([Bind("Assignee,Summary,Description")] [FromBody] TaskViewModel taskViewModel)
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

如果您不想添加[FromBody],您可以使用以下方式发送数据form-data

在此输入图像描述