I need multiple submit buttons to perform different actions in the controller.
I saw an elegant solution here: How do you handle multiple submit buttons in ASP.NET MVC Framework?
With this solution, action methods can be decorated with a custom attribute. When the routes are processed a method of this custom attribute checks if the attribute's property matches the name of the clicked submit button.
But in MVC Core (RC2 nightly build) I have not found ActionNameSelectorAttribute (I also searched the Github repository). I found a similar solution which uses ActionMethodSelectorAttribute (http://www.dotnetcurry.com/aspnet-mvc/724/handle-multiple-submit-buttons-aspnet-mvc-action-methods).
ActionMethodSelectorAttribute is available but the method IsValidForRequest has a different signature. There is a parameter of type RouteContext. But I could not find the post data there. So I have nothing to compare with my custom attribute property.
Is there a similar elegant solution available in MVC Core like the ones in previous MVC versions?
You can use the HTML5 formaction attribute for this, instead of routing it server-side.
<form action="" method="post">
<input type="submit" value="Option 1" formaction="DoWorkOne" />
<input type="submit" value="Option 2" formaction="DoWorkTwo"/>
</form>
Then simply have controller actions like this:
[HttpPost]
public IActionResult DoWorkOne(TheModel model) { ... }
[HttpPost]
public IActionResult DoWorkTwo(TheModel model) { ... }
A good polyfill for older browsers can be found here.
Keep in mind that...
The first submit button will always be chosen when the user presses the carriage return.
If an error - ModelState or otherwise - occurs on the action that was posted too, it will need to send the user back to the correct view. (This is not an issue if you are posting through AJAX, though.)
ASP.NET Core 1.1.0 has the FormActionTagHelper that creates a formaction attribute.
<form>
<button asp-action="Login" asp-controller="Account">log in</button>
<button asp-action="Register" asp-controller="Account">sign up</button>
</form>
That renders like this:
<button formaction="/Account/Login">log in</button>
<button formaction="/Account/Register">sign up</button>
It also works with input tags that are type="image" or type="submit".
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.
An even better answer is to use jQuery Unobtrusive AJAX and forget about all the mess.
You can give your controller actions semantic names.
You don't have to redirect or use tempdata.
You don't have the post action name in the URL on server side validation errors.
On server side validation errors, you can return a form or simply the error message.
Related
I am trying to create a second submit button on my screen that will take the value of the model on the front end to delete this user's data. The problem is I don't know how I can have buttons on the same screen point to different functions and then still have the model's data. is there a way I can have two buttons that point to different functions that both pass the same model? if so please let me know how. P.S I'm new to ASP.NET core
My front end code looks like this
#if ((currentUser.IsInRole("SuperAdmin")))
{
*** this button doesnt pass the model***
<input type="button" class="btn-glow primary" value="#_stringLocalizer["DELETE"]" />
}
*** this button passes the model***
<input type="submit" class="btn-glow primary" value="#_stringLocalizer["AccountEditViewSubmitButtonDisplayName"]" id="submit" />
The back end looks like this
[Authorize(Roles = "SuperAdmin")]
public async Task<IActionResult> DeleteUser(frontEndModel model)***the model is Null
{
frontEndModel.user = null;
savechanges();
}
[Authorize(Roles = "Admin,GroupAdmin,SuperAdmin")]
[HttpPost]
public async Task<IActionResult> Edit(frontEndModel model)
{
***uses frontEndModel
}
Working in a MVC project using C# .Net.
I have a top menu where I have links to different views.
I'm trying to get my button click to take me to one of my views;
<input type="button" value="About Me" onclick="location.href='<%= #Url.Action("About Me", "AboutMePage") %>'"/>
But I'm hit with this error:
A potentially dangerous Request.Path value was detected from the client (<).
Not sure where I'm going wrong?
Cheers.
Try to add a Controller to Post your action.
<input type="submit" value="About Me"/>
After your controller
[HttpPost]
public ActionResult Index()
{
return RedirectToAction("About Me", "AboutMePage");
}
I have two buttons on my cshtml page - Agree and Disagree - how can I easily pass back there values to my Controller so I can make a decision to either take them to Homepage or Log them back out.
So on my cshtml page I have...
<input type="button" name='Agree' value="I Agree"
onclick="location.href='#Url.Action("DoAccept", "Home")'" />
<input type="button" name='Delete' value="I Disagree"
onclick="location.href='#Url.Action("DoAccept", "Home")'" />
So In my Home Controller then I have a DoAccept method as below:
public ActionResult DoAcceptTC()
{
//if( buttom clicked was Agree)
return RedirectToAction(VIEW_HOMEPAGE);
//else
//return Logout page..
}
So My question is how can I easily get a value back to the controller method?
There's no much point in using inputs without html form hosting them. You could use form, or simple links
//view
#using(Html.BeginForm("DoAccept"))
{
<button name="decision" value="agree">I Agree</button>
<button name="decision" value="disagree">I disagree</button>
}
//controller
public ActionResult DoAcceptTC(string decision)
{
//decision == "agree"
}
You must send paramaters along with Url.Action to the controller so that you can be able to perform your actions easily. Below link may help you.
Url.Action with params
I have an action as follows:
public ActionResult ChangeFeeCheck(string id)
{
ViewBag.id = id;
return View();
}
on my View, I have the following:
#{
ViewBag.Title = "CreateList";
}
Please enter first name <br /><br />
#using (Html.BeginForm())
{
#Html.Textbox("firstname")
<input type="button" id="SaveChanges" value="Save" />
}
When I click on the button, I was expecting it to to the following
[HttpPost]
public ActionResult ChangeFeeCheck(string firstname)
{
.....
}
I am not sure when MVC will automatically go to the HttpPost or if I when need to manually have it there. In the above, it does not go there directly. I have to use the
window.location.href
and pass the url of the controller/action.
Meaning, isn't the default for
Html.BeginForm()
The HttpPost(same name as the HttpGet)
You need the button to be a submit button:
Change:
<input type="button" id="SaveChanges" value="Save" />
^^^^^^
To:
<input type="submit" id="SaveChanges" value="Save" />
^^^^^^
If you are following "Convention over configuration" rule over here, then the view you have created here must be for ChangeFeeCheck action, and ChangeFeeCheck here looks like will make compiler confused as no overload, same name, same signatures.
and then when method for form is get it would go to the first one, while if method for form is POST, it will call the one decorated with [HttpPost]
And because you are using submit button and by default HTML form generated uses POST action, it call the [HttpPost]
You can refer this article (from the internet archive as original link is now down): https://web.archive.org/web/20120527133344/http://microsoftmentalist.com:80/2011/09/07/asp-net-mvc-difference-between-httpget-and-httppost-with-example/
See for example how GET and POST action methods are overloaded.
First of all same signature same name method can't be compiled in same controller it will give you the compilation error already have member of same parameter type.
You have to differentiate these two similar name method by different signature.
Regarding HttpPost and HttpGet your get method will be called whenever you have to retrive data or your page load is called for that view.
HttpPost method will be called either you are using button of type submit or your input type is button but using jquery you are giving ajax call on button click and your ajax type is "Post"
$.ajax({
url: "Action"
type: "Post"
},succees: function(){alert('succeed');});
Strait to the details...
I'm working on a personal project basically it's a task list. Anyways; I managed to get the standard Add, Edit, Delete a task functionality going good; now I'm stuck on something that I know must be very simple. I would like for users to be able to accept a task from the details page, now I could easily put that option in a drop down list and allow the user to select it and then save; BUT i would like to just provide a link that they can click "Accept Task" that link then goes to my controller action and pulls the task then updates the TaskStatus field.
This is my controller action
//
// TaskStatus Updates
[AcceptVerbs(HttpVerbs.Post), Authorize]
public ActionResult AcceptTask(int id)
{
Task task = taskRepository.GetTask(id);
try
{
task.TaskStatus = "Accepted";
taskRepository.Save();
return RedirectToAction("Details", new { id = task.TaskId });
}
catch (Exception)
{
throw;
}
}
So now how do I call this action from within my "Details" view?
A small form containing a button that posts back to the AcceptTask action would work. It could even be an AJAX form, if you wanted.
<% using (Html.BeginForm("accepttask","task",new { id = Model.TaskId })) { %>
<input type="submit" value="Accept Task" />
<% } %>
A link (<a>) cannot post a form, so you have three choices:
Use a button instead;
Use an <input type="image"> instead, which you can finesse to look similar to a link;
Use JavaScript to do it.
There's already an answer posted for #1, which is easily modified to #2. I'll post a jQuery example for #3, although you can use any JS library for this:
<a id="acceptTaskLink" href="#">Accept</a>
<span id="accepted" style="display: none">Accepted</span>
...
<script type="text/javascript">
$('#acceptTaskLink').click(function() {
$.post('/site/task/accept',
function(result) {
$('#acceptTaskLink').hide();
$('#accepted').show();
});
});
</script>
This script makes an Ajax post to the controller, then after it successfully posts, it changes the "Accept" link into regular "Accepted" text (by hiding the link and showing a confirmation element, which is presumably in the same place).
A link should not perform any action by definition. It leads to strange problems: What happens when google crawls your site? What happens when somebody refreshes the page? What happens when somebody bookmarks the page?
Actions that change content are usuallly done by get after post. First post to the Action that changes something (AcceptTask) then redirect to a page that displays the result. In you case I would suggest for the get the list of tasks with a success message. For the Mesage to be available after redirect use Tempdata.