Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I fix a 400 Bad Request error in .Net Core POST operation?

I have an .Net Core 2.1 API that posts data using EF core. When I make a POST request from Postman to http://localhost:3642/task/create I get a 400 Bad Request Error (The request cannot be fulfilled due to Bad Syntax).After digging around I got a suggestion to comment out the ValidateAntiForgery token from the controller. When I pass the request from postman with this change I get 200 Ok status message but no data is being committed to the table in Sql Server. Is there something that I should configure in my API, something else am I missing?

My controller looks as follows:

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

In TaskViewModel.cs I have:

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

This is my payload in Postman:

{
    "Assignee": "Ed tshuma",
    "Summary": "Finish reconciliations",
    "Description": "collate all the pending data"
}
like image 1000
Golide Avatar asked Apr 17 '19 09:04

Golide


People also ask

What is a Post 400 error?

The HyperText Transfer Protocol (HTTP) 400 Bad Request response status code indicates that the server cannot or will not process the request due to something that is perceived to be a client error (for example, malformed request syntax, invalid request message framing, or deceptive request routing).

Why do I keep getting 400 Bad Request on Chrome?

What causes bad request errors on Chrome? Error 400 is a client error that occurs due to incorrect requests, invalid syntax, or routing issues. It can also occur if the URL is not recognized or you did not type it correctly. So, check again and make sure you typed the URL correctly.


1 Answers

There's a number of issues here. First and foremost, why are you saving your view model to the database. This is actually an entity in this case, not a view model. You should definitely be using a view model, but you should also have a separate entity class. Then, your view model should only contain properties that you want to actually allow the user to edit, negating the need entirely for the Bind attribute, which should be avoided anyways. (see: 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; }
}

Also, note the division of responsibility. The entity has only things that matter to the database ([Required] here indicates that the column should be non-nullable). Whereas the view model is concerned only with the view. There's no Id property, since it's not needed or desired, and the display names and error messages to be presented to the user are placed here only.

Then, you'll need to map from your view model to your entity class:

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

The mapping here is fairly straight-forward, but you may prefer to utilize a library like AutoMapper to handle this for you: _mapper.Map<TaskEntity>(model).

While this is specifically for a create action, it's worth pointing out the subtle difference for an update. You'll want to first retrieve the existing task from your database and then map the posted values onto that. The rest remains relatively the same:

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

Finally, as to the main problem from your question, there's two issues. First, this action is designed for a traditional HTML form post (x-www-form-urlencoded). As such, it doesn't make sense to send JSON to it, and sending JSON to it will not work. To test it in Postman, you should send the request as x-www-form-urlencoded. If you do not, then your model will essentially always be invalid, because nothing will be bound to your model from the post body.

In order to receive JSON, your param would need to have the FromBody attribute applied to it ([FromBody]TaskViewModel model). However, if you do that, you can no longer receive traditional form posts, and in this context, that's what's going to be sent. If you were sending via AJAX (where you could conceivably use JSON), then you should also be returning JSON or maybe PartialView, but not View or a redirect.

Lastly, you need to include the request verification token, which should be another key in the post body name __RequestVerificationToken. To get the value to send, you'll need to load the GET version of the view, first, and inspect the source. There will be a hidden input with the value.

like image 126
Chris Pratt Avatar answered Sep 19 '22 18:09

Chris Pratt