Most people coming to ASP.NET MVC tend to have similar problems. Most of them are related to refreshed understanding of HTML+HTTP limitations. Some are mitigated by ASP.NET MVC (checkboxes), but a lot require custom solutions.

One of the more common problems I get asked about by every second person learning MVC is how to make a form with two independent submit buttons, where both should submit the same data, but a have a different processing logic. For example, form may have a Save Draft button and Publish button.

Until now, I always said that it is possible, Google/StackOverflow and find out. But I just had to build such kind of thing myself, so I finally took some time to find/build the final solution.

So, let’s look at the solutions available online.

StackOverflow question “How do you handle multiple submit buttons in ASP.NET MVC Framework?” is the starting point, but it does not provide a good solution for the situation where you need to send same data for both buttons, except the solution to switch by button name, which is duct-taping.

Post “ASP.NET MVC – Multiple buttons in the same form” by David Findley is much more interesting, since his AcceptParameterAttribute is very similar to my solution. However, this has several (small) shortcomings: first, you have to specify what is actually an action you want to do in an attribute. So even if you name your action “SaveDraft”, you will still need to specify AcceptParameter(Name=“button”, Value=“saveDraft”). Another thing is need to put [ActionName] on your actions, which is understandable, but a bit confusing for people who do not yet know the idea.

So, I wanted to build the solution that would require at most one attribute, and where the name of action method corresponds to the attributes of the button. Also, since <input> value is the thing being shown in the button, and <button> value has issues in IE, I decided to go with name attribute and ignore value completely (which seems to be consistent with David Findley’s commenters).

Now, the solution. Basically, instead of using ActionMethodSelectorAttribute, I am using ActionNameSelectorAttribute, which allows me to pretend the action name is whatever I want it to be. Fortunately, ActionNameSelectorAttribute does not just make me specify action name, instead I can choose whether the current action matches request.

So there is my class (btw I am not too fond of the name):

public class HttpParamActionAttribute : ActionNameSelectorAttribute {
    public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo) {
        if (actionName.Equals(methodInfo.Name, StringComparison.InvariantCultureIgnoreCase))
            return true;

        if (!actionName.Equals("Action", StringComparison.InvariantCultureIgnoreCase))
            return false;
        
        var request = controllerContext.RequestContext.HttpContext.Request;
        return request[methodInfo.Name] != null;
    }
}

How to use it? Just have a form similar to this:

<% using (Html.BeginForm("Action", "Post")) { %>
  <!— …form fields… -->
  <input type="submit" name="saveDraft" value="Save Draft" />
  <input type="submit" name="publish" value="Publish" />
<% } %>

and controller with two methods

public class PostController : Controller {
    [HttpParamAction]
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult SaveDraft() {
        //…
    }

    [HttpParamAction]
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Publish() {
        //…
    }
}

As you see, the attribute does not require you to specify anything at all. Also, name of the buttons are translated directly to the method names. Additionally (I haven’t tried that) these should work as normal actions as well, so you can post to any of them directly.

There is some room for improvements (hardcoded “action” as a default action name), but in general I’m satisfied with this solution.

  • Pingback: 9eFish

  • Diego

    I am rookie in asp and asp mvc.
    But please help me.
    If i left the Html.BeginForm() as default then independent of what button i click the actionName parameter is always the same, Ex: “delete”, so my collection of buttons only behavior in the same way.
    If i left the Html.BeginForm(“Action”, “Post”) as you used in this article then the IsValidName will always return false and no method at all will be executed.
    What am i doing wrong ?
    Thanks for the article anyway.

  • Diego

    i figured out the mistake
    silly thing
    now it works great.
    ty !!!

  • Diego

    I have another problem if i specify the Html.BeginForm(“Action”, “Controller”) my post method is unable to understant the parameters.
    [HttpParamAction]
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Delete(int id)

    The id is null and my application crashes.

    If I use a default begin form Html.BeginForm() the method above just works fine.

    How can I fix this problem ?

  • http://blog.ashmind.com Andrey Shchekin

    Strange, I'll try this a bit later.
    I tried my method with parameters before and it worked.

    However, does it fail if id is not specified in the route?

  • geykel

    Hi, I have the same issue as Diego about the id with null value, did you guys found any solution for that?

    Thanks

  • http://blog.ashmind.com Andrey Shchekin

    Haven't had time to look at it yet. However, is the id specified in the route?
    Does it work with other parameters?

  • http://blog.ashmind.com Andrey Shchekin

    To all people who have id problems: I currently can't reproduce it (id works very well for me). Please, provide an example of how your form is structured.

  • maxwedwards

    I feel clean again! No more checking the value of the button in the controller :) Thanks for this, great solution

  • Randhir

    Found solution to ID problem REPLACE <% using (Html.BeginForm(“Action”, “Post”)) { %> WITH <% using (Html.BeginForm(“Action”, “Post”, new { id = Html.Encode(ViewContext.RouteData.Values["id"]) })) {%> ,
    Thanks for the post Andrey, very handy

  • Nipunvarma

    Andrey, I have added 2 different action methods Save and Delete. I have also added the custom attribute. All submit actions are still directed to the Index post method instead of Save, Delete methods. When I use “Html.BeginForm(“Action”, “Post))”, I get an error “Cannot find Post.aspx/Action. What am I doing wrong here? Please help.
    Thank you.

  • http://blog.ashmind.com Andrey Shchekin

    It's kind of hard to say without looking into details.
    Do the submit button names match actions?
    If you set breakpoint in IsValidName, is it called at all?

  • Nipunvarma

    Yes, IsValidName is called every time.

    The button tag is as follows.

    <input type=”submit” name=”Save” value =”Save” class=”button save”/>

    I have a Save Action method that has the custom attribute. When the button is clicked, the default action name (“Action”) is passed instead of “Save”. MethodInfo.Name is “Save”, but I think I am not pasing the action name correctly.

    Thank you Andrey.

  • http://blog.ashmind.com Andrey Shchekin

    It seems you are doing everything right (it should be “Action”), so it should come to this line
    var request = controllerContext.RequestContext.HttpContext.Request;
    return request[methodInfo.Name] != null;
    which checks whether the submit name matches the method name.
    Check why this does not work (method name not in request?).

  • Nipunvarma

    Thanks, Andrey. I could get it work. But, now I face another issue. Similar to the Save button, I have delete button which calls a javascript function when clicked. After validation, this javascript function submits the form, but I am not able to get the delete button working. Is it because of the javascript intervention?

    BTW, thank you again for your prompt replies and support.

  • http://blog.ashmind.com Andrey Shchekin

    Check what your javascript posts (in Firebug, for example), it should include delete=<any non-empty value> in a list of posted names/values.

  • Sander

    Great solution, I was looking for this a few hours now, only problem I still have is that I cannot use a <input a=”" an=”" another=”" but=”" button.=”" get=”" image=”" is=”" likely=”" most=”" solution=”" submit=”" system,=”" there=”" this=”" to=”" type=”image” with=”">

  • Sander

    Posts do not accept html very well i see, summary I want an image with my submit button but attribute is called and controller method not

  • Pingback: MVC3 RemoteAttribute and muliple submit buttons - Programmers Goodies

  • Pingback: How to get the value from a custom attribute of abutton in ASP.NET MVC - Programmers Goodies

  • Pingback: How to get the value from a custom attribute of a button in ASP.NET MVC - Programmers Goodies

  • Pingback: Validation and Updating UI Values Based on User Input in MVC 3 Razor | InterKnowlogy Blogs

  • Pingback: Multiple submit buttons in a single ASP.NET MVC Form | Blog

  • Pingback: It’s all or nothing – MVC Action Attributes « Mark Gilbert's Blog