I have done this before and in the past I would have posted the form to different controller actions. The problem is, on a server side validation error you are either stuck with:
return View(vm)
leaves the post action name in the url… yuck.
return Redirect(...)
requires using TempData
to save the ModelState
. Also yuck.
Here is what I chose to do.
- Use the
name
of the button to bind to a variable on POST.
- The button
value
is an enum to distinguish the submit actions. Enum is type safe and works better in a switch statement. ;)
- POST to the same action name as the GET. That way you don't get the POST action name in your URL on a server side validation error.
- If there is a validation error, rebuild your view model and
return View(viewModel)
, following the proper PGR pattern.
Using this technique, there is no need to use TempData
!
In my use case, I have a User/Details page with an "Add Role" and "Remove Role" action.
Here are the buttons. They can be button instead of input tags... ;)
<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>
Here is the controller action. I refactored out the switch code blocks to their own functions to make them easier to read. I have to post to 2 different view models, so one will not be populated, but the model binder does not care!
[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());
}
As an alternative, you could post the form using AJAX.