如何处理 ASP.NET MVC Framework 中的多个提交按钮?

Is there some easy way to handle multiple submit buttons from the same form? 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.

转载于:https://stackoverflow.com/questions/442704/how-do-you-handle-multiple-submit-buttons-in-asp-net-mvc-framework

csdnceshi69
YaoRaoLov Worth mentioning starting from ASP.NET Core there are much easier solutions around than the ones listed here.
3 年多之前 回复

23个回答

Here is a mostly clean attribute-based solution to the multiple submit button issue based heavily on the post and comments from Maartin 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.

csdnceshi73
喵-见缝插针 I think adding a parameter to the post action named "submit" is easier than this. Check this binaryintellect.net/articles/…
大约 3 年之前 回复
csdnceshi77
狐狸.fox worked great , thanks
大约 3 年之前 回复
csdnceshi80
胖鸭 doesnt work for me, dont know why it always send me into index controller
3 年多之前 回复
csdnceshi71
Memor.の hello @mkozicki.. can u provide me demo link for this
3 年多之前 回复
csdnceshi60
℡Wang Yan var value = controllerContext.Controller.ValueProvider.GetValue(keyValue) is always returning null.
接近 4 年之前 回复
csdnceshi60
℡Wang Yan 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!
接近 4 年之前 回复
weixin_41568184
叼花硬汉 Many many thanks, it works fine, a great help for me.
接近 4 年之前 回复
csdnceshi66
必承其重 | 欲带皇冠 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.
大约 4 年之前 回复
csdnceshi56
lrony* 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?
大约 4 年之前 回复
weixin_41568131
10.24 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.
大约 4 年之前 回复
csdnceshi66
必承其重 | 欲带皇冠 This may help: stackoverflow.com/questions/25015833/…
大约 4 年之前 回复
csdnceshi79
python小菜 It works till I call redirect from action... I got error "Child actions are not allowed to perform redirect actions"
大约 4 年之前 回复
csdnceshi53
Lotus@ 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.
4 年多之前 回复
csdnceshi66
必承其重 | 欲带皇冠 I was unable to reproduce your issue in a new project.
4 年多之前 回复
csdnceshi66
必承其重 | 欲带皇冠 Make sure you have the following using statements using System; using System.Reflection; using System.Web.Mvc;
4 年多之前 回复
csdnceshi53
Lotus@ I'm using MVC 5.2.3, it is working in IE11 but in Chrome buttons are not comming in FormValueProvider. :(
4 年多之前 回复
csdnceshi76
斗士狗 Is this still valid in ASP.net 5 MVC 6? I am getting error for ActionNameSelectorAttribute not found when used in ASP.net 5.
4 年多之前 回复
csdnceshi55
~Onlooker Works very well in both IE and Chrome
4 年多之前 回复
csdnceshi66
必承其重 | 欲带皇冠 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.
4 年多之前 回复
csdnceshi58
Didn"t forge i tried in IE, it didn't work. I'm getting 404 error, but it works in chrome. Any thoughts?
4 年多之前 回复
csdnceshi74
7*4 Works like a hot damn in MVC 5.2.3 Cheers.
接近 5 年之前 回复
weixin_41568134
MAO-EYE 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?
大约 5 年之前 回复
csdnceshi57
perhaps? Why controllerContext.Controller.ControllerContext.RouteData? Isn't that the same as just controllerContext.RouteData?
大约 5 年之前 回复
csdnceshi61
derek5. just an information, we need to add system.Reflection for MethodInfo
5 年多之前 回复
weixin_41568174
from.. 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.
5 年多之前 回复
weixin_41568183
零零乙 Doesn't work for me. Wasted a lot of time playing with this. Comes back with nothing but it calls getvalue.
5 年多之前 回复
csdnceshi54
hurriedly% 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.
6 年多之前 回复
csdnceshi66
必承其重 | 欲带皇冠 Answers to your question here can help you with this.
6 年多之前 回复
weixin_41568208
北城已荒凉 It's not working with remote validation. Please any help for handling multiple buttons with remote validation.
6 年多之前 回复
csdnceshi67
bug^君 - 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)
6 年多之前 回复
weixin_41568196
撒拉嘿哟木头 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.
接近 7 年之前 回复
csdnceshi72
谁还没个明天 Yeaaaaaaaa. Works on MVC 4.5. The other dont seem to work. great +1
大约 7 年之前 回复
csdnceshi63
elliott.david Me likey nice and clean going to have to add this one to my toolbox
7 年多之前 回复
weixin_41568127
?yb? 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..
7 年多之前 回复
csdnceshi66
必承其重 | 欲带皇冠 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).
7 年多之前 回复
weixin_41568127
?yb? 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?
7 年多之前 回复
csdnceshi75
衫裤跑路 what is the line 'value = new ValueProviderResult(Argument, Argument, null);' used for? It seems redundant.
7 年多之前 回复
csdnceshi52
妄徒之命 Great solution!! Much nore useful than those top scored answers
8 年多之前 回复
weixin_41568126
乱世@小熊 I found this solution to be the happy marriage of the other techniques used. Works perfectly and doesn't effect localization.
8 年多之前 回复

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());
}
csdnceshi80
胖鸭 Is there a good way to pass in an ID as well ( delete button with ID ) ?
4 年多之前 回复
weixin_41568174
from.. -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...
5 年多之前 回复
csdnceshi57
perhaps? This approach is so much easier than the crazy custom-attribute stuff. KISS principle for the win.
5 年多之前 回复
csdnceshi63
elliott.david To answer from @bizzehdee, just pass the model as first param and the submit button name as the second param.
5 年多之前 回复
weixin_41568196
撒拉嘿哟木头 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.
接近 6 年之前 回复
csdnceshi67
bug^君 see also stackoverflow.com/questions/4171664/… for how to create a button where the text can differ from the submitted value
6 年多之前 回复
csdnceshi60
℡Wang Yan How would this work with passing the model to the action instead of just the submit value?
大约 7 年之前 回复
csdnceshi56
lrony* What about capturing the entire model in the controller as well in addition to the submit button (string submitButton)?
7 年多之前 回复
csdnceshi66
必承其重 | 欲带皇冠 interesting solution. The site was down when I went there, but wayback had it: web.archive.org/web/20110312212221/http://blogs.sonatribe.com/
7 年多之前 回复
csdnceshi62
csdnceshi62 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>
大约 8 年之前 回复
weixin_41568183
零零乙 - 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.
大约 8 年之前 回复
csdnceshi72
谁还没个明天 : What if I want to pass the Model as parameter to my action method? In that case can I still retrieve the button name?
大约 8 年之前 回复
csdnceshi75
衫裤跑路 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
8 年多之前 回复
weixin_41568134
MAO-EYE What about form validation? Some buttons may not require full form validation.
大约 9 年之前 回复
weixin_41568183
零零乙 - you're absolutely right. Code has been edited to use if/else instead of switch() for the localized version. Thanks.
接近 10 年之前 回复
csdnceshi52
妄徒之命 Have a look at the solution from Izmoto using MultiButton attribute, seems very nice.
接近 10 年之前 回复
weixin_41568208
北城已荒凉 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.
接近 10 年之前 回复
weixin_41568183
零零乙 Omu - see latest edit, which explains (briefly!) how to do the same thing with localized string/message resources.
10 年多之前 回复
weixin_41568131
10.24 too bad you depend on the text displayed on the button, it's kinda tricky with a multilanguage user interface
10 年多之前 回复
csdnceshi61
derek5. Just what I needed ... thanks
10 年多之前 回复
csdnceshi77
狐狸.fox This is what I was looking for here: stackoverflow.com/questions/649513/… - thanks
11 年多之前 回复

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") %>
csdnceshi77
狐狸.fox 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.
大约 4 年之前 回复
csdnceshi58
Didn"t forge 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...
大约 7 年之前 回复
csdnceshi68
local-host seriously? does that not smell to anyone but me?!
8 年多之前 回复
csdnceshi50
三生石@ 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.
接近 9 年之前 回复
csdnceshi75
衫裤跑路 you can position your buttons with CSS and they can still reside in 2 different form sections.
9 年多之前 回复
csdnceshi75
衫裤跑路 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.
9 年多之前 回复
csdnceshi60
℡Wang Yan About visual presentation, how in this case have the "Send" button next to the "Cancel" button ?
9 年多之前 回复
weixin_41568127
?yb? 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?
接近 11 年之前 回复

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".

csdnceshi77
狐狸.fox 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.
接近 9 年之前 回复

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.

//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();
        }
csdnceshi50
三生石@ 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.
7 年多之前 回复
csdnceshi54
hurriedly% i have tested this and its working fine..
7 年多之前 回复
csdnceshi50
三生石@ 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.
7 年多之前 回复
csdnceshi50
三生石@ You may not have realized it, but this same answer was posted quite a few times to this question already.
7 年多之前 回复

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.

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.

csdnceshi75
衫裤跑路 Much more simpler than the top answer! Thanks!
接近 2 年之前 回复
csdnceshi73
喵-见缝插针 Nice, simple and gets the job done.
接近 3 年之前 回复
csdnceshi65
larry*wei Awesome. exactly what I used to do in webforms. Cheers buddy
大约 3 年之前 回复

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.

weixin_41568134
MAO-EYE By far the cleanest, and easiest, solution I've seen.
5 年多之前 回复

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

weixin_41568127
?yb? Hi Ian - thanks for digging that out - I've reposted it here: iwayneo.blogspot.co.uk/2013/10/…
接近 7 年之前 回复
csdnceshi66
必承其重 | 欲带皇冠 Link currently broken, this is the closest archived version I could find: web.archive.org/web/20110706230408/http://blogs.sonatribe.com/
接近 7 年之前 回复
共23条数据 1 3 尾页
Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问