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.



1 comments:

  1. bad decision, look at
    public 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
    {}

    ReplyDelete

 
Top