Is there some easy way to handle multiple submit buttons from the same form? For example:
<% Html.BeginForm(\"MyAction\", \"MyController\", FormMethod.Pos
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") />
}
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"
.
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"...
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.
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();
}
For each submit button just add:
$('#btnSelector').click(function () {
$('form').attr('action', "/Your/Action/);
$('form').submit();
});