ASP.NET

Handle multiple submit buttons in one ASP.NET MVC form

ASP.NET · · Published 2026-09-07

An edit form with Save draft and Publish, or a wizard with Back and Next, has several submit buttons in one <form>. In Web Forms each button gets its own event handler; in MVC the form posts to one URL, so the action has to find out which button was used.

1. Name the buttons and bind the value

A submit button with a name sends its name=value pair, and only the clicked button does so:

@model ArticleEditModel
@using (Html.BeginForm("Edit", "Articles", FormMethod.Post))
{
    @Html.AntiForgeryToken()
    @Html.HiddenFor(m => m.Id)
    @Html.EditorFor(m => m.Title)
    @Html.TextAreaFor(m => m.Body)

    <button type="submit" name="command" value="draft">Save draft</button>
    <button type="submit" name="command" value="publish">Publish</button>
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(ArticleEditModel model, string command)
{
    if (!ModelState.IsValid) return View(model);

    switch (command)
    {
        case "publish":
            _articles.Publish(model);
            return RedirectToAction("Details", new { id = model.Id });
        case "draft":
            _articles.SaveDraft(model);
            TempData["Message"] = "Draft saved.";
            return RedirectToAction("Edit", new { id = model.Id });
        default:
            return new HttpStatusCodeResult(400); // unknown or missing button
    }
}

Use <button> with a separate value rather than <input type="submit" value="Publish">: the label can then change or be translated without breaking the code. If the user submits by pressing Enter in a text field, browsers use the first submit button in the form as the "clicked" one, so put the safest action first.

2. One action per button with formaction

HTML5's formaction attribute sends the form to a different URL depending on the button:

<button type="submit" formaction="@Url.Action("SaveDraft", "Articles")">Save draft</button>
<button type="submit" formaction="@Url.Action("Publish", "Articles")">Publish</button>
[HttpPost, ValidateAntiForgeryToken]
public ActionResult SaveDraft(ArticleEditModel model) { /* ... */ }

[HttpPost, ValidateAntiForgeryToken]
public ActionResult Publish(ArticleEditModel model) { /* ... */ }

Each action stays small and has its own authorisation attributes, which is useful when only editors may publish. formnovalidate on a button (for example Cancel) skips the browser's built-in validation for that button only.

3. A selector attribute

For many forms with the same pattern, an ActionNameSelectorAttribute picks the action whose name matches the posted button name:

public class SubmitButtonAttribute : ActionNameSelectorAttribute
{
    public override bool IsValidName(ControllerContext context, string actionName, MethodInfo methodInfo)
    {
        return context.HttpContext.Request.Form["action:" + methodInfo.Name] != null;
    }
}

// View: <button type="submit" name="action:Publish">Publish</button>
[HttpPost, ValidateAntiForgeryToken, SubmitButton, ActionName("Edit")]
public ActionResult Publish(ArticleEditModel model) { /* ... */ }

This is clever but less obvious to the next developer; the first two options are usually enough.

Validation per button

Server-side validation runs on the bound model whatever the button. If a draft may be incomplete while publishing requires every field, validate conditionally: remove errors for optional fields with ModelState.Remove("Body") in the draft branch, or use separate view models per action with option 2.

ASP.NET Core

The same three approaches work in ASP.NET Core MVC. Razor Pages adds named handlers: <button asp-page-handler="Publish"> calls OnPostPublishAsync. The programming models are compared in The three ASP.NET programming models.

Official documentation