779

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.

John Smith
  • 7,243
  • 6
  • 49
  • 61
Spoike
  • 119,724
  • 44
  • 140
  • 158
  • 9
    Worth mentioning [starting from **ASP.NET Core** there are much easier solutions around](http://stackoverflow.com/questions/36555265/asp-net-mvc-core-6-multiple-submit-buttons) than the ones listed here. – Steven Jeuris Feb 16 '17 at 00:37
  • 1
    This tutorial might help: [Submitting a form to different action methods in ASP.NET MVC](https://medium.com/@bahreinihooman/submitting-a-form-to-different-action-methods-in-asp-net-mvc-c7a06e3c2766) – Hooman Bahreini Sep 10 '19 at 02:01

35 Answers35

667

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.

Natrium
  • 30,772
  • 17
  • 59
  • 73
mkozicki
  • 6,802
  • 1
  • 17
  • 16
  • 5
    I found this solution to be the happy marriage of the other techniques used. Works perfectly and doesn't effect localization. – trevorc Nov 29 '11 at 22:33
  • what is the line 'value = new ValueProviderResult(Argument, Argument, null);' used for? It seems redundant. – Rebecca Jan 09 '13 at 13:48
  • 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? – adelb Feb 25 '13 at 10:00
  • @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). – mkozicki Mar 05 '13 at 17:08
  • @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.. – adelb Mar 12 '13 at 12:37
  • Me likey nice and clean going to have to add this one to my toolbox – Chris McGrath Apr 19 '13 at 05:14
  • 3
    Yeaaaaaaaa. Works on MVC 4.5. The other dont seem to work. great +1 – Piotr Kula Jul 18 '13 at 20:35
  • 11
    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. – jamesSampica Oct 17 '13 at 16:03
  • 16
    @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)` – ajbeaven Nov 27 '13 at 21:08
  • @mkozicki, It's not working with remote validation. Please any help for handling multiple buttons with remote validation. – KomalJariwala Jan 25 '14 at 12:56
  • @KomalJariwala Answers to your question [here](http://stackoverflow.com/questions/21351116/handle-multiple-submit-buttons-with-remote-validation-in-mvc4-razor) can help you with this. – mkozicki Jan 27 '14 at 14:57
  • 1
    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 Apr 30 '14 at 12:05
  • Doesn't work for me. Wasted a lot of time playing with this. Comes back with nothing but it calls getvalue. – KingOfHypocrites Nov 18 '14 at 20:27
  • 1
    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. – Chris Hawkes Jan 29 '15 at 19:41
  • 4
    just an information, we need to add system.Reflection for MethodInfo – Vignesh Subramanian Apr 01 '15 at 11:06
  • 1
    Why `controllerContext.Controller.ControllerContext.RouteData`? Isn't that the same as just `controllerContext.RouteData`? – Tobias J May 15 '15 at 17:28
  • 1
    Can't get this to work. controllerContext.Controller.ValueProvider.GetValue(keyValue) is always null. Button is -- – boilers222 Jul 08 '15 at 15:16
  • Works like a hot damn in `MVC 5.2.3` Cheers. – Aaron Hudon Nov 07 '15 at 04:28
  • @mkozicki, i tried in IE, it didn't work. I'm getting 404 error, but it works in chrome. Any thoughts? – Yass Jan 19 '16 at 02:50
  • @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. – mkozicki Jan 20 '16 at 16:56
  • Works very well in both IE and Chrome – Nirman Jan 25 '16 at 09:08
  • Is this still valid in ASP.net 5 MVC 6? I am getting error for `ActionNameSelectorAttribute` not found when used in ASP.net 5. – skjoshi Feb 14 '16 at 15:55
  • I'm using MVC 5.2.3, it is working in IE11 but in Chrome buttons are not comming in FormValueProvider. :( – Kaan Feb 15 '16 at 15:29
  • 1
    @Sanju. Make sure you have the following using statements `using System; using System.Reflection; using System.Web.Mvc;` – mkozicki Feb 15 '16 at 21:17
  • @Kaan, I was unable to reproduce your issue in a new project. – mkozicki Feb 15 '16 at 21:18
  • 1
    @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. – Kaan Feb 16 '16 at 11:55
  • It works till I call redirect from action... I got error "Child actions are not allowed to perform redirect actions" – Anton May 21 '16 at 20:59
  • @Anton This may help: http://stackoverflow.com/questions/25015833/child-actions-are-not-allowed-to-perform-redirect-actions-after-setting-the-sit – mkozicki May 25 '16 at 14:52
  • 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. – alamin May 27 '16 at 04:42
  • 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? – Tom Jul 28 '16 at 08:34
  • @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. – mkozicki Aug 01 '16 at 14:44
  • @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! – ᴍᴀᴛᴛ ʙᴀᴋᴇʀ Nov 07 '16 at 10:06
  • `var value = controllerContext.Controller.ValueProvider.GetValue(keyValue)` is always returning `null`. – ᴍᴀᴛᴛ ʙᴀᴋᴇʀ Nov 07 '16 at 12:48
  • hello @mkozicki.. can u provide me demo link for this – Mohd Aman Jan 12 '17 at 06:05
  • doesnt work for me, dont know why it always send me into index controller – Gusti Arya Apr 25 '17 at 07:28
  • I think adding a parameter to the post action named "submit" is easier than this. Check this http://www.binaryintellect.net/articles/c69d78a3-21d7-416b-9d10-6b812a862778.aspx – Damitha Jun 30 '17 at 15:29
  • 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** – j1rjacob Feb 01 '19 at 20:03
  • This is another useful [link](http://www.binaryintellect.net/articles/c69d78a3-21d7-416b-9d10-6b812a862778.aspx) – Himalaya Garg Oct 21 '19 at 07:37
487

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());
}
Dylan Beattie
  • 53,688
  • 35
  • 128
  • 197
  • This is what I was looking for here: http://stackoverflow.com/questions/649513/how-do-i-perform-a-secondary-action-i-e-calculate-fields-in-asp-net-mvc - thanks – paulwhit Mar 17 '09 at 02:44
  • 28
    too bad you depend on the text displayed on the button, it's kinda tricky with a multilanguage user interface – Omu Apr 22 '10 at 11:52
  • Omu - see latest edit, which explains (briefly!) how to do the same thing with localized string/message resources. – Dylan Beattie Apr 22 '10 at 12:36
  • 3
    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. – mlibby Nov 01 '10 at 20:58
  • Have a look at the solution from Izmoto using MultiButton attribute, seems very nice. – Simon Keep Nov 02 '10 at 15:31
  • @mcl - you're absolutely right. Code has been edited to use if/else instead of switch() for the localized version. Thanks. – Dylan Beattie Nov 03 '10 at 11:38
  • What about form validation? Some buttons may not require full form validation. – Eduardo Jun 17 '11 at 15:08
  • @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 – bjan Feb 29 '12 at 12:06
  • @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? – Biki May 29 '12 at 05:17
  • @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 and then pass (PostModel model) into your controller method, and you should find the button value is available on model.SubmitButton. – Dylan Beattie May 29 '12 at 09:42
  • 12
    you should use a – J4N May 29 '12 at 12:32
  • @iwayneo, interesting solution. The site was down when I went there, but wayback had it: http://web.archive.org/web/20110312212221/http://blogs.sonatribe.com/wayne/2011/03/03/asp-net-mvc-action-selector/ – Darren Griffith Mar 01 '13 at 00:22
  • What about capturing the entire model in the controller as well in addition to the submit button (string submitButton)? – crichavin Mar 20 '13 at 02:05
  • 5
    How would this work with passing the model to the action instead of just the submit value? – bizzehdee Aug 07 '13 at 07:56
  • see also http://stackoverflow.com/questions/4171664/html-submit-button-different-value-button-text for how to create a button where the text can differ from the submitted value – Simon_Weaver Dec 09 '13 at 02:21
  • 2
    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. – HotN Oct 14 '14 at 21:20
  • To answer from @bizzehdee, just pass the model as first param and the submit button name as the second param. – KingOfHypocrites Nov 18 '14 at 20:56
  • This approach is so much easier than the crazy custom-attribute stuff. KISS principle for the win. – Graham Jan 23 '15 at 16:46
  • 2
    -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... – Ian Newson Mar 31 '15 at 15:51
  • @DylanBeattie Is there a good way to pass in an ID as well ( delete button with ID ) ? – patrick Feb 29 '16 at 14:51
124

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") %>
Trevor de Koekkoek
  • 2,496
  • 1
  • 20
  • 15
  • 55
    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? – zidane Sep 28 '09 at 09:30
  • 3
    About visual presentation, how in this case have the "Send" button next to the "Cancel" button ? – TheBoubou Mar 09 '11 at 04:49
  • 1
    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. – Trevor de Koekkoek Mar 16 '11 at 15:08
  • 1
    @Kris: you can position your buttons with CSS and they can still reside in 2 different form sections. – Trevor de Koekkoek Mar 16 '11 at 15:10
  • 1
    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. – Niklas Nov 01 '11 at 15:17
  • 8
    seriously? does that not smell to anyone but me?! –  Nov 24 '11 at 11:48
  • 1
    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... – JotaBe May 16 '13 at 12:21
  • 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. – Worthy7 Aug 02 '16 at 06:25
107

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

Spoike
  • 119,724
  • 44
  • 140
  • 158
PabloBlamirez
  • 2,692
  • 3
  • 18
  • 14
  • 2
    If anyone comes across this old question, this is the cleanest answer if you don't wan't to use HTML5 – Kugel Jun 12 '13 at 01:28
  • How would you do it if creating an ajax call on this form. It seems form.serialize() does not pickup up submit button name.. – mko Sep 15 '14 at 14:15
  • @Kugel is right, this is still the cleanest answer. Thanks – Arif YILMAZ Oct 31 '16 at 12:42
46

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.

Andrey Shchekin
  • 21,101
  • 19
  • 94
  • 162
  • 1
    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. – Kirk Woll Oct 26 '11 at 04:29
  • +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! – Samuel Jan 18 '13 at 18:39
  • 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 ;-) – ejhost Apr 21 '15 at 19:30
42

it is short and suite:

It was answered by Jeroen Dop

<input type="submit" name="submitbutton1" value="submit1" />
<input type="submit" name="submitbutton2" value="submit2" />

and do like this in code behinde

 if( Request.Form["submitbutton1"] != null)
{
    // Code for function 1
}
else if(Request.Form["submitButton2"] != null )
{
       // code for function 2
}

Good luck.

Community
  • 1
  • 1
Ali Gol Gol
  • 775
  • 7
  • 14
34

I would suggest interested parties have a look at Maarten Balliauw's solution. I think it is very elegant.

In case the link dissapears, it's using the MultiButton attribute applied to a controller action to indicate which button click that action should relate to.

Steven Jeuris
  • 18,274
  • 9
  • 70
  • 161
Izmoto
  • 1,939
  • 2
  • 17
  • 21
  • This is the solution we are using now and its very neat. Is it MVC 2 only though? – Simon Keep Nov 02 '10 at 15:30
  • 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! – Rikon Sep 30 '11 at 14:19
  • This is a great solution. Very clean – Omnia9 Oct 24 '11 at 13:42
  • Tried this approach and wasn't able to get it working in MVC3. A variation of the #1 vote-getter worked for me. – Scott Lawrence Oct 03 '12 at 19:36
  • Short and sweet.. but not for mvc 3+ – Piotr Kula Jul 18 '13 at 20:23
  • This is another useful [link](http://www.binaryintellect.net/articles/c69d78a3-21d7-416b-9d10-6b812a862778.aspx) explaining different ways of form post. – Himalaya Garg Oct 21 '19 at 07:38
20

You should be able to name the buttons and give them a value; then map this name as an argument to the action. Alternatively, use 2 separate action-links or 2 forms.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
15

You could write:

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

And then in the page check if the name == "Send" or name == "Cancel"...

Kris van der Mast
  • 16,343
  • 8
  • 39
  • 61
Ironicnet
  • 547
  • 3
  • 14
  • 1
    Although this is working, but I think it's wrong practice to have two elements with the same name. – Péter Jul 05 '13 at 11:25
  • 1
    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 – Ironicnet Aug 30 '13 at 18:29
12

I've came across this 'problem' as well but found a rather logical solution by adding the name attribute. I couldn't recall having this problem in other languages.

http://www.w3.org/TR/html401/interact/forms.html#h-17.13.2

  • ...
  • If a form contains more than one submit button, only the activated submit button is successful.
  • ...

Meaning the following code value attributes can be changed, localized, internationalized without the need for extra code checking strongly-typed resources files or constants.

<% Html.BeginForm("MyAction", "MyController", FormMethod.Post); %>
<input type="submit" name="send" value="Send" />
<input type="submit" name="cancel" value="Cancel" />
<input type="submit" name="draft" value="Save as draft" />
<% Html.EndForm(); %>`

On the receiving end you would only need to check if any of your known submit types isn't null

public ActionResult YourAction(YourModel model) {

    if(Request["send"] != null) {

        // we got a send

    }else if(Request["cancel"]) {

        // we got a cancel, but would you really want to post data for this?

    }else if(Request["draft"]) {

        // we got a draft

    }

}
Tom Hofman
  • 518
  • 5
  • 19
  • This is the solution we chose to use for a simple web app where we wanted ASP.NET WebForms functionality but within MVC. – BrandonG Sep 14 '16 at 20:17
11

Something I don't like about ActionSelectName is that IsValidName is called for every action method in the controller; I don't know why it works this way. I like a solution where every button has a different name based on what it does, but I don't like the fact that you have to have as many parameters in the action method as buttons in the form. I have created an enum for all button types:

public enum ButtonType
{
    Submit,
    Cancel,
    Delete
}

Instead of ActionSelectName, I use an ActionFilter:

public class MultipleButtonsEnumAttribute : ActionFilterAttribute
{
    public Type EnumType { get; set; }

    public MultipleButtonsEnumAttribute(Type enumType)
    {
        EnumType = enumType;
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        foreach (var key in filterContext.HttpContext.Request.Form.AllKeys)
        {
            if (Enum.IsDefined(EnumType, key))
            {
                var pDesc = filterContext.ActionDescriptor.GetParameters()
                    .FirstOrDefault(x => x.ParameterType == EnumType);
                filterContext.ActionParameters[pDesc.ParameterName] = Enum.Parse(EnumType, key);
                break;
            }
        }
    }
}

The filter will find the button name in the form data and if the button name matches any of the button types defined in the enum, it will find the ButtonType parameter among the action parameters:

[MultipleButtonsEnumAttribute(typeof(ButtonType))]
public ActionResult Manage(ButtonType buttonPressed, ManageViewModel model)
{
    if (button == ButtonType.Cancel)
    {
        return RedirectToAction("Index", "Home");
    }
    //and so on
    return View(model)
}

and then in views, I can use:

<input type="submit" value="Button Cancel" name="@ButtonType.Cancel" />
<input type="submit" value="Button Submit" name="@ButtonType.Submit" />
Gordon Mackie JoanMiro
  • 3,499
  • 3
  • 34
  • 42
sanjuro
  • 1,611
  • 1
  • 21
  • 29
10

If your browser supports the attribute formaction for input buttons (IE 10+, not sure about other browsers) then the following should work:

@using (Html.BeginForm()){
    //put form inputs here

<input id="sendBtn" value="Send" type="submit" formaction="@Url.Action("Name Of Send Action")" />

<input id="cancelBtn" value="Cancel" type="submit" formaction="@Url.Action("Name of Cancel Action") />

}
user3361233
  • 101
  • 1
  • 2
  • 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. – Tom Hofman Apr 30 '14 at 12:10
10

Here is what works best for me:

<input type="submit" value="Delete" name="onDelete" />
<input type="submit" value="Save" name="onSave" />


public ActionResult Practice(MyModel model, string onSave, string onDelete)
{
    if (onDelete != null)
    {
        // Delete the object
        ...
        return EmptyResult();
    }

    // Save the object
    ...
    return EmptyResult();
}
Sergey
  • 240
  • 3
  • 4
9

If you do not have restrictions on the use of HTML 5, you can use the <button> tag with formaction Attribute:

<form action="demo_form.asp" method="get">
   First name: <input type="text" name="fname" /><br />
   Last name: <input type="text" name="lname" /><br />
   <button type="submit">Submit</button><br />
   <button type="submit" formaction="demo_admin.asp">Submit as admin</button>
</form>

Reference: http://www.w3schools.com/html5/att_button_formaction.asp

Acaz Souza
  • 8,311
  • 11
  • 54
  • 97
8

There are three ways by which you can solve the above issue

  1. HTML way
  2. Jquery way
  3. “ActionNameSelectorAttribute” way

Below is a video which summarizes all the three approaches in a demonstrative way.

https://www.facebook.com/shivprasad.koirala/videos/vb.100002224977742/809335512483940

HTML way :-

In the HTML way we need to create two forms and place the “Submit” button inside each of the forms. And every form’s action will point to different / respective actions. You can see the below code the first form is posting to “Action1” and the second form will post to “Action2” depending on which “Submit” button is clicked.

<form action="Action1" method=post>
<input type=”submit” name=”Submit1”/>
</form>

<form action="Action2" method=post>
<input type=”submit” name=”Submit2”>
</form>

Ajax way :-

In case you are a Ajax lover this second option would excite you more. In the Ajax way we can create two different functions “Fun1” and “Fun1” , see the below code. These functions will make Ajax calls by using JQUERY or any other framework. Each of these functions are binded with the “Submit” button’s “OnClick” events. Each of these function make call to respective action names.

<Script language="javascript">
function Fun1()
{
$.post(“/Action1”,null,CallBack1);
}
function Fun2()
{
$.post(“/Action2”,null,CallBack2);
}
</Script>

<form action="/Action1" method=post>
<input type=submit name=sub1 onclick=”Fun2()”/>
</form>
<form action="/Action2" method=post>
<input type=submit name=sub2 onclick=”Fun1()”/>
</form>

Using “ActionNameSelectorAttribute”:-

This is a great and a clean option. The “ActionNameSelectorAttribute” is a simple attribute class where we can write decision making logic which will decide which action can be executed.

So the first thing is in HTML we need to put proper name’s to the submit buttons for identifying them on the server.

You can see we have put “Save” and “Delete” to the button names. Also you can notice in the action we have just put controller name “Customer” and not a particular action name. We expect the action name will be decide by “ActionNameSelectorAttribute”.

<form action=”Customer” method=post>
<input type=submit value="Save" name="Save" /> <br />
<input type=submit value="Delete" name="Delete"/>
</form>

So when the submit button is clicked , it first hits the “ActionNameSelector” attribute and then depending on which submit is fired it invokes the appropriate action.

enter image description here

So the first step is to create a class which inherits from “ActionNameSelectorAttribute” class. In this class we have created a simple property “Name”.

We also need to override the “IsValidName” function which returns true or flase. This function is where we write the logic whether an action has to be executed or not. So if this function returns true then the action is executed or else it is not.

public class SubmitButtonSelector : ActionNameSelectorAttribute
    {
        public string Name { get; set; }
        public override bool IsValidName(ControllerContext controllerContext, string actionName, System.Reflection.MethodInfo methodInfo)
        {
            // Try to find out if the name exists in the data sent from form
var value = controllerContext.Controller.ValueProvider.GetValue(Name);
            if (value != null)
            {
                return true;
            }
            return false;

        }
    }

The main heart of the above function is in the below code. The “ValueProvider” collection has all the data that has been posted from the form. So it first looks up the “Name” value and if its found in the HTTP request it returns true or else it returns false.

var value = controllerContext.Controller.ValueProvider.GetValue(Name);
if (value != null)
      {
        return true;
      }
      return false;

This attribute class can then decorated on the respective action and the respective “Name” value can be provided. So if the submit is hitting this action and if the name matches of the HTML submit button name it then executes the action further or else it does not.

public class CustomerController : Controller
{
        [SubmitButtonSelector(Name="Save")]
        public ActionResult Save()
        {
            return Content("Save Called");
        }
        [SubmitButtonSelector(Name = "Delete")]
        public ActionResult Delete()
        {
            return Content("Delete Called");
        }
}
Shivprasad Koirala
  • 27,644
  • 7
  • 84
  • 73
7

This script allows to specify a data-form-action attribute which will work as the HTML5 formaction attribute in all browsers (in an unobtrusive way):

$(document).on('click', '[type="submit"][data-form-action]', function(event) {
    var $this = $(this),
    var formAction = $this.attr('data-form-action'),
    $form = $($this.closest('form'));
    $form.attr('action', formAction);             
});

The form containing the button will be posted to the URL specified in the data-form-action attribute:

<button type="submit" data-form-action="different/url">Submit</button>   

This requires jQuery 1.7. For previous versions you should use live() instead of on().

JotaBe
  • 38,030
  • 8
  • 98
  • 117
Jay Douglass
  • 4,828
  • 2
  • 27
  • 19
6

David Findley writes about 3 different options you have for doing this, on his ASP.Net weblog.

Read the article multiple buttons in the same form to see his solutions, and the advantages and disadvantages of each. IMHO he provides a very elegant solution which makes use of attributes that you decorate your action with.

Saajid Ismail
  • 8,029
  • 11
  • 48
  • 56
6

This is the technique I'd use and I don't see it here yet. The link (posted by Saajid Ismail ) that inspires this solution is http://weblogs.asp.net/dfindley/archive/2009/05/31/asp-net-mvc-multiple-buttons-in-the-same-form.aspx). It adapts Dylan Beattie's answer to do localization without any problems.

In the View:

<% Html.BeginForm("MyAction", "MyController", FormMethod.Post); %>
<button name="button" value="send"><%: Resources.Messages.Send %></button>
<button name="button" value="cancel"><%: Resources.Messages.Cancel %></button>
<% Html.EndForm(); %>

In the Controller:

public class MyController : Controller 
{
    public ActionResult MyAction(string button)
    {
         switch(button)
         {
             case "send":
                 this.DoSend();
                 break;
             case "cancel":
                 this.DoCancel();
                 break;
         }
    }
}
mlibby
  • 6,567
  • 1
  • 32
  • 41
  • Looks like the solution Ironicnet provided. – Kris van der Mast Nov 02 '10 at 15:02
  • 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. – mlibby Nov 03 '10 at 11:56
  • 1
    In fact, it's not the same as Ironicnet's beyond that. He uses `` elements. I use ` – mlibby Nov 03 '10 at 11:59
6
[HttpPost]
public ActionResult ConfirmMobile(string nameValueResend, string nameValueSubmit, RegisterModel model)
    {
        var button = nameValueResend ?? nameValueSubmit;
        if (button == "Resend")
        {

        }
        else
        {

        }
    }


    Razor file Content:
    @using (Html.BeginForm()
    {
        <div class="page registration-result-page">

            <div class="page-title">
                <h1> Confirm Mobile Number</h1>
            </div>

            <div class="result">
                @Html.EditorFor(model => model.VefificationCode)
                @Html.LabelFor(model => model.VefificationCode, new { })
                @Html.ValidationMessageFor(model => model.VefificationCode)
            </div>
            <div class="buttons">
                <button type="submit" class="btn" name="nameValueResend" value="Resend">
                    Resend
                </button>
                <button type="submit" class="btn" name="nameValueSubmit" value="Verify">
                    Submit
                </button>

            </div>
            </div>

    }
Mahendra
  • 447
  • 6
  • 7
  • This is another useful [link](http://www.binaryintellect.net/articles/c69d78a3-21d7-416b-9d10-6b812a862778.aspx) explaining different ways of form post. – Himalaya Garg Oct 21 '19 at 07:38
5

I don't have enough rep to comment in the correct place, but I spent all day on this so want to share.

While trying to implement the "MultipleButtonAttribute" solution ValueProvider.GetValue(keyValue) would incorrectly come back null.

It turned out I was referencing System.Web.MVC version 3.0 when it should have been 4.0 (other assemblies are 4.0). I don't know why my project didn't upgrade correctly and I had no other obvious problems.

So if your ActionNameSelectorAttribute is not working... check that.

lhan
  • 4,585
  • 11
  • 60
  • 105
HeatherD
  • 1,521
  • 1
  • 10
  • 7
5

I'm pretty late to the party, but here goes... My implementation borrows from @mkozicki but requires less hardcoded strings to get wrong. Framework 4.5+ required. Essentially, the controller method name should be the key to the routing.

Markup. The button name must be keyed with "action:[controllerMethodName]"

(notice the use of the C#6 nameof API, providing type-specific reference to the name of the controller method you wish to invoke.

<form>
    ... form fields ....
    <button name="action:@nameof(MyApp.Controllers.MyController.FundDeathStar)" type="submit" formmethod="post">Fund Death Star</button>
    <button name="action:@nameof(MyApp.Controllers.MyController.HireBoba)" type="submit" formmethod="post">Hire Boba Fett</button>
</form>

Controller:

namespace MyApp.Controllers
{
    class MyController
    {    
        [SubmitActionToThisMethod]
        public async Task<ActionResult> FundDeathStar(ImperialModel model)
        {
            await TrainStormTroopers();
            return View();
        }

        [SubmitActionToThisMethod]
        public async Task<ActionResult> HireBoba(ImperialModel model)
        {
            await RepairSlave1();
            return View();
        }
    }
}

Attribute Magic. Notice the use of CallerMemberName goodness.

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class SubmitActionToThisMethodAttribute : ActionNameSelectorAttribute
{        
    public SubmitActionToThisMethodAttribute([CallerMemberName]string ControllerMethodName = "")
    {
        controllerMethod = ControllerMethodName;
        actionFormat = string.Concat(actionConstant, ":", controllerMethod);
    }
    const string actionConstant = "action";
    readonly string actionFormat;
    readonly string controllerMethod;

    public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
    {
        var isValidName = false;
        var value = controllerContext.Controller.ValueProvider.GetValue(actionFormat);

        if (value != null)
        {
            controllerContext.Controller.ControllerContext.RouteData.Values[actionConstant] = controllerMethod;
            isValidName = true;
        }
        return isValidName;
    }
}
Aaron Hudon
  • 5,280
  • 4
  • 53
  • 60
  • 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. – ooXei1sh Jun 17 '16 at 21:15
  • The purpose of this solution is a work around to the MVC post routing. Please describe an improvement. – Aaron Hudon Jun 18 '16 at 04:03
5

Here's an extension method I wrote to handle multiple image and/or text buttons.

Here's the HTML for an image button:

<input id="btnJoin" name="Join" src="/content/images/buttons/btnJoin.png" 
       type="image">

or for a text submit button :

<input type="submit" class="ui-button green" name="Submit_Join" value="Add to cart"  />
<input type="submit" class="ui-button red" name="Submit_Skip" value="Not today"  />

Here is the extension method you call from the controller with form.GetSubmitButtonName(). For image buttons it looks for a form parameter with .x (which indicates an image button was clicked) and extracts the name. For regular input buttons it looks for a name beginning with Submit_ and extracts the command from afterwards. Because I'm abstracting away the logic of determining the 'command' you can switch between image + text buttons on the client without changing the server side code.

public static class FormCollectionExtensions
{
    public static string GetSubmitButtonName(this FormCollection formCollection)
    {
        return GetSubmitButtonName(formCollection, true);
    }

    public static string GetSubmitButtonName(this FormCollection formCollection, bool throwOnError)
    {
        var imageButton = formCollection.Keys.OfType<string>().Where(x => x.EndsWith(".x")).SingleOrDefault();
        var textButton = formCollection.Keys.OfType<string>().Where(x => x.StartsWith("Submit_")).SingleOrDefault();

        if (textButton != null)
        {
            return textButton.Substring("Submit_".Length);
        }

        // we got something like AddToCart.x
        if (imageButton != null)
        {
            return imageButton.Substring(0, imageButton.Length - 2);
        }

        if (throwOnError)
        {
            throw new ApplicationException("No button found");
        }
        else
        {
            return null;
        }
    }
}

Note: For text buttons you have to prefix the name with Submit_. I prefer this way becuase it means you can change the text (display) value without having to change the code. Unlike SELECT elements, an INPUT button has only a 'value' and no separate 'text' attribute. My buttons say different things under different contexts - but map to the same 'command'. I much prefer extracting the name this way than having to code for == "Add to cart".

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
  • 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. – Max Aug 26 '11 at 11:09
4

I've tried to make a synthesis of all solutions and created a [ButtenHandler] attribute that makes it easy to handle multiple buttons on a form.

I've described it on CodeProject Multiple parameterized (localizable) form buttons in ASP.NET MVC.

To handle the simple case of this button:

<button type="submit" name="AddDepartment">Add Department</button>

You'll have something like the following action method:

[ButtonHandler()]
public ActionResult AddDepartment(Company model)
{
    model.Departments.Add(new Department());
    return View(model);
}

Notice how the name of the button matches the name of the action method. The article also describes how to have buttons with values and buttons with indexes.

codetuner
  • 316
  • 3
  • 8
4
//model
    public class input_element
        {
         public string Btn { get; set; }
        }   

//views--submit btn can be input type also...
    @using (Html.BeginForm())
    {
            <button type="submit" name="btn" value="verify">
             Verify data</button>
            <button type="submit" name="btn" value="save">
             Save data</button>    
            <button type="submit" name="btn" value="redirect">
                 Redirect</button>
    }

//controller

    public ActionResult About()
        {
            ViewBag.Message = "Your app description page.";
            return View();
        }

        [HttpPost]
        public ActionResult About(input_element model)
        {
                if (model.Btn == "verify")
                {
                // the Verify button was clicked
                }
                else if (model.Btn == "save")
                {
                // the Save button was clicked
                } 
                else if (model.Btn == "redirect")
                {
                // the Redirect button was clicked
                } 
                return View();
        }
SHAURAJ SINGH
  • 517
  • 7
  • 20
  • You may not have realized it, but this same answer was posted quite a few times to this question already. – Andrew Barber Feb 26 '13 at 05:39
  • 2
    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 Feb 26 '13 at 05:41
  • 2
    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. – Andrew Barber Feb 26 '13 at 05:49
4

Modified version of HttpParamActionAttribute method but with a bug fix for not causing an error on expired/invalid session postbacks. To see if this is a problem with your current site, open the your form in a window and just before you go to click Save or Publish, open a duplicate window, and logout. Now go back to your first window and try to submit your form using either button. For me I got an error so this change solves that problem for me. I omit a bunch of stuff for the sake of brevity but you should get the idea. The key parts are the inclusion of ActionName on the attribute and making sure the name passed in is the name of the View that shows the form

Attribute Class

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class HttpParamActionAttribute : ActionNameSelectorAttribute
{
    private readonly string actionName;

    public HttpParamActionAttribute(string actionName)
    {
        this.actionName = actionName;
    }

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

        if (!actionName.Equals(this.actionName, StringComparison.InvariantCultureIgnoreCase))
            return false;

        var request = controllerContext.RequestContext.HttpContext.Request;
        return request[methodInfo.Name] != null;
    }
}

Controller

[Authorize(Roles="CanAddContent")]
public ActionResult CreateContent(Guid contentOwnerId)
{
    var viewModel = new ContentViewModel
    {
        ContentOwnerId = contentOwnerId
        //populate rest of view model
    }
    return View("CreateContent", viewModel);
}

[Authorize(Roles="CanAddContent"), HttpPost, HttpParamAction("CreateContent"), ValidateAntiForgeryToken]
public ActionResult SaveDraft(ContentFormModel model)
{
    //Save as draft
    return RedirectToAction("CreateContent");
}

[Authorize(Roles="CanAddContent"), HttpPost, HttpParamAction("CreateContent"), ValidateAntiForgeryToken]
public ActionResult Publish(ContentFormModel model)
{
    //publish content
    return RedirectToAction("CreateContent");
}

View

@using (Ajax.BeginForm("CreateContent", "MyController", new { contentOwnerId = Model.ContentOwnerId }))
{
    @Html.AntiForgeryToken()
    @Html.HiddenFor(x => x.ContentOwnerId)

    <!-- Rest of your form controls -->
    <input name="SaveDraft" type="submit" value="SaveDraft" />
    <input name="Publish" type="submit" value="Publish" />
}
Nick Albrecht
  • 16,607
  • 10
  • 66
  • 101
4

this is the best way that i have found:

http://iwayneo.blogspot.co.uk/2013/10/aspnet-mvc-action-selector-with-list.html

Here is the code:

    /// <summary>
    /// ActionMethodSelector to enable submit buttons to execute specific action methods.
    /// </summary>
    public class AcceptParameterAttribute : ActionMethodSelectorAttribute
   {
        /// <summary>
        /// Gets or sets the value to use to inject the index into
        /// </summary>
       public string TargetArgument { get; set; }

       /// <summary>
       /// Gets or sets the value to use in submit button to identify which method to select. This must be unique in each controller.
       /// </summary>
       public string Action { get; set; }

       /// <summary>
       /// Gets or sets the regular expression to match the action.
       /// </summary>
       public string ActionRegex { get; set; }

       /// <summary>
       /// Determines whether the action method selection is valid for the specified controller context.
       /// </summary>
       /// <param name="controllerContext">The controller context.</param>
       /// <param name="methodInfo">Information about the action method.</param>
       /// <returns>true if the action method selection is valid for the specified controller context; otherwise, false.</returns>
       public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
       {

           if (controllerContext == null)
           {
               throw new ArgumentNullException("controllerContext");
           }

           Func<NameValueCollection> formGetter;
           Func<NameValueCollection> queryStringGetter;

           ValidationUtility.GetUnvalidatedCollections(HttpContext.Current, out formGetter, out queryStringGetter);

           var form = formGetter();
           var queryString = queryStringGetter();

           var req = form.AllKeys.Any() ? form : queryString;

           if (!string.IsNullOrEmpty(this.ActionRegex))
           {
               foreach (var key in req.AllKeys.Where(k => k.StartsWith(Action, true, System.Threading.Thread.CurrentThread.CurrentCulture)))
               {
                   if (key.Contains(":"))
                   {
                       if (key.Split(':').Count() == this.ActionRegex.Split(':').Count())
                       {
                           bool match = false;
                           for (int i = 0; i < key.Split(':').Count(); i++)
                           {
                               if (Regex.IsMatch(key.Split(':')[0], this.ActionRegex.Split(':')[0]))
                               {
                                   match = true;
                               }
                               else
                               {
                                   match = false;
                                   break;
                               }
                           }

                           if (match)
                           {
                               return !string.IsNullOrEmpty(req[key]);
                           }
                       }
                   }
                   else
                   {
                       if (Regex.IsMatch(key, this.Action + this.ActionRegex))
                       {
                           return !string.IsNullOrEmpty(req[key]);
                       }
                   }

               }
               return false;
           }
           else
           {
               return req.AllKeys.Contains(this.Action);
           }
       }
   }

Enjoy a code-smell-less multi submit button future.

thank you

  • Link currently broken, this is the closest archived version I could find: http://web.archive.org/web/20110706230408/http://blogs.sonatribe.com/wayne/2011/06/15/171/ – Ian Kemp Oct 25 '13 at 15:09
  • Hi Ian - thanks for digging that out - I've reposted it here: http://iwayneo.blogspot.co.uk/2013/10/aspnet-mvc-action-selector-with-list.html –  Oct 28 '13 at 10:57
3

Based on mkozicki answer I come up with a bit different solution. I still use ActionNameSelectorAttribute But I needed to handle two buttons 'Save' and 'Sync'. They do almost the same so I didn't want to have two actions.

attribute:

public class MultipleButtonActionAttribute : ActionNameSelectorAttribute
{        
    private readonly List<string> AcceptedButtonNames;

    public MultipleButtonActionAttribute(params string[] acceptedButtonNames)
    {
        AcceptedButtonNames = acceptedButtonNames.ToList();
    }

    public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
    {            
        foreach (var acceptedButtonName in AcceptedButtonNames)
        {
            var button = controllerContext.Controller.ValueProvider.GetValue(acceptedButtonName);
            if (button == null)
            {
                continue;
            }                
            controllerContext.Controller.ControllerContext.RouteData.Values.Add("ButtonName", acceptedButtonName);
            return true;
        }
        return false;
    }
}

view

<input type="submit" value="Save" name="Save" />
<input type="submit" value="Save and Sync" name="Sync" />

controller

 [MultipleButtonAction("Save", "Sync")]
 public ActionResult Sync(OrgSynchronizationEditModel model)
 {
     var btn = this.RouteData.Values["ButtonName"];

I also want to point out that if actions do different things I would probably follow mkozicki post.

Petr J
  • 182
  • 4
  • 12
2

For each submit button just add:

$('#btnSelector').click(function () {

    $('form').attr('action', "/Your/Action/);
    $('form').submit();

});
mishap
  • 8,176
  • 14
  • 61
  • 92
2

My JQuery approach using an extension method:

public static MvcHtmlString SubmitButtonFor<TController>(this HtmlHelper helper, Expression<Action<TController>> action, string value) where TController : Controller
{
    RouteValueDictionary routingValues = Microsoft.Web.Mvc.Internal.ExpressionHelper.GetRouteValuesFromExpression(action);

    var onclick = string.Format("$('form').first().attr('action', '/{0}')", string.Join("/", routingValues.Values.ToArray().Where(x => x != null).Select(x => x.ToString()).ToArray()));
    var html = "<input type=\"submit\" value=\"" + value + "\" onclick=\"" + onclick + "\" />";

    return MvcHtmlString.Create(html);
}

You can use it like this:

@(Html.SubmitButtonFor<FooController>(c => c.Save(null), "Save"))

And it renders like this:

<input type="submit" value="Save" onclick="$('form').first().attr('action', '/Foo/Save')" >
NahuelGQ
  • 178
  • 1
  • 1
  • 10
1

I've created an ActionButton method for the HtmlHelper. It will generate normal input button with a bit of javascript in the OnClick event that will submit the form to the specified Controller/Action.

You use the helper like that

@Html.ActionButton("MyControllerName", "MyActionName", "button text")

this will generate the following HTML

<input type="button" value="button text" onclick="this.form.action = '/MyWebsiteFolder/MyControllerName/MyActionName'; this.form.submit();">

Here is the extension method code:

VB.Net

<System.Runtime.CompilerServices.Extension()>
Function ActionButton(pHtml As HtmlHelper, pAction As String, pController As String, pRouteValues As Object, pBtnValue As String, pBtnName As String, pBtnID As String) As MvcHtmlString
    Dim urlHelperForActionLink As UrlHelper
    Dim btnTagBuilder As TagBuilder

    Dim actionLink As String
    Dim onClickEventJavascript As String

    urlHelperForActionLink = New UrlHelper(pHtml.ViewContext.RequestContext)
    If pController <> "" Then
        actionLink = urlHelperForActionLink.Action(pAction, pController, pRouteValues)
    Else
        actionLink = urlHelperForActionLink.Action(pAction, pRouteValues)
    End If
    onClickEventJavascript = "this.form.action = '" & actionLink & "'; this.form.submit();"

    btnTagBuilder = New TagBuilder("input")
    btnTagBuilder.MergeAttribute("type", "button")

    btnTagBuilder.MergeAttribute("onClick", onClickEventJavascript)

    If pBtnValue <> "" Then btnTagBuilder.MergeAttribute("value", pBtnValue)
    If pBtnName <> "" Then btnTagBuilder.MergeAttribute("name", pBtnName)
    If pBtnID <> "" Then btnTagBuilder.MergeAttribute("id", pBtnID)

    Return MvcHtmlString.Create(btnTagBuilder.ToString(TagRenderMode.Normal))
End Function

C# (the C# code is just decompiled from the VB DLL, so it can get some beautification... but time is so short :-))

public static MvcHtmlString ActionButton(this HtmlHelper pHtml, string pAction, string pController, object pRouteValues, string pBtnValue, string pBtnName, string pBtnID)
{
    UrlHelper urlHelperForActionLink = new UrlHelper(pHtml.ViewContext.RequestContext);
    bool flag = Operators.CompareString(pController, "", true) != 0;
    string actionLink;
    if (flag)
    {
        actionLink = urlHelperForActionLink.Action(pAction, pController, System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValue(pRouteValues));
    }
    else
    {
        actionLink = urlHelperForActionLink.Action(pAction, System.Runtime.CompilerServices.RuntimeHelpers.GetObjectValue(pRouteValues));
    }
    string onClickEventJavascript = "this.form.action = '" + actionLink + "'; this.form.submit();";
    TagBuilder btnTagBuilder = new TagBuilder("input");
    btnTagBuilder.MergeAttribute("type", "button");
    btnTagBuilder.MergeAttribute("onClick", onClickEventJavascript);
    flag = (Operators.CompareString(pBtnValue, "", true) != 0);
    if (flag)
    {
        btnTagBuilder.MergeAttribute("value", pBtnValue);
    }
    flag = (Operators.CompareString(pBtnName, "", true) != 0);
    if (flag)
    {
        btnTagBuilder.MergeAttribute("name", pBtnName);
    }
    flag = (Operators.CompareString(pBtnID, "", true) != 0);
    if (flag)
    {
        btnTagBuilder.MergeAttribute("id", pBtnID);
    }
    return MvcHtmlString.Create(btnTagBuilder.ToString(TagRenderMode.Normal));
}

These methods have various parameters, but for the ease of use you can create some overload that take just the parameters you need.

Max
  • 7,408
  • 2
  • 26
  • 32
0

When using ajax forms, we can use ActionLinks with POST HttpMethod and serialize the form in the AjaxOptions.OnBegin event.

Let's say you have two actions, InsertAction and UpdateAction:

<form>
    @Html.Hidden("SomeField", "SomeValue")

    @Ajax.ActionLink(
        "Insert",
        "InsertAction",
        null,
        new AjaxOptions { 
            OnBegin = "OnBegin", 
            UpdateTargetId = "yourDiv", 
            HttpMethod = "POST" })
    @Ajax.ActionLink(
        "Update",
        "UpdateAction",
        null,
        new AjaxOptions { 
            OnBegin = "OnBegin", 
            UpdateTargetId = "yourDiv", 
            HttpMethod = "POST" })
</form>

Javascript

function OnBegin(xhr, settings) {
    settings.data = $("form").serialize();
}
jBelanger
  • 1,526
  • 18
  • 11
0

Use a custom helper(create a file "Helpers.cshtml" inside the App_Code folder, at the root of your project) with javascript to rewrite (at an 'onclick' event) the form's 'action' attribute to something you want and then submit it.

The helper could be like:

@helper SubmitButton(string text, string controller,string action)
{   
    var uh = new System.Web.Mvc.UrlHelper(Context.Request.RequestContext);
    string url = @uh.Action(action, controller, null);   
    <input type=button  onclick="(
                                       function(e)
                                                 {
                                                   $(e).parent().attr('action', '@url'); //rewrite action url
                                                   //create a submit button to be clicked and removed, so that onsubmit is triggered
                                                   var form = document.getElementById($(e).parent().attr('id'));
                                                   var button = form.ownerDocument.createElement('input');
                                                   button.style.display = 'none';
                                                   button.type = 'submit';
                                                   form.appendChild(button).click(); 
                                                   form.removeChild(button);              
                                                  }
                                      )(this)" value="@text"/>
}

And then use it as:

@Helpers.SubmitButton("Text for 1st button","ControllerForButton1","ActionForButton1")
@Helpers.SubmitButton("Text for 2nd button","ControllerForButton2","ActionForButton2")
...

Inside your form.

galmeida
  • 221
  • 2
  • 10
-1

In View the code is:

<script language="javascript" type="text/javascript">
    function SubmeterForm(id)
    {
        if (id=='btnOk')
            document.forms[0].submit(id);
        else
            document.forms[1].submit(id);
    }
</script>


<% Html.BeginForm("ObterNomeBotaoClicado/1", "Teste01", FormMethod.Post); %>
<input id="btnOk" type="button" value="Ok" onclick="javascript:SubmeterForm('btnOk')" />
<% Html.EndForm(); %>
<% Html.BeginForm("ObterNomeBotaoClicado/2", "Teste01", FormMethod.Post); %>
<input id="btnCancelar" type="button" value="Cancelar" onclick="javascript:SubmeterForm('btnCancelar')" />
<% Html.EndForm(); %>

In Controller the code is:

public ActionResult ObterNomeBotaoClicado(string id)
{
    if (id=="1")
        btnOkFunction(...);
    else
        btnCancelarFunction(...);
    return View();
}
Mario S
  • 11,715
  • 24
  • 39
  • 47
Eduardo
  • 1
  • 1
  • In the future, it is helpful for you to explain your code. Thank you! –  Mar 16 '14 at 07:12
-3

Index.cshtml

@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { id = "submitForm" }))

{

button type="submit" id="btnApprove" name="Command" value="Approve"> Approve

button type="submit" id="btnReject" name="Command" value="Reject"> Reject

}

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public ActionResult Index(FormCollection frm, string Command)
    {
        if (Command == "Approve")
        {

        }
        else if (Command == "Reject")
        {

        }

        return View();
    }

}
Vignesh Raja
  • 560
  • 6
  • 10
  • 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. – misterbee180 Sep 27 '21 at 12:19
-3

My Solution was to use 2 asp panels:

<asp:Panel ID=”..” DefaultButton=”ID_OF_SHIPPING_SUBMIT_BUTTON”….></asp:Panel>

PUG
  • 4,301
  • 13
  • 73
  • 115