Как исправить ошибку 400 Bad Request в операции .NET Core POST - PullRequest
0 голосов
/ 17 апреля 2019

У меня есть .Net Core 2.1 API, который отправляет данные, используя EF core.Когда я делаю POST-запрос от Почтальона к http://localhost:3642/task/create, я получаю 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; }
}

Это моя полезная нагрузка в Почтальоне:

{
    "Assignee": "Ed tshuma",
    "Summary": "Finish reconciliations",
    "Description": "collate all the pending data"
}

Ответы [ 3 ]

1 голос
/ 17 апреля 2019

Здесь есть ряд проблем.Прежде всего, почему вы сохраняете модель представления в базу данных.Это на самом деле сущность в данном случае, а не модель представления.Вам определенно следует использовать модель представления, но у вас также должен быть отдельный класс сущностей.Тогда ваша модель представления должна содержать только те свойства, которые вы хотите разрешить пользователю редактировать, полностью исключая необходимость в атрибуте 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; }
}

Также обратите внимание на разделение ответственности.У сущности есть только то, что имеет значение для базы данных ([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 на него не будет работать.Чтобы проверить это в Почтальоне, вы должны отправить запрос как x-www-form-urlencoded.Если вы этого не сделаете, то ваша модель по существу всегда будет недействительной, потому что ничто не будет привязано к вашей модели из тела сообщения.

Чтобы получить JSON, ваш параметр должен иметь атрибут FromBodyприменяется к нему ([FromBody]TaskViewModel model).Однако, если вы это сделаете, вы больше не сможете получать сообщения в традиционной форме, и в этом контексте это будет отправлено.Если вы отправляете через AJAX (где вы можете использовать JSON), то вы также должны вернуть JSON или PartialView, но не View или перенаправление.

Наконец, вам нужно включить токен подтверждения запроса, который должен быть еще одним ключом в имени тела сообщения __RequestVerificationToken.Чтобы получить значение для отправки, вам сначала нужно загрузить GET-версию представления и проверить источник.Там будет скрытый ввод со значением.

0 голосов
/ 19 апреля 2019

Крис Пратт прав, вам нужно отправить __RequestVerificationToken.

Если вы закомментируете атрибут [ValidateAntiForgeryToken], похоже, что вы отправляете данные из Body-raw-JSON, тогда вам нужно использовать [FromBody] для доступа к данным.

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

enter image description here

Если вы не хотите добавлять [FromBody], вы можете отправлять данные, используя form-data

enter image description here

0 голосов
/ 17 апреля 2019

Вы должны отправить токен защиты от подделки с вашим запросом, если вы хотите использовать декоратор [ValidateAntiForgeryToken]. См. эту ссылку для получения дополнительной информации.

Кроме того, даже если ваша модель недействительна, вы return View(). Это означает, что вы получаете статус http 200, даже если отправляете неверные данные.

Установите точку останова на if(ModelState.IsValid) и проверьте, вводите ли вы ее. Если нет, проверьте формат вашей полезной нагрузки.

Надеюсь, это поможет.

РЕДАКТИРОВАТЬ относительно вашей полезной нагрузки и вашей модели: вам необходимо предоставить Id полезную нагрузку из-за декоратора [Required] в TaskViewModel. Или вам нужно избавиться от атрибута [Required] на Id. Если вы этого не сделаете, if (ModelState.IsValid) всегда будет ложным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...