ASP.NET MVC Core/6: несколько кнопок отправки

Мне нужно несколько кнопок отправки для выполнения различных действий в контроллере.

Я увидел элегантное решение здесь: Как вы обрабатываете несколько кнопок отправки в ASP.NET MVC Framework? С этим решением методы действия могут быть украшены пользовательским атрибутом. Когда маршруты обрабатываются, метод этого пользовательского атрибута проверяет, соответствует ли свойство атрибута названию нажатой кнопки отправки.

Но в MVC Core (ночная сборка RC2) я не нашел ActionNameSelectorAttribute (я также искал репозиторий Github). Я нашел похожее решение, которое использует ActionMethodSelectorAttribute (http://www.dotnetcurry.com/aspnet-mvc/724/handle-multiple-submit-buttons-aspnet-mvc-action-methods).

ActionMethodSelectorAttribute доступен, но метод IsValidForRequest имеет другую подпись. Есть параметр типа RouteContext. Но я не смог найти данные поста там. Так что мне нечего сравнивать с моим свойством атрибутов.

Есть ли в MVC Core такое же элегантное решение, как в предыдущих версиях MVC?

Ответ 1

Для этого можно использовать атрибут HTML5 formaction, а не маршрутизировать его на стороне сервера.

<form action="" method="post">
    <input type="submit" value="Option 1" formaction="DoWorkOne" />
    <input type="submit" value="Option 2" formaction="DoWorkTwo"/>
</form>

Затем просто выполните действия контроллера, подобные этому:

[HttpPost]
public IActionResult DoWorkOne(TheModel model) { ... }

[HttpPost]
public IActionResult DoWorkTwo(TheModel model) { ... }

Хороший polyfill для старых браузеров может быть найден здесь.

Имейте в виду, что...

  • Первая кнопка отправки всегда будет выбрана, когда пользователь нажимает на возврат каретки.
  • Если ошибка над ошибкой - ModelState или иначе - произошла и в том случае, если она была отправлена, она должна будет отправить пользователя обратно в правильное представление. (Это не проблема, если вы отправляете через AJAX.)

Ответ 2

В ASP.NET Core 1.1.0 есть FormActionTagHelper, который создает атрибут formaction.

<form>
    <button asp-action="Login" asp-controller="Account">log in</button>
    <button asp-action="Register" asp-controller="Account">sign up</button>
</form>

Это выглядит следующим образом:

<button formaction="/Account/Login">log in</button>
<button formaction="/Account/Register">sign up</button>

Он также работает с тегами input, которые type="image" или type="submit".

Ответ 3

Еще лучше - использовать ненавязчивый JQuery AJAX и забыть обо всем этом.

  1. Вы можете дать своему контролеру действия семантические имена.
  2. Вам не нужно перенаправлять или использовать временные данные.
  3. У вас нет имени действия публикации в URL-адресе при ошибках проверки на стороне сервера.
  4. В случае ошибок проверки на стороне сервера вы можете вернуть форму или просто сообщение об ошибке.

Ответ 4

Я делал это раньше и в прошлом я бы опубликовал форму для различных действий контроллера. Проблема в ошибке проверки на стороне сервера, с которой вы застряли:

  1. return View(vm) оставляет название пост-действия в URL-адресе… чёрт.
  2. return Redirect(...) требует использования TempData для сохранения ModelState. Также гадость.

Вот что я решил сделать.

  1. Используйте кнопку name для привязки к переменной в POST.
  2. Кнопка value - это перечисление для различения действий отправки. Enum является типобезопасным и работает лучше в выражении switch. ;)
  3. POST к тому же имени действия, что и GET. Таким образом, вы не получите имя действия POST в своем URL-адресе при ошибке проверки на стороне сервера.
  4. В случае ошибки проверки перестройте модель представления и return View(viewModel), следуя правильному шаблону PGR.

Используя эту технику, нет необходимости использовать TempData!

В моем случае у меня есть страница User/Details с действиями "Добавить роль" и "Удалить роль".

Вот кнопки. Они могут быть кнопками вместо тегов ввода...;)

<button type="submit" class="btn btn-primary" name="SubmitAction" value="@UserDetailsSubmitAction.RemoveRole">Remove Role</button>
<button type="submit" class="btn btn-primary" name="SubmitAction" value="@UserDetailsSubmitAction.AddRole">Add Users to Role</button>

Вот действие контроллера. Я переделал блоки кода переключателя на их собственные функции, чтобы их было легче читать. Я должен опубликовать в двух разных моделях представления, чтобы одна из них не была заполнена, но переплет модели не имеет значения!

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Details(
    SelectedUserRoleViewModel removeRoleViewModel, 
    SelectedRoleViewModel addRoleViewModel,
    UserDetailsSubmitAction submitAction)
{
    switch (submitAction)
    {
        case UserDetailsSubmitAction.AddRole:
        {
            return await AddRole(addRoleViewModel);
        }
        case UserDetailsSubmitAction.RemoveRole:
        {
            return await RemoveRole(removeRoleViewModel);
        }
        default:
            throw new ArgumentOutOfRangeException(nameof(submitAction), submitAction, null);
    }
}

private async Task<IActionResult> RemoveRole(SelectedUserRoleViewModel removeRoleViewModel)
{
    if (!ModelState.IsValid)
    {
        var viewModel = await _userService.GetDetailsViewModel(removeRoleViewModel.UserId);
        return View(viewModel);
    }

    await _userRoleService.Remove(removeRoleViewModel.SelectedUserRoleId);

    return Redirect(Request.Headers["Referer"].ToString());
}

private async Task<IActionResult> AddRole(SelectedRoleViewModel addRoleViewModel)
{
    if (!ModelState.IsValid)
    {
        var viewModel = await _userService.GetDetailsViewModel(addRoleViewModel.UserId);
        return View(viewModel);
    }

    await _userRoleService.Add(addRoleViewModel);

    return Redirect(Request.Headers["Referer"].ToString());
}

В качестве альтернативы вы можете опубликовать форму, используя AJAX.