ASP.NET MVC – Multiple buttons in the same form

I keep seeing this question in forums and on twitter so I thought I’d post all the various ways you can handle this and what the pros and cons are.

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.

Option 1 – Each button submits the form but provides a different value

~/Views/Account/Register.aspx

  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:                 <button name="button" value="register">Register</button>
 26:                 <button name="button" value="cancel">Cancel</button>
 27:             </p>
 28:         </fieldset>
 29:     </div>
 30:  <% } %>

~/Controllers/AccountController.cs

  1: [AcceptVerbs(HttpVerbs.Post)]
  2: public ActionResult Register(string button, string userName, string email, string password, string confirmPassword)
  3: {
  4:     if (button == "cancel")
  5:         return RedirectToAction("Index", "Home");
  6: 
  7:     ViewData["PasswordLength"] = MembershipService.MinPasswordLength;
  8: 
  9:     if (ValidateRegistration(userName, email, password, confirmPassword))
 10:     {
 11:         // Attempt to register the user
 12:         MembershipCreateStatus createStatus = MembershipService.CreateUser(userName, password, email);
 13: 
 14:         if (createStatus == MembershipCreateStatus.Success)
 15:         {
 16:             FormsAuth.SignIn(userName, false /* createPersistentCookie */);
 17:             return RedirectToAction("Index", "Home");
 18:         }
 19:         else
 20:         {
 21:             ModelState.AddModelError("_FORM", ErrorCodeToString(createStatus));
 22:         }
 23:     }
 24: 
 25:     // If we got this far, something failed, redisplay form
 26:     return View();
 27: }

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:

  1: public class AcceptParameterAttribute : ActionMethodSelectorAttribute
  2: {
  3:     public string Name { get; set; }
  4:     public string Value { get; set; }
  5: 
  6:     public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
  7:     {
  8:         var req = controllerContext.RequestContext.HttpContext.Request;
  9:         return req.Form[this.Name] == this.Value;
 10:     }
 11: }
 12: 

Now I can split into two action methods like this:

  1: [ActionName("Register")]
  2: [AcceptVerbs(HttpVerbs.Post)]
  3: [AcceptParameter(Name="button", Value="cancel")]
  4: public ActionResult Register_Cancel()
  5: {
  6:     return RedirectToAction("Index", "Home");
  7: }
  8: 
  9: [AcceptVerbs(HttpVerbs.Post)]
 10: [AcceptParameter(Name="button", Value="register")]
 11: public ActionResult Register(string userName, string email, string password, string confirmPassword)
 12: {
 13:   // process registration
 14: }

Again, this isn’t the most efficient method but it does let you handle different buttons with different controller methods.

Option 2 – 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.

Option 3: All client side script

  1: <p>
  2:     <button name="button">Register</button>
  3:     <button name="button" type="button" onclick="document.location.href=$('#cancelUrl').attr('href')">Cancel</button>
  4:     <a id="cancelUrl" href="<%= Html.AttributeEncode(Url.Action("Index", "Home")) %>" style="display:none;"></a>
  5: </p>
  6: 

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. I’ve noticed several sites that have buttons for actions that submit data and links for actions like cancel that do not. I bet it has to do with this same sort of problem.

Conclusion

When I write MVC applications I use this criteria for choosing what a button should do:

  • Does the button apply to the form that it is sitting in? e.g. “Save”, “Update”, “Ok”, “Submit”
    Use a standard <button> element.
  • Does the button need to post some other data to the server? e.g. “Delete”
    Use a  <button> element with type=”button” and and onclick handler that submits another form with hidden input elements. more on this in a follow-up blog post
  • Is the button purely navigational? e.g. “Cancel”, “Back”, “Next”, “Prev”
    Use and <a> element or a <button> with script to simply navigate the browser to the new location
Published Sunday, May 31, 2009 8:04 AM by findleyd
Filed under: ,

Comments

# ASP.NET MVC – Multiple buttons in the same form - David Findley

Sunday, May 31, 2009 4:38 PM by DotNetShoutout

Thank you for submitting this cool story - Trackback from DotNetShoutout

# re: ASP.NET MVC – Multiple buttons in the same form

Monday, June 01, 2009 12:16 AM by lenocin

I had to do this also lately, and think I'm going to be applying the [AcceptParameter] attribute (a new one too me).

Not really a fan of adding inline JavaScript. I don't know.  Am I wrong to be be against using JavaScript as much as possible (I only use JavaScript for enhancements?)

# ASP.NET MVC Archived Blog Posts, Page 1

Monday, June 01, 2009 1:51 AM by ASP.NET MVC Archived Blog Posts, Page 1

Pingback from  ASP.NET MVC Archived Blog Posts, Page 1

# re: ASP.NET MVC – Multiple buttons in the same form

Monday, June 08, 2009 12:11 AM by Danny

Just what the doctor ordered !

Are you the same person teaching the 10 part MVC series

for Bob T. ?

Thanks

# re: ASP.NET MVC – Multiple buttons in the same form

Monday, June 08, 2009 8:40 AM by findleyd

Yep thats me. Hope you're enjoying the series.

# re: ASP.NET MVC – Multiple buttons in the same form

Monday, June 08, 2009 5:15 PM by yazılım

Really cool to learn about ActionMethodSelectorAttribute. Thanks for sharing

# re: ASP.NET MVC – Multiple buttons in the same form

Sunday, June 14, 2009 1:43 AM by Boris

The 1st solution was awesome!

Thanks for sharing!

# re: ASP.NET MVC – Multiple buttons in the same form

Monday, June 22, 2009 12:53 PM by John H. Goode

Why not use jQuery, like so:

   $(document).ready(function() {

       $("#btnCancel").click(function() {

           window.location = editUrl;

       });

   });

No need for hyperlink hack!

# re: ASP.NET MVC – Multiple buttons in the same form

Wednesday, August 19, 2009 12:17 PM by Tanrikut

Just what I am looking for. First solution is great for for no javascript situations.

# re: ASP.NET MVC – Multiple buttons in the same form

Wednesday, September 09, 2009 4:17 AM by Markus

Great article, David. However, option 1 does not seem to work if the button text differs from the value:

HTML:

<button type="submit" name="submitButton" value="next" class="btn_red_high"><%=Server.HtmlEncode(Resources.LocalizedResources.BUTTON_NEXT)%></button>

The text for the "next" button is language dependent. Option 1 always returns the text to the controller and not the value, i.e. the variable submitButton has the value of the expression <%=Server.HtmlEncode(Resources.LocalizedResources.BUTTON_NEXT)%> and not the value of the button element. Does anybody else encounter the same behaviour?

# re: ASP.NET MVC – Multiple buttons in the same form

Wednesday, September 09, 2009 5:10 AM by Markus

Addendum to my previous post. The multiple buttons value issue in multilingual pages is now resolved. The key is to give each button an unique name. Conforming to HTTP standards, only the clicked button value is posted to the controller. The controller checks for null (vb=nothing) against each submit button and executes the relevant code:

Page:

<button type="submit" name="addButton" value="add">Button Text or Image</button>

<button type="submit" name="saveButton" value="save">Button Text or Image</button>

<button type="submit" name="nextButton" value="next">Button Text or Image</button>

Controller:

   <AcceptVerbs(HttpVerbs.Post)> _

   Function MyAction(ByVal model As MyModel, ByVal addButton As String, ByVal saveButton As String, ByVal nextButton As String) As ActionResult

           If Not IsNothing(nextButton) Then

               'next code

           ElseIf Not IsNothing(addButton) Then

               'add code

           ElseIf Not IsNothing(saveButton) Then

               'save code

           Else

               'Else Code

           End If

.

.

.

End Function

I hope this helps anyone having the same issue.

# re: ASP.NET MVC – Multiple buttons in the same form

Monday, October 12, 2009 11:32 AM by Simon

This works great unless you are validating the form using jQuery. As a result I just get a null value for the button.

# re: ASP.NET MVC – Multiple buttons in the same form

Saturday, October 24, 2009 1:50 AM by sureshkumar

Thank you very much..

Leave a Comment

(required) 
(required) 
(optional)
(required)