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

  • ryant

    Great solution. I like this better than the alternatives for multi-button form submits. I ran into one issue that others may want to be aware of. This will not work correctly in IE (6/7/8) browsers if you are using an INPUT tag with a type of “image”. IE will only send the X and Y coordinates of the clicked image when the form is submitted and not the value of the input element itself. Without the value getting passed to the server – the IsValidName() method will not work in its current form. I worked around this by changing my markup to not use the Image type. I’m sure a server solution is possible as well – although I haven’t thought through it myself.

  • Sebastian

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] //—You Forget it !!!
    public class HttpParamActionAttribute : ActionNameSelectorAttribute {

    }

    Sebastian.

  • AEL

    Hi, thanks for this blog however this won’t work for the buttons in the partial class.

    Here’s the code snippet when calling the partial class:
    asp:Content ID=”Content2″ ContentPlaceHolderID=”PageContent” runat=”server”>

    Please advice.

    Thanks,
    AEL

  • AEL

    Correction partial view not partial class.

  • Werewolf

    Great post!

    As for your last statement of: There is some room for improvements (hardcoded “action” as a default action name), but in general I’m satisfied with this solution.
    How about adding a static read only property to the attribute?
    i.e.
    public class HttpParamActionAttribute : ActionNameSelectorAttribute
    {
    … existing code here … with one change
    if (!actionName.Equals(ActionName (<– referencing the property so there's only one place to change the action name)

    ///
    /// Gets the name of the action for the Html.BeginForm to allow multiple submit buttons to invoke specific action methods in ASP.NET MVC.
    ///
    /// using (Html.BeginForm(HttpParamActionAttribute.ActionName, "Controller Name Here")) {
    /// <button type="submit" name="ActualActionName">Save</button>
    /// }
    ///

    ///
    ///
    /// The name of the action.
    ///
    public static string ActionName
    {
    get { return “MultipleSubmitButtons”; }
    }
    }

    and the begin form looking like:
    using (Html.BeginForm(HttpParamActionAttribute.ActionName, “Post”)

    If you don’t care for the ActionName property being in the actual attribute, you could create a new class both the attribute and BeginForm method can use…

  • teachM3

    Maybe I’m completely missing something here, but if you don’t provide an action with the Name of “Action” in your Post controller, how does the view even compile?

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

    I haven’t yet had time to look into it since I do not have image buttons, sorry.

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

    Well the actions are not strongly typed when used this way.

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

    Yes, I agree, and I should probably create a separate class for the name.
    But I am a bit too lazy right now to update the post, so I’ll leave it for now with only a demonstration of more complex part.

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

    That’s weird. Can you look at what exactly gets submitted? (using Firebug, for example)

  • teachM3

    Ahhhh, ok, that makes perfect sense now, I’m so used to strongly typed views being a requirement due to the fact that it’s required in all of our designs at work. Great post, I’ll see if I can come up with an implementation using strongly typed views.

  • Jackandjane

    Very useful, thanks.

  • Garry

    Thats work fine.
    But what if u use Ajax.BeginForm? can we have more than one actionmethod?

  • Prateeksasanker

    I am having the problem in passing an id to my action method. The id passed is null. I have read the comments in the blog but haven’t found any solutions. Can anybody who previously faced the same problem help me

  • DGurram

    Very clean. I like it. Thanks for a great post.

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

  • http://twitter.com/MindstormInt sarah perrie

    this seems like a very complicated solution to a very simple issue. All you have to do is give each button a unique name, set the names as optional string parameters with default empty string values, then check if the parameters have a value. 

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

    It depends on a mindset — if you are looking for a solution, yes, there are several ways to do this.
    However I wanted to solve it in the way MVC framework has intended for controllers to be used — abstracted from the lowest details of HTTP (see Model Binders etc).

  • Richard Lim

    Hi,

    I can’t really get it to work with JQuery, seems to be conflicting in some area. Any ideas?

  • willem dhaeseleer

    Hey Andrey,

    This solution is just excellent, very clean , much better then the proposed solution from David Findley

    I added some commenting and swapped the hard coded “Action” for a default, check it out:
    http://pastebin.com/PhnKDekw

    I will be using this whenever i use multiple buttons, in other situations i usually just use jQuery Postback.

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

  • Ambrish

    Hi Diego,

    Can you please explain what was the mistake and what fix you had to do. I am facimg the same problem.

    Regards,
    Ambrish

  • Ambrish

    Hello Andrey,

    If i left the Html.BeginForm() as default then irrespective of which 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. Am I missing on anything here?

    Regards,
    Ambrish

  • David Faucher

    For anyone curious, I had a similar issue and the problem was in my Action.  I was using return View(model), so the server was looking for an “Action” view which really does not exist. The simple fix is to specifiy the view in the return:  return View(“Edit”, model).

    Thanks for this solution.  It has helped make a very nice Edit/Preview feature work efficently and cleanly.

  • Jared Ross

    Would anyone be willing to share some code related to how they’re registering routes in their Global.asax.cs? I’ve been attempting to utilize the code in this post (beautifully written, btw) however I’m struggling to get it working correctly. When I run the given code I receive an error because it attempts to find the controller “Post” and the action “Action”. So based on my MapRoute it’s trying to go to Post.aspx/Action which does not exist. 

    I also tried removing BeginForm(“Action”,”Post”) and instead stuck with simply BeginForm(). This almost appears to work, it enters the overridden IsValidName method but the actionName does not match my button, instead it matches my page.
    Any thoughts or help would be appreciated. I believe we’re using MVC v2.0.50727

    Thank you,
    Jared

  • Jared Ross

    After working with another developer on my team we solved our issue. For us, instead of using BeginForm(“Action”,”Post”) we needed to specify our actual controller: BeginForm(“Action”,”ActualControllerNameHere”) and this worked. 

  • Raj

    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?

  • http://twitter.com/stevenbey Steven Bey

     There are two drawbacks to this implementation: 1) when the form is posted, the URL changes; and 2) the executing action must explicitly specify the view name.  I have written a blog post demonstrating how to avoid these issues:

    http://stevenbey.com/archive/2012/02/18/enable-multiple-submit-buttons-in-asp-net-mvc.aspx

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

  • Junk

    I am gettingÂ
    The current request for action ‘Action’ on controller type ‘MyController’ is ambiguous between the following action methods:  and it lists my two actions. 

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

    What are the
    1. Names of actions
    2. Names of buttons
    3. Name of action in Html.BeginForm?