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

38 Comments

  • 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?)

  • Just what the doctor ordered !
    Are you the same person teaching the 10 part MVC series
    for Bob T. ?

    Thanks

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

  • Really cool to learn about ActionMethodSelectorAttribute. Thanks for sharing

  • The 1st solution was awesome!
    Thanks for sharing!

  • Why not use jQuery, like so:

    $(document).ready(function() {
    $("#btnCancel").click(function() {
    window.location = editUrl;
    });
    });
    No need for hyperlink hack!

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

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

    HTML:



    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 and not the value of the button element. Does anybody else encounter the same behaviour?


  • 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 Text or Image
    Button Text or Image
    Button Text or Image
    Controller:
    _
    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.

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

  • Thank you very much..

  • Thanks! I changed attribute to work with localised buttons:

    Register
    Cancel
    ...
    public class AcceptParameterAttribute : ActionMethodSelectorAttribute
    {
    public string Name { get; set; }
    public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
    {
    var req = controllerContext.RequestContext.HttpContext.Request;
    return req.Form[this.Name] != null;
    }
    }
    ...

    Then you can use it:

    [AcceptVerbs(HttpVerbs.Post)]
    [AcceptParameter(Name = "register")]
    public ActionResult Register(string userName)
    ...
    [AcceptVerbs(HttpVerbs.Post)]
    [AcceptParameter(Name = "cancel")]
    public ActionResult Register(string userName)
    ...

  • I thing the ActionMethodSelectorAttribute usage is the best way to handle multiple submit requests from same page. I my case I need to "Save" temporarly and I need to "Publish" which result in two different actions executed ... .
    thanks for sharing ... you saved the day !

  • Thanks. Its helped me.

  • Thanks for the article...

    the 1st solution was exactly the one i was looking for.

    thank you

  • Thanks!

    Great post...short and sweet

    Thanks once again!!!

  • Thanks... The option 3 works pretty well...

  • Excellent separation of concerns in the first approach, really helped us tidy up some messy actions (we have to support js free browsers). Thanks!

  • I like option 2 best, but I can't find a way to write unit tests that set ActionMethodSelectorAttribute before calling the controller action. So I went for option 1, as it's easy to write tests that just pass a string as the first parameter for the controller (second parameter being the model).

  • The first option does not work under IE7..Anyone else has the same problem...

  • Option 3 works very good! Thanks for this solution. I've been looking for a working solution since hours and finally came up to something useful with this article.
    I use it with:

    <a id="continue" href="" style="display:none;">

  • What a HELL MVC !! maan, it is just going back to the stoneage. I love MVC pattern, but not the ASP.NET MVC framework. I implement MVC pattern in my Web Form application in my own way and it is possible. MVC vouce for separation of concern, but forcing me to write code in the VIEW page !! What a ridiculous point of view !! !! I love WEB Forms and my Heart will go on for Web Form all the time.

  • I am trying to use ActionMethodSelectorAttribute ..but wen i implement,i gives an error on Method info parameter.It says The type or namespace name 'MethodInfo' could not be found.
    Can any one help please

  • using System.Reflection
    It'll help. I guess google was down when you came to the problem;)

  • Option 3 is awful. Ever heard of unobtrusive javascript?

  • I have to do is close to the process.
    Thanks

  • Nice post...The third solution works for me. However i have an added requirement. When the user hits Cancel button, a confirm message with Yes/No buttons needs to be shown. If User selects Yes, user will be redirected to another page and if user selects no, should stay on the same page.
    Can you please let me know how to accomplish that?

  • Do something like option #2 but call a js function that shows a prompt and depending on the user response submits the form or navigates to a new page.

  • It's possible to always use only links. This is very simple approach, only jQuery must be referenced (actually easy to rewrite this to not use jQuery and/or move this onclick script to script file):

    @Html.ActionLink("Save", "SaveActionName", null, null, new { onclick = "$(this).parents('form').attr('action', $(this).attr('href'));$(this).parents('form').submit();return false;" })

  • Awesome article!
    Very useful for people who come from ASP.NET

  • I'm think the solution was not sutiate with multiple languange. the button value will be difference for each language

    I'm think we should check the button name value in request. if the "request[buttonname]!=null", it should be clicked by the button

  • Worked perfectly! Thanks for sharing. You saved my day.

  • Steven Bey, i like your solution. you could modify it to allow the attribute to accept the name of the button (or buttons) that the action should react to, rather than having it take a prefix. This would eliminate the need for a prefix.

  • Are you talking about the action method prefix (e.g. Register_Cancel)? Technically I think you can leave the _Cancel off of the name. I just did that for clarity.

  • Awesome. Just what I was looking for and it works great just don't name the ActionResults the same as the Get method and you're laughing.

  • Greate article, Really very helpful

  • great solution but I have 2 edit methods, one for post and 1 for get. I use data annotations for validation. Everything works first time but if validation fails, the GET method is called next time I press one of the buttons.

  • Like the solution but have an issue with IE8 when capatibility mode is turned on. Would anyone know why it is an issue?

Comments have been disabled for this content.