Привязка модели не работает с маршрутизацией атрибутов в.net Core 2.1

Я могу заставить привязку модели работать нормально без маршрутизации атрибута - например:

/// <summary>
/// Collect user details
/// </summary>
public IActionResult RegisterDetails(Guid CustomerId)
{
    var Details = new Details()
    {
        CustomerID = CustomerId
    };

    return View(Details);
}

/// <summary>
/// Save user details in Db if valid
/// </summary>
[HttpPost]
public IActionResult RegisterDetails(Details Details)
{
    if (ModelState.IsValid)
    {
        // Do stuff
    }

    // Error, return ViewModel to view
    return View(RegisterDetails);
}

Но я не уверен, как передать модель методу, который ее обрабатывает. Когда я отправляю форму, он запускает оригинальный метод, а не тот, который находится под [HttpPost] - он продолжает публиковать в исходном методе снова и снова (где // Делать вещи - когда я делаю это:

/// <summary>
/// Collect user details
/// </summary>
[Route("Register/Details/{CustomerId}")]
public IActionResult RegisterDetails(Guid CustomerId)
{
    var Details = new Details()
    {
        CustomerID = CustomerId
    };

    return View(Details);
}

/// <summary>
/// Save user details in Db if valid
/// </summary>
[HttpPost]
[Route("Register/Details")]
public IActionResult RegisterDetails(Details Details)
{
    if (ModelState.IsValid)
    {
        // Do stuff
    }

    // Error, return ViewModel to view
    return View(RegisterDetails);
}

Как правильно связать модель при использовании атрибутной маршрутизации?

Я искал в Google - я нашел вещи, которые не помогли, например, это: https://www.red-gate.com/simple-talk/dotnet/asp-net/improved-model-binding-asp-net-core/

Спасибо

Обновить

Я также заметил, что CustomerId добавляется к URL-адресу даже после публикации формы. Я не думаю, что это произошло в MVC 5 и не требует этого, CustomerId скрыт на странице.

Как я могу удалить это (это приводит к тому, что маршрут не соответствует оформленному методу [HttpPost].

3 ответа

Решение

Как правило, вы можете добавить атрибут "маршрут" через контроллер класса API в качестве базового пути, а затем добавить параметры. Я также пытался всегда добавлять глагол для контроллера для лучшего понимания...

[Route("Register/Details/")]
public class RegistrerExampleController : ControllerBase
{

    /// <summary>
    /// Collect user details
    /// </summary>
    [HttpGet("{CustomerId}", Name = nameof(RegisterDetails))]
    public IActionResult RegisterDetails(Guid CustomerId)
    {
        var Details = new Details()
        {
            CustomerID = CustomerId
        };

        return View(Details);
    }

    /// <summary>
    /// Save user details in Db if valid
    /// </summary>
    [HttpPost]
    public IActionResult RegisterDetails(Details Details)
    {
        if (ModelState.IsValid)
        {
            // Do stuff
        }

        // Error, return ViewModel to view
        return View(RegisterDetails);
    }
}

Вы пробовали это?

Вопрос в два раза. Во-первых, у вас есть RegisterDetails(Guid CustomerId) действие с маршрутом, установленным через Route приписывать. Это открывает его для принятия всех HTTP-глаголов: GET, POST и т. Д. Во-вторых, хотя вы не опубликовали свое представление, я могу предположить, что у вас, вероятно, есть форма с пустым действием, т.е.

 <form method="post">
     ...
 </form>

По умолчанию будет отправляться обратно на ту же страницу, что здесь в этом сценарии /Register/Details/{CustomerId}, Поскольку этот маршрут не соответствует вашим действиям, явно помеченным как HttpPost и действие, которое оно соответствует, принимает POST (из-за использования Route), действие, которое принимает идентификатор клиента, вызывается снова. Поскольку в нем нет кода для обработки сообщения, он просто перезагружает вашу страницу.

Прежде всего, вы должны использовать явные атрибуты глагола HTTP, а не Route на ваши действия, а именно:

[HttpGet("Register/Details/{CustomerId}")]
public IActionResult RegisterDetails(Guid CustomerId)

[HttpPost("Register/Details")]
public IActionResult RegisterDetails(Details Details)

Затем, если вы хотите сохранить эту схему маршрутов, вам нужно четко указать форму. Однако, поскольку оба действия имеют одно и то же имя, это немного сложно сделать. ASP.NET Core попытается выполнить обратную засыпку части CustomerId маршрута, поскольку она уже существует в данных маршрута. У вас есть три варианта:

  1. Используйте разные имена действий. Затем вы можете явно привязать свою форму к действию публикации:

    [HttpPost("Register/Details")]
    public IActionResult RegisterDetailsPost(Details Details)
    

    Затем:

    <form asp-action="RegisterDetailsPost" method="post">
    
  2. Используйте названия маршрутов:

    [HttpPost("Register/Details", Name = "RegisterDetailsPost")]
    public IActionResult RegisterDetails(Details Details)
    

    Затем:

    <form asp-route="RegisterDetailsPost" method="post">
    
  3. Не делайте идентификатор клиента частью начального маршрута. Если два маршрута действительно совпадают, то будет выбран правильный путь, основанный на действующем глаголе HTTP. Другими словами:

    [HttpGet("Register/Details")]
    public IActionResult RegisterDetails(Guid CustomerId)
    

    Вы все еще можете указать идентификатор клиента, но вам нужно использовать строку запроса, т.е. /Register/Details?CustomerId=123456, Генерация URL в ваших ссылках и тому подобное не будет отличаться. ASP.NET Core автоматически присоединит любые включенные данные маршрута, которые явно не являются параметром маршрута, в качестве параметра строки запроса. Другими словами, вы все еще можете сделать что-то вроде:

    <a asp-action="RegisterDetails" asp-route-customerId="123456">Click me</a>
    

Попробуйте использовать атрибут FromBody.

public IActionResult RegisterDetails([FromBody]Details Details)