This is one of the very general problem developers face when they start working over MVC. How to call multiple actions or how to handle multiple button clicks on a form in MVC.
The Scenario
Imagine you have a user signup form.
There are several textbox fields for entering the new account information and
then two buttons: Signup and Cancel. Signup will process the account
information and Cancel will return the user to the home page. In this case we
have more than one button on the same form. There my be some other cases as
well. Here I am presenting a very simple example to handle such situation.
Solution 1 – Each button submits
the form but provides a different value
Imagine we have a form and a student
model class as follows:
public class Student
{
[Required]
public string
StudentID { get; set;
}
public string
StudentName { get; set;
}
}
On our view we have two fields – one for student id and other for Student Name.
And we have two buttons on this page. Let the name
of first button is ‘Submit 1’ and
that of second is ‘Submit 2’.
So first we develop the view for this scenario as
follows:
<% using
(Html.BeginForm("MultipleSubmitButtonsInForm",
" MultipleSubmitButtonsInFormController",
FormMethod.Post))
{ %>
<%: Html.AntiForgeryToken() %>
<%: Html.ValidationSummary(true)
%>
<fieldset>
<legend>Log in Form</legend>
<ol>
<li>
<%: Html.LabelFor(m => m.StudentID) %>
<%: Html.TextBoxFor(m => m.StudentID) %>
<%: Html.ValidationMessageFor(m => m.StudentID) %>
</li>
<li>
<%: Html.LabelFor(m => m.StudentName) %>
<%: Html.PasswordFor(m => m.StudentName)%>
<%: Html.ValidationMessageFor(m => m.StudentName)%>
</li>
</ol>
<input type="submit"
name="submitButton"
value="Submit
1" />
<input type="submit"
name="submitButton"
value="Submit
2" />
</fieldset>
<% } %>
This view will look like this :
Here we can see that we have mentioned the same
name of two submit buttons.
In the line
<% using
(Html.BeginForm("MultipleSubmitButtonsInForm",
" MultipleSubmitButtonsInFormController ",
FormMethod.Post))
We have defined the Action and Controller name. So
in our project we are having a Controller with name ‘MultipleSubmitButtonsInFormController’and
an Action with name MultipleSubmitButtonsInForm in
that controller.
Now what we will do we will distinguish the two
buttons click in the action.
The action body will look like as follows:
[HttpPost]
public ActionResult
a(string submitButton)
{
switch (submitButton)
{
case "Submit
1":
return (Submit_1());
case "Submit
2":
return (Submit_2());
default:
return (View());
}
}
Here we are having two functions which will be called
in accordance with the clicked button.
private ActionResult
Submit_1()
{
// process the cancellation request here.
return (View("MultipleSubmitButtonsInForm_2"));
}
private ActionResult
Submit_2()
{
// perform the actual send operation here.
return (View("MultipleSubmitButtonsInForm_2"));
}
It is clear from the action code that we
distinguish which button is clicked and use switch case to call the required functionality.
Note: The name of two submit buttons and the
parameter name in the action should be same. See it in our example.
Buttons:
<input type="submit"
name="submitButton" value="Submit 1"
/>
<input type="submit"
name="submitButton" value="Submit 2"
/>
Action Signature
public ActionResult
MultipleSubmitButtonsInForm2(string submitButton).
So this is the very general approach to handle the
situation of having more than one action on a form.
The
downside to this solution is that you have to add some yucky conditional logic
to your controller and all the form data has to be submitted to the server just
so the server can issue a redirect. To make the controller code a little better
you could implement a custom ActionMethodSelectorAttribute
like this:
public class AcceptParameterAttribute : ActionMethodSelectorAttribute
{
public string Name { get; set; }
public string Value { get; set; }
public override bool IsValidForRequest(ControllerContext
controllerContext, MethodInfo methodInfo)
{
var req =
controllerContext.RequestContext.HttpContext.Request;
return req.Form[this.Name] == this.Value;
}
}
Now I can
split into two action methods like this:
[ActionName("MultipleSubmitButtonsInForm_Click")]
[AcceptVerbs(HttpVerbs.Post)]
[AcceptParameter(Name="submitButton", Value=" Submit 1")]
public ActionResult Submit1Action()
{
// submit 1 functionality
}
[AcceptVerbs(HttpVerbs.Post)]
[AcceptParameter(Name="SubmitButton", Value=" Submit 2")]
public ActionResult Submit2Action()
{
// submit 2 functionality
}
Solution 2: All client side script
<p>
<button name="button">Register</button>
<button name="button" type="button" onclick="document.location.href=$('#cancelUrl').attr('href')">Cancel</button>
<a id="cancelUrl" href="<%= Html.AttributeEncode(Url.Action("Index", "Home")) %>" style="display:none;"></a>
</p>
This is the most efficient
way to handle the cancel button. There is no interaction with the server to get
the url to redirect to.
I rendered a hidden <a> tag to contain the url
but still used the <button> and some script so that the cancel option
still looked like a button on the form. It would also work if I just displayed
the <a> tag instead of the button.
Solution 3: Option 2 – Using a second
form
1: <% using (Html.BeginForm()) { %>
2: <div>
3: <fieldset>
4: <legend>Account Information</legend>
5: <p>
6: <label for="username">Username:</label>
7: <%= Html.TextBox("username") %>
8: <%= Html.ValidationMessage("username") %>
9: </p>
10: <p>
11: <label for="email">Email:</label>
12: <%= Html.TextBox("email") %>
13: <%= Html.ValidationMessage("email") %>
14: </p>
15: <p>
16: <label for="password">Password:</label>
17: <%= Html.Password("password") %>
18: <%= Html.ValidationMessage("password") %>
19: </p>
20: <p>
21: <label for="confirmPassword">Confirm password:</label>
22: <%= Html.Password("confirmPassword") %>
23: <%= Html.ValidationMessage("confirmPassword") %>
24: </p>
25: <p>
26: <button name="button">Register</button>
27: <button name="button" type="button" onclick="$('#cancelForm').submit()">Cancel</button>
28: </p>
29: </fieldset>
30: </div>
31: <% } %>
32: <% using (Html.BeginForm("Register_Cancel", "Account", FormMethod.Post, new { id="cancelForm" })) {} %>
33:
All I did here was add a new
form after the registration form and point it at my other controller action. I
then changed the cancel button to type=”button” so that it would try to submit
the form it was sitting in and added an onlick that uses a simple jQuery
expression to submit my other “cancel” form. This is more efficient now that it
wont submit all the registration data but it is still not the most efficient
since it is still using the server to do a redirect.
bad decision, look at
ReplyDeletepublic class AcceptButtonAttribute : ActionMethodSelectorAttribute
{
public string ButtonName { get; set; }
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
var req = controllerContext.RequestContext.HttpContext.Request;
return !string.IsNullOrEmpty(req.Form[this.ButtonName]);
}
}
and use two attributes
[ActionName("Users")] // - action medhod
[AcceptButton(ButtonName = "Remove")] // - button which was clicked
public ActionResult Users_Remove(int? removeId) // -doesnt matter what the method name
{}