How do you handle multiple submit buttons in ASP.NET MVC Framework?
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.
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, 2022Comments
-
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 about 15 yearsThis is what I was looking for here: stackoverflow.com/questions/649513/… - thanks
-
zidane over 14 yearsThis 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 about 14 yearstoo bad you depend on the text displayed on the button, it's kinda tricky with a multilanguage user interface
-
Dylan Beattie about 14 yearsOmu - see latest edit, which explains (briefly!) how to do the same thing with localized string/message resources.
-
sam1132 over 13 yearsSwitch/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 over 13 yearsLooks like the solution Ironicnet provided.
-
Simon Keep over 13 yearsThis is the solution we are using now and its very neat. Is it MVC 2 only though?
-
Simon Keep over 13 yearsHave a look at the solution from Izmoto using MultiButton attribute, seems very nice.
-
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 over 13 yearsCertainly 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 over 13 yearsIn 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 about 13 yearsAbout visual presentation, how in this case have the "Send" button next to the "Cancel" button ?
-
desperateCoder about 13 yearsDylan: 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 about 13 years@Kris: you can position your buttons with CSS and they can still reside in 2 different form sections.
-
Eduardo almost 13 yearsWhat about form validation? Some buttons may not require full form validation.
-
Max over 12 yearsI 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 over 12 yearsThis 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 over 12 yearsThis is a great solution. Very clean
-
Kirk Woll over 12 yearsBeautiful! 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 uniquename
attribute of eachsubmit
tag translates directly into a discrete action method on your controller. -
Niklas over 12 yearsI think it should be mentioned here that if the controller class is named
MyController
the second parameter should not readMyController
but justMy
, since the controller part of the name is assumed. -
Admin over 12 yearsseriously? does that not smell to anyone but me?!
-
trevorc over 12 yearsI found this solution to be the happy marriage of the other techniques used. Works perfectly and doesn't effect localization.
-
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 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 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 almost 12 yearsyou 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 over 11 yearsTried this approach and wasn't able to get it working in MVC3. A variation of the #1 vote-getter worked for me.
-
Rebecca over 11 yearswhat is the line 'value = new ValueProviderResult(Argument, Argument, null);' used for? It seems redundant.
-
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 over 11 yearsI get null value for onDelete and onSave in the controller method. Do you know why?
-
adelb about 11 yearsI'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 about 11 yearsYou may not have realized it, but this same answer was posted quite a few times to this question already.
-
Andrew Barber about 11 yearsAlso, 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 about 11 yearsSure, 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 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.com/…
-
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 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 about 11 yearsWhat about capturing the entire model in the controller as well in addition to the submit button (string submitButton)?
-
Chris McGrath about 11 yearsMe likey nice and clean going to have to add this one to my toolbox
-
JotaBe almost 11 yearsI 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 almost 11 yearsIf 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 almost 11 yearsAlthough this is working, but I think it's wrong practice to have two elements with the same name.
-
Sergey almost 11 yearsEither one won't be null if you click the according button. Which button do you click getting the null?
-
Piotr Kula almost 11 yearsShort and sweet.. but not for mvc 3+
-
Piotr Kula almost 11 yearsYeaaaaaaaa. Works on MVC 4.5. The other dont seem to work. great +1
-
bizzehdee over 10 yearsHow would this work with passing the model to the action instead of just the submit value?
-
Ironicnet over 10 yearsIt'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 over 10 yearsA 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 calledSend
or depending on what your argument name is. -
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 over 10 yearssee also stackoverflow.com/questions/4171664/… for how to create a button where the text can differ from the submitted value
-
KomalJariwala over 10 years@mkozicki, It's not working with remote validation. Please any help for handling multiple buttons with remote validation.
-
mkozicki over 10 years@KomalJariwala Answers to your question here can help you with this.
-
Admin about 10 yearsIn the future, it is helpful for you to explain your code. Thank you!
-
Tom Hofman about 10 yearsI 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 about 10 yearsTake 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 over 9 yearsHow would you do it if creating an ajax call on this form. It seems form.serialize() does not pickup up submit button name..
-
HotN over 9 yearsBe 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 over 9 yearsDoesn't work for me. Wasted a lot of time playing with this. Comes back with nothing but it calls getvalue.
-
KingOfHypocrites over 9 yearsTo answer from @bizzehdee, just pass the model as first param and the submit button name as the second param.
-
Graham over 9 yearsThis approach is so much easier than the crazy custom-attribute stuff. KISS principle for the win.
-
Chris Hawkes over 9 yearsthe 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 about 9 yearsBy far the cleanest, and easiest, solution I've seen.
-
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 about 9 yearsjust an information, we need to add system.Reflection for MethodInfo
-
ejhost about 9 yearsI 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 almost 9 yearsWhy
controllerContext.Controller.ControllerContext.RouteData
? Isn't that the same as justcontrollerContext.RouteData
? -
boilers222 almost 9 yearsCan'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 over 8 yearsWorks like a hot damn in
MVC 5.2.3
Cheers. -
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 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 over 8 yearsWorks very well in both IE and Chrome
-
skjoshi about 8 yearsIs this still valid in ASP.net 5 MVC 6? I am getting error for
ActionNameSelectorAttribute
not found when used in ASP.net 5. -
Kaan about 8 yearsI'm using MVC 5.2.3, it is working in IE11 but in Chrome buttons are not comming in FormValueProvider. :(
-
mkozicki about 8 years@Sanju. Make sure you have the following using statements
using System; using System.Reflection; using System.Web.Mvc;
-
mkozicki about 8 years@Kaan, I was unable to reproduce your issue in a new project.
-
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 about 8 years@DylanBeattie Is there a good way to pass in an ID as well ( delete button with ID ) ?
-
Anton almost 8 yearsIt works till I call redirect from action... I got error "Child actions are not allowed to perform redirect actions"
-
mkozicki almost 8 years@Anton This may help: stackoverflow.com/questions/25015833/…
-
alamin almost 8 yearswhen 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 almost 8 yearsAlthough 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 almost 8 yearsThe purpose of this solution is a work around to the MVC post routing. Please describe an improvement.
-
Tom almost 8 yearsWorks 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 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 almost 8 yearsBasically 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 over 7 years@mkozicki Many many thanks, it works fine, a great help for me.
-
BrandonG over 7 yearsThis is the solution we chose to use for a simple web app where we wanted ASP.NET WebForms functionality but within MVC.
-
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 returningnull
. -
Mohd Aman over 7 yearshello @mkozicki.. can u provide me demo link for this
-
Gusti Arya about 7 yearsdoesnt work for me, dont know why it always send me into index controller
-
Damitha almost 7 yearsI think adding a parameter to the post action named "submit" is easier than this. Check this binaryintellect.net/articles/…
-
djack109 almost 7 yearsAwesome. exactly what I used to do in webforms. Cheers buddy
-
pabben over 5 yearsMuch more simpler than the top answer! Thanks!
-
j1rjacob over 5 yearsMy 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 over 4 yearsThis is another useful link
-
Himalaya Garg over 4 yearsThis is another useful link explaining different ways of form post.
-
Himalaya Garg over 4 yearsThis is another useful link explaining different ways of form post.
-
misterbee180 over 2 yearsNot 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.