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 и забыть обо всем этом.
- Вы можете дать своему контролеру действия семантические имена.
- Вам не нужно перенаправлять или использовать временные данные.
- У вас нет имени действия публикации в URL-адресе при ошибках проверки на стороне сервера.
- В случае ошибок проверки на стороне сервера вы можете вернуть форму или просто сообщение об ошибке.
Ответ 4
Я делал это раньше и в прошлом я бы опубликовал форму для различных действий контроллера. Проблема в ошибке проверки на стороне сервера, с которой вы застряли:
return View(vm)
оставляет название пост-действия в URL-адресе… чёрт.
return Redirect(...)
требует использования TempData
для сохранения ModelState
. Также гадость.
Вот что я решил сделать.
- Используйте кнопку
name
для привязки к переменной в POST.
- Кнопка
value
- это перечисление для различения действий отправки. Enum является типобезопасным и работает лучше в выражении switch. ;)
- POST к тому же имени действия, что и GET. Таким образом, вы не получите имя действия POST в своем URL-адресе при ошибке проверки на стороне сервера.
- В случае ошибки проверки перестройте модель представления и
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.