How do you handle multiple submit buttons in ASP.NET MVC Framework?

483,875

Solution 1

Here is a mostly clean attribute-based solution to the multiple submit button issue based heavily on the post and comments from Maarten Balliauw.

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class MultipleButtonAttribute : ActionNameSelectorAttribute
{
    public string Name { get; set; }
    public string Argument { get; set; }

    public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
    {
        var isValidName = false;
        var keyValue = string.Format("{0}:{1}", Name, Argument);
        var value = controllerContext.Controller.ValueProvider.GetValue(keyValue);

        if (value != null)
        {
            controllerContext.Controller.ControllerContext.RouteData.Values[Name] = Argument;
            isValidName = true;
        }

        return isValidName;
    }
}

razor:

<form action="" method="post">
 <input type="submit" value="Save" name="action:Save" />
 <input type="submit" value="Cancel" name="action:Cancel" />
</form>

and controller:

[HttpPost]
[MultipleButton(Name = "action", Argument = "Save")]
public ActionResult Save(MessageModel mm) { ... }

[HttpPost]
[MultipleButton(Name = "action", Argument = "Cancel")]
public ActionResult Cancel(MessageModel mm) { ... }

Update: Razor pages looks to provide the same functionality out of the box. For new development, it may be preferable.

Solution 2

Give your submit buttons a name, and then inspect the submitted value in your controller method:

<% Html.BeginForm("MyAction", "MyController", FormMethod.Post); %>
<input type="submit" name="submitButton" value="Send" />
<input type="submit" name="submitButton" value="Cancel" />
<% Html.EndForm(); %>

posting to

public class MyController : Controller {
    public ActionResult MyAction(string submitButton) {
        switch(submitButton) {
            case "Send":
                // delegate sending to another controller action
                return(Send());
            case "Cancel":
                // call another action to perform the cancellation
                return(Cancel());
            default:
                // If they've submitted the form without a submitButton, 
                // just return the view again.
                return(View());
        }
    }

    private ActionResult Cancel() {
        // process the cancellation request here.
        return(View("Cancelled"));
    }

    private ActionResult Send() {
        // perform the actual send operation here.
        return(View("SendConfirmed"));
    }

}

EDIT:

To extend this approach to work with localized sites, isolate your messages somewhere else (e.g. compiling a resource file to a strongly-typed resource class)

Then modify the code so it works like:

<% Html.BeginForm("MyAction", "MyController", FormMethod.Post); %>
<input type="submit" name="submitButton" value="<%= Html.Encode(Resources.Messages.Send)%>" />
<input type="submit" name="submitButton" value="<%=Html.Encode(Resources.Messages.Cancel)%>" />
<% Html.EndForm(); %>

and your controller should look like this:

// Note that the localized resources aren't constants, so 
// we can't use a switch statement.

if (submitButton == Resources.Messages.Send) { 
    // delegate sending to another controller action
    return(Send());

} else if (submitButton == Resources.Messages.Cancel) {
     // call another action to perform the cancellation
     return(Cancel());
}

Solution 3

You can check the name in the action as has been mentioned, but you might consider whether or not this is good design. It is a good idea to consider the responsibility of the action and not couple this design too much to UI aspects like button names. So consider using 2 forms and 2 actions:

<% Html.BeginForm("Send", "MyController", FormMethod.Post); %>
<input type="submit" name="button" value="Send" />
<% Html.EndForm(); %>

<% Html.BeginForm("Cancel", "MyController", FormMethod.Post); %>
<input type="submit" name="button" value="Cancel" />
<% Html.EndForm(); %>

Also, in the case of "Cancel", you are usually just not processing the form and are going to a new URL. In this case you do not need to submit the form at all and just need a link:

<%=Html.ActionLink("Cancel", "List", "MyController") %>

Solution 4

Eilon suggests you can do it like this:

If you have more than one button you can distinguish between them by giving each button a name:

<input type="submit" name="SaveButton" value="Save data" />
<input type="submit" name="CancelButton" value="Cancel and go back to main page" />

In your controller action method you can add parameters named after the HTML input tag names:

public ActionResult DoSomeStuff(string saveButton, string
cancelButton, ... other parameters ...)
{ ... }

If any value gets posted to one of those parameters, that means that button was the one that got clicked. The web browser will only post a value for the one button that got clicked. All other values will be null.

if (saveButton != null) { /* do save logic */ }
if (cancelButton != null) { /* do cancel logic */ }

I like this method as it does not rely on the value property of the submit buttons which is more likely to change than the assigned names and doesn't require javascript to be enabled

See: http://forums.asp.net/p/1369617/2865166.aspx#2865166

Solution 5

Just written a post about that: Multiple submit buttons with ASP.NET MVC:

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;
    }
} 

To use just define a form like 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.

Share:
483,875
Troj
Author by

Troj

I'm a jack of most trades residing in Sweden and usually involved with full-stack web development technologies. I work for tretton37 as a contractor, my list of clients includes among others Sony and IKEA. I dabble in open source software and have many projects in my Github repository and my Bitbucket repository, among many: RefluxJS - Library for uni-directional data flows, inspired by Facebook's Flux In the little free time that I have, all kinds of stuff happen such as drawing pretty pictures, perform ball juggling, play a guitar, hack on games, and solve a Rubik's cube.

Updated on July 08, 2022

Comments

  • Troj
    Troj almost 2 years

    Is there some easy way to handle multiple submit buttons from the same form? For example:

    <% Html.BeginForm("MyAction", "MyController", FormMethod.Post); %>
    <input type="submit" value="Send" />
    <input type="submit" value="Cancel" />
    <% Html.EndForm(); %>
    

    Any idea how to do this in ASP.NET Framework Beta? All examples I've googled for have single buttons in them.

  • paulwhit
    paulwhit about 15 years
    This is what I was looking for here: stackoverflow.com/questions/649513/… - thanks
  • zidane
    zidane over 14 years
    This is ok when you dont need same form data for every submit button. If you need all data in common form than Dylan Beattie is the way to go. Is there any more elegant way to do this?
  • Omu
    Omu about 14 years
    too bad you depend on the text displayed on the button, it's kinda tricky with a multilanguage user interface
  • Dylan Beattie
    Dylan Beattie about 14 years
    Omu - see latest edit, which explains (briefly!) how to do the same thing with localized string/message resources.
  • sam1132
    sam1132 over 13 years
    Switch/case only works with constants, so the localized version can't use switch/case. You need to switch to if else or some other dispatch method.
  • Kris van der Mast
    Kris van der Mast over 13 years
    Looks like the solution Ironicnet provided.
  • Simon Keep
    Simon Keep over 13 years
    This is the solution we are using now and its very neat. Is it MVC 2 only though?
  • Simon Keep
    Simon Keep over 13 years
    Have a look at the solution from Izmoto using MultiButton attribute, seems very nice.
  • Dylan Beattie
    Dylan Beattie over 13 years
    @mcl - you're absolutely right. Code has been edited to use if/else instead of switch() for the localized version. Thanks.
  • sam1132
    sam1132 over 13 years
    Certainly similar, but this shows both localization and the controller code, which is something I didn't see done this way in this thread. I found this thread while looking for how to do this and wanted to document what I came up with for anyone else who might be in the same boat.
  • sam1132
    sam1132 over 13 years
    In fact, it's not the same as Ironicnet's beyond that. He uses <input> elements. I use <button>, which is required to do the localization without having variable value attributes.
  • Kris-I
    Kris-I about 13 years
    About visual presentation, how in this case have the "Send" button next to the "Cancel" button ?
  • desperateCoder
    desperateCoder about 13 years
    Dylan: Well for a cancel button you don't need to submit the data at all and it is bad practice to couple the controller to the UI elements. However if you can make a more or less generic "command" then I think it is ok, but I would not tie it to "submitButton" as that is the name of a UI element.
  • desperateCoder
    desperateCoder about 13 years
    @Kris: you can position your buttons with CSS and they can still reside in 2 different form sections.
  • Eduardo
    Eduardo almost 13 years
    What about form validation? Some buttons may not require full form validation.
  • Max
    Max over 12 years
    I like checking the name as an alternative because you can't always check for the value for eg. you have a list of items and each has a "Delete" button.
  • Rikon
    Rikon over 12 years
    This is beautiful! I'd not seen this before! While I agree that you may want to redesign any solution that is using multiple submits to only use one button, I'm in a spot where I'm hamstrung and must do this. This answer should have won!
  • Omnia9
    Omnia9 over 12 years
    This is a great solution. Very clean
  • Kirk Woll
    Kirk Woll over 12 years
    Beautiful! I think this is the most elegant solution. It eliminates the value of the submit tag from consideration, which is ideal since it is a pure-UI attribute that should have no bearing on the control-flow. Instead, the unique name attribute of each submit tag translates directly into a discrete action method on your controller.
  • Niklas
    Niklas over 12 years
    I think it should be mentioned here that if the controller class is named MyController the second parameter should not read MyController but just My, since the controller part of the name is assumed.
  • Admin
    Admin over 12 years
    seriously? does that not smell to anyone but me?!
  • trevorc
    trevorc over 12 years
    I found this solution to be the happy marriage of the other techniques used. Works perfectly and doesn't effect localization.
  • bjan
    bjan about 12 years
    @DylanBeattie, what if the view is composed of partial views and you want to validate the view? Means each submit button will correspond to a different view and the related action should first validate the view. The master action will validate all partial views before proceeding
  • Biki
    Biki almost 12 years
    @DylanBeattie : What if I want to pass the Model as parameter to my action method? In that case can I still retrieve the button name?
  • Dylan Beattie
    Dylan Beattie almost 12 years
    @Biki - yeah, just bind the button value as a parameter on your model. In a couple of places I've split a particular model into an explicit ViewModel and PostModel - then you call your submit button something like <input type="submit" name="model.SubmitButton" value="Yada" /> and then pass (PostModel model) into your controller method, and you should find the button value is available on model.SubmitButton.
  • J4N
    J4N almost 12 years
    you should use a <button type="submit" instead of <input type, because the value of a <button type isn't the text ;). Then you can have something like this: <button name="mySubmitButton" type="submit" value="keyValue">YourButtonText</button>
  • Scott Lawrence
    Scott Lawrence over 11 years
    Tried this approach and wasn't able to get it working in MVC3. A variation of the #1 vote-getter worked for me.
  • Rebecca
    Rebecca over 11 years
    what is the line 'value = new ValueProviderResult(Argument, Argument, null);' used for? It seems redundant.
  • Samuel
    Samuel over 11 years
    +1 For me, it's by far the best solution for this problem. Since I implemented it, I notice that a lot of traffic pass throught the HttpParamActionAttribut but compared to all other things that Asp.Net MVC have to do while processing a request, it's totally acceptable. To only hack I have to do is put an empty 'Action' named in my controller to prevent Resharper warning me that the action 'Action' don't exist. Thank you very much!
  • Diganta Kumar
    Diganta Kumar over 11 years
    I get null value for onDelete and onSave in the controller method. Do you know why?
  • adelb
    adelb about 11 years
    I'm trying to implement this but it doesn't work. It seems that the attribute is never hit. It goes directly to my Index controller. Any idea why?
  • Andrew Barber
    Andrew Barber about 11 years
    You may not have realized it, but this same answer was posted quite a few times to this question already.
  • Andrew Barber
    Andrew Barber about 11 years
    Also, you seem to post answers that only contain code, with no explanation. Would you consider adding some narrative to explain why the code works, and what makes it an answer to the question? This would be very helpful to the person asking the question, and anyone else who comes along.
  • Andrew Barber
    Andrew Barber about 11 years
    Sure, it works fine. But that answer has already been given by others, a long time ago. And they included explanations about why it works, too.
  • Darren Griffith
    Darren Griffith about 11 years
    @iwayneo, interesting solution. The site was down when I went there, but wayback had it: web.archive.org/web/20110312212221/http://blogs.sonatribe.co‌​m/…
  • mkozicki
    mkozicki about 11 years
    @AdeLia I haven't had any issues with just decorating the methods with the MultipleSubmit attribute. I'm happy to help, but I need more info to do so (code, markup, etc).
  • adelb
    adelb about 11 years
    @mkozicki It seems that it was because I had the MultipleButtonAttribute class in another project. Once I put it in the same project it works. Weird, i wouldn't think that would be a problem as long as the reference is correct..
  • crichavin
    crichavin about 11 years
    What about capturing the entire model in the controller as well in addition to the submit button (string submitButton)?
  • Chris McGrath
    Chris McGrath about 11 years
    Me likey nice and clean going to have to add this one to my toolbox
  • JotaBe
    JotaBe almost 11 years
    I can't understand how this was chosen as the right answer. HTML5 supports formaction to post a form to different URLs, so I suspect that's not so bad an idea...
  • Kugel
    Kugel almost 11 years
    If anyone comes across this old question, this is the cleanest answer if you don't wan't to use HTML5 <button> elements. If you don't mind HTML5 then use <button> with value attribute.
  • Péter
    Péter almost 11 years
    Although this is working, but I think it's wrong practice to have two elements with the same name.
  • Sergey
    Sergey almost 11 years
    Either one won't be null if you click the according button. Which button do you click getting the null?
  • Piotr Kula
    Piotr Kula almost 11 years
    Short and sweet.. but not for mvc 3+
  • Piotr Kula
    Piotr Kula almost 11 years
    Yeaaaaaaaa. Works on MVC 4.5. The other dont seem to work. great +1
  • bizzehdee
    bizzehdee over 10 years
    How would this work with passing the model to the action instead of just the submit value?
  • Ironicnet
    Ironicnet over 10 years
    It's not necessary wrong. It depends of how you are using the inputs. You can have multiple elements with the same name, and expecting to receive multiple data (this is how the radiobuttons and checkboxes works). But yeah, if you are using this method is because you are doing it "wrong"... That's why i put "You could" but not "You should" :P
  • jamesSampica
    jamesSampica over 10 years
    A problem with this approach is that if you attempt to return View(viewmodel) in the case where your model has errors, it will attempt to return a view called Send or depending on what your argument name is.
  • ajbeaven
    ajbeaven over 10 years
    @Shoe - have just found a similar thing. Ensure you explicitly specify the name of the view to return if you're using this method: return View("Index", viewModel)
  • aruno
    aruno over 10 years
    see also stackoverflow.com/questions/4171664/… for how to create a button where the text can differ from the submitted value
  • KomalJariwala
    KomalJariwala over 10 years
    @mkozicki, It's not working with remote validation. Please any help for handling multiple buttons with remote validation.
  • mkozicki
    mkozicki over 10 years
    @KomalJariwala Answers to your question here can help you with this.
  • Admin
    Admin about 10 years
    In the future, it is helpful for you to explain your code. Thank you!
  • Tom Hofman
    Tom Hofman about 10 years
    I see load of custom/extra code on this question but w3.org html standards already covered this bit see my answer all the way down. Minimal code, maximum result, my favorite kind.
  • Tom Hofman
    Tom Hofman about 10 years
    Take a look at my answer below, it doesn't rely on draft specifications. Your answer does allow the possibility to have different action urls, which mine doesn't.
  • mko
    mko over 9 years
    How would you do it if creating an ajax call on this form. It seems form.serialize() does not pickup up submit button name..
  • HotN
    HotN over 9 years
    Be careful not to name your buttons "action" if you're using jQuery. It causes a conflict within the library that breaks the action URL.
  • KingOfHypocrites
    KingOfHypocrites over 9 years
    Doesn't work for me. Wasted a lot of time playing with this. Comes back with nothing but it calls getvalue.
  • KingOfHypocrites
    KingOfHypocrites over 9 years
    To answer from @bizzehdee, just pass the model as first param and the submit button name as the second param.
  • Graham
    Graham over 9 years
    This approach is so much easier than the crazy custom-attribute stuff. KISS principle for the win.
  • Chris Hawkes
    Chris Hawkes over 9 years
    the biggest problem with multiple submit buttons is the first submit will always be fired on carriage return. This requires javascript to detect button clicks and things get messy from there.
  • Jason Evans
    Jason Evans about 9 years
    By far the cleanest, and easiest, solution I've seen.
  • Ian Newson
    Ian Newson about 9 years
    -1, in practice I see this approach lead to horrendous code. For one, you end up with hard(er) to follow logic and two, you end up having to accept every parameter required by each concrete method which leads to a massive method declaration. The custom attribute approach IS the KISS approach! If you don't like custom attributes ASP.NET isn't for you...
  • Vignesh Subramanian
    Vignesh Subramanian about 9 years
    just an information, we need to add system.Reflection for MethodInfo
  • ejhost
    ejhost about 9 years
    I reviewed all of the solutions and also agree this is a nice elegant, simple solution. Great bc there are no conditional statements and robust where you can define a new controller action when you have a new button. Called my class MultiButtonActionHandler FYI ;-)
  • Tobias J
    Tobias J almost 9 years
    Why controllerContext.Controller.ControllerContext.RouteData? Isn't that the same as just controllerContext.RouteData?
  • boilers222
    boilers222 almost 9 years
    Can't get this to work. controllerContext.Controller.ValueProvider.GetValue(keyValue‌​) is always null. Button is -- <button id="buttonNotesSave" type="submit" value="NotesSave" name="action:CustomerNotes"> -- Controller usage is -- [MultipleButton(Name = "action", Argument = "CustomerNotes")] -- Can anyone help?
  • Aaron Hudon
    Aaron Hudon over 8 years
    Works like a hot damn in MVC 5.2.3 Cheers.
  • Yass
    Yass over 8 years
    @mkozicki, i tried in IE, it didn't work. I'm getting 404 error, but it works in chrome. Any thoughts?
  • mkozicki
    mkozicki over 8 years
    @Yass Not sure why it wouldn't work in IE, but check that IsValidName is being hit and what the values for Name and Argument. It might be that IE isn't including the submit value in the request.
  • Nirman
    Nirman over 8 years
    Works very well in both IE and Chrome
  • skjoshi
    skjoshi about 8 years
    Is this still valid in ASP.net 5 MVC 6? I am getting error for ActionNameSelectorAttribute not found when used in ASP.net 5.
  • Kaan
    Kaan about 8 years
    I'm using MVC 5.2.3, it is working in IE11 but in Chrome buttons are not comming in FormValueProvider. :(
  • mkozicki
    mkozicki about 8 years
    @Sanju. Make sure you have the following using statements using System; using System.Reflection; using System.Web.Mvc;
  • mkozicki
    mkozicki about 8 years
    @Kaan, I was unable to reproduce your issue in a new project.
  • Kaan
    Kaan about 8 years
    @mkozicki, actually I found the issue. The reason why it is not working is because of jquery.validation's submitHandler methods. If you don't use it, great.
  • patrick
    patrick about 8 years
    @DylanBeattie Is there a good way to pass in an ID as well ( delete button with ID ) ?
  • Anton
    Anton almost 8 years
    It works till I call redirect from action... I got error "Child actions are not allowed to perform redirect actions"
  • mkozicki
    mkozicki almost 8 years
  • alamin
    alamin almost 8 years
    when inhereting ActionNameSelectorAttribute for MultipleSubmitButtonAttribute it could not find a definition. plz can anyone tell he how to resolve the red squiggle and what using statements should i use.
  • ooXei1sh
    ooXei1sh almost 8 years
    Although this is a nice approach, you won't be able to use the built in mvc model binder because it uses the buttons "name" attribute.
  • Aaron Hudon
    Aaron Hudon almost 8 years
    The purpose of this solution is a work around to the MVC post routing. Please describe an improvement.
  • Tom
    Tom almost 8 years
    Works perfectly for me while handling multiple buttons. I just have one issue with it: Lets say I also want to call the action manually with $.post(path/to/controller/test). This would throw an error: No matching action was found on controller... I need to add the action:test manually to get it work. Something like $.post(path/to/controller/test, [{ name: 'action:test', value: '' }]) to get it work. Is there any way to make a manual call possible?
  • mkozicki
    mkozicki almost 8 years
    @Tom You should be able to directly map the route to the action you want to call. Otherwise, the implementation depends on the name='action:test' attribute being set.
  • Worthy7
    Worthy7 almost 8 years
    Basically this is the answer to the actual question. And it could all be achieved by just using route values. So long as he isn't posting any actual data beside that. So I'm gonna upvote this one.
  • Ananda G
    Ananda G over 7 years
    @mkozicki Many many thanks, it works fine, a great help for me.
  • BrandonG
    BrandonG over 7 years
    This is the solution we chose to use for a simple web app where we wanted ASP.NET WebForms functionality but within MVC.
  • Arif YILMAZ
    Arif YILMAZ over 7 years
    @Kugel is right, this is still the cleanest answer. Thanks
  • ᴍᴀᴛᴛ ʙᴀᴋᴇʀ
    ᴍᴀᴛᴛ ʙᴀᴋᴇʀ over 7 years
    @boilers222 Hi, I'm experiencing the same issue now, but it worked 111 days ago when I implemented it. Did you manage to resolve the null problem and if so what was the solution (if you can remember). Thanks!
  • ᴍᴀᴛᴛ ʙᴀᴋᴇʀ
    ᴍᴀᴛᴛ ʙᴀᴋᴇʀ over 7 years
    var value = controllerContext.Controller.ValueProvider.GetValue(keyValue‌​) is always returning null.
  • Mohd Aman
    Mohd Aman over 7 years
    hello @mkozicki.. can u provide me demo link for this
  • Gusti Arya
    Gusti Arya about 7 years
    doesnt work for me, dont know why it always send me into index controller
  • Damitha
    Damitha almost 7 years
    I think adding a parameter to the post action named "submit" is easier than this. Check this binaryintellect.net/articles/…
  • djack109
    djack109 almost 7 years
    Awesome. exactly what I used to do in webforms. Cheers buddy
  • pabben
    pabben over 5 years
    Much more simpler than the top answer! Thanks!
  • j1rjacob
    j1rjacob over 5 years
    My only issue is the url it's stay the same. ex http://localhost:66666/MaterialRequisition/Step1 for Step 2 and Step 3; fyi I have 3 buttons Previous Next Cancel
  • Himalaya Garg
    Himalaya Garg over 4 years
    This is another useful link
  • Himalaya Garg
    Himalaya Garg over 4 years
    This is another useful link explaining different ways of form post.
  • Himalaya Garg
    Himalaya Garg over 4 years
    This is another useful link explaining different ways of form post.
  • misterbee180
    misterbee180 over 2 years
    Not sure what everyone's downvoting this for. It's a super easy solution that works. Command will always be populated w/ the value of the button that was clicked and you can simply jump to what ever function you want to run based on it. Otherwise you run the main index logic.