如何修复 .Net Core POST 操作中的 400 Bad Request 错误?
Posted
技术标签:
【中文标题】如何修复 .Net Core POST 操作中的 400 Bad Request 错误?【英文标题】:How do I fix a 400 Bad Request error in .Net Core POST operation? 【发布时间】:2019-09-07 11:44:51 【问题描述】:我有一个使用 EF 核心发布数据的 .Net Core 2.1 API。当我从 Postman 向 http://localhost:3642/task/create 发出 POST 请求时,我收到 400 错误请求错误(由于语法错误,请求无法完成)。在四处挖掘后,我得到了一个注释掉的建议来自控制器的 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();
在 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;
这是我在 Postman 中的有效载荷:
"Assignee": "Ed tshuma",
"Summary": "Finish reconciliations",
"Description": "collate all the pending data"
【问题讨论】:
Id
是必需的,并且不存在于显示的有效负载中。这会导致 ModelState.IsValid
返回 false
并且不提交。
【参考方案1】:
这里有很多问题。首先,为什么要将视图模型保存到数据库中。在这种情况下,这实际上是一个 entity,而不是视图模型。您绝对应该使用视图模型,但您还应该有一个单独的实体类。然后,您的视图模型应该只包含您希望实际允许用户编辑的属性,完全不需要 Bind
属性,无论如何都应该避免。 (见:Bind is Evil)。
// 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;
另外,请注意责任分工。实体只有对数据库重要的东西([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");
这里的映射相当简单,但您可能更喜欢使用像 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");
最后,关于你的问题的主要问题,有两个问题。首先,此操作是为传统的 html 表单帖子 (x-www-form-urlencoded
) 设计的。因此,向它发送 JSON 是没有意义的,并且向它发送 JSON 将不起作用。要在 Postman 中测试它,您应该将请求发送为 x-www-form-urlencoded
。如果你不这样做,那么你的模型基本上总是无效的,因为没有任何东西会从帖子正文绑定到你的模型。
为了接收 JSON,您的参数需要应用 FromBody
属性 ([FromBody]TaskViewModel model
)。但是,如果您这样做,您将无法再收到传统的表单帖子,在这种情况下,这就是将要发送的内容。如果您通过 AJAX 发送(可以想象使用 JSON),那么您也应该返回 JSON 或PartialView
,但不是View
或重定向。
最后,您需要包含请求验证令牌,它应该是帖子正文名称__RequestVerificationToken
中的另一个键。要获取要发送的值,您需要首先加载视图的 GET 版本,然后检查源代码。将有一个隐藏的输入值。
【讨论】:
【参考方案2】:Chris Pratt 说得对,你需要发送__RequestVerificationToken
。
如果注释掉[ValidateAntiForgeryToken]
属性,好像是从Body-raw-JSON发送数据,那么需要使用[FromBody]来访问数据。
[HttpPost]
public async Task<IActionResult> Create([Bind("Assignee,Summary,Description")] [FromBody] TaskViewModel taskViewModel)
如果您不想添加 [FromBody],可以使用 form-data
发送数据
【讨论】:
你的 2Headers
是什么?【参考方案3】:
如果你想使用装饰器[ValidateAntiForgeryToken]
,你必须在你的请求中发送防伪令牌。请参阅this link 了解更多信息。
另外,即使你的模型无效,你return View()
。这意味着即使您发送了错误的数据,您也会获得 http 状态 200。
在if(ModelState.IsValid)
上设置断点并检查是否输入。如果不是,请检查有效载荷的格式。
希望对你有帮助。
编辑关于您的有效负载和模型:由于 TaskViewModel 中的 [Required]
装饰器,您需要为有效负载提供 Id
。或者你需要去掉Id
上的[Required]
属性。如果你不这样做,if (ModelState.IsValid)
将永远是错误的。
【讨论】:
以上是关于如何修复 .Net Core POST 操作中的 400 Bad Request 错误?的主要内容,如果未能解决你的问题,请参考以下文章
在 POST API 中传递多个参数,而不使用 .Net Core MVC 中的 DTO 类
asp net core 3 在发布后获取带有正文内容的 POST 操作的 BadRequest 响应
如何修复 .net core 2.2 应用程序中未找到的 swagger.json
ASP.NET Core Web API HTTP POST 在 Azure 中返回 404