Click here to Skip to main content
14,211,730 members
Click here to Skip to main content
Posted 23 Sep 2012

Tagged as


26 bookmarked

Multiple parameterized (localizable) form buttons in ASP.NET MVC

Rate this:
4.45 (7 votes)
Please Sign up or sign in to vote.
4.45 (7 votes)
27 Sep 2012     CPOL    
Easy handling of buttons in ASP.NET MVC


One of the common problems with ASP.NET MVC is the handling of buttons on a form when multiple 'submit' buttons are present. Take for instance the following demonstration form (from the enclosed sample app):

Every button posts the form so that the information filled in does not get lost. But every button should have a different effect. In addition, some buttons are repeated per element in collections. For instance, there is an “Add Employee” button for each department, and an “+50” button for each employee.

Several solutions exist to handling multiple buttons on a form, see:

However, I found none of the existing solutions to be sufficiently complete to handle the cases I wanted to handle in a clean way.

The features of the button handler I here present include:

  • Simple and intuitive HTML-code in the view
  • Simple attribute based controller action method selection
  • Supports button with values
  • Supports indexing of buttons (for button per array/collection element)
  • No localization issues
  • Fully javascript/JQuery free

Some examples

A simple button

For a first example, let’s see how the HTML/Razor code looks like for a simple button as the “Add Department” button at the bottom of the above screen dump:

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

Or, using the INPUT element:

<input type="submit" name="AddDepartment" value="Add Department" />

The code in the MVC controller to handle this button is the following:

public ActionResult AddDepartment(Company model)
    model.Departments.Add(new Department());
    return View(model);

It is a regular action method returning an ActionResult. The differences are that the method has a [ButtonHandler] attribute, and that the name of the method does not match the action (the post action in this sample is “Index”), but matches the button name!

However, if you like, you could mention the action name, either by using an ActionName attribute, or by setting the ActionName property of the ButtonHandler. You could also explicitely set the ButtonName property, in which case the name of the method does not matter any more.

Following are valid alternatives of the above ButtonHandler attribute:

[ActionName("Index"), ButtonHandler()]
[ActionName("Index"), ButtonHandler(ButtonName = "AddDepartment")]
[ButtonHandler(ActionName = "Index")]
[ButtonHandler(ActionName = "Index", ButtonName = "AddDepartment")]

So the [ButtonHandler] attribute is used to mark the action method that will handle a button action.

Buttons with values

Now let’s take a look at the company budget buttons:

It would have been possible to create two distinct buttons with distinct names and distinct button handler action methods. But in this case I’ve solved this differently. The HTML/Razor code is the following:

<label>Remaining budget of the company :</label>
@Html.EditorFor(m => m.Budget, @readonly)
<button type="submit" name="UpdateCompanyBudget" value="100">Budget+100</button>
<button type="submit" name="UpdateCompanyBudget" value="-100">Budget-100</button>

As you can see, both buttons have the same name ! However, they also have a (different) value. This allows them to be handled by the same MVC controller action method, which looks like:

public ActionResult UpdateCompanyBudget(Company model, decimal value)
    // Increase the bonus budget by lowering the benefits of the shareholders:
    model.ShareHoldersBenefit -= value;
    model.Budget += value;
    return View(model);

Still we have a simple [ButtonHandler] attribute, and an action method of which the name matches the button name. In addition we have a ‘value’ argument. This value argument will contain the value of the button (100 or -100-).

The name of the argument (‘value’) is hardcoded but can be overwritten by using the ‘ValueArgumentName’ of the ButtonHandler property. For instance:

[ButtonHandler(ValueArgumentName = "amount")]
public ActionResult UpdateCompanyBudget(Company model, decimal amount) ...

Of course, you can still explicitely mention ActionName and/or ButtonName properties.

Having buttons with values helps in a better separation between controller code and view: the view can decide to add even more buttons with different values without impacting the controller code.

For the INPUT element in HTML, the value represents also the displayed button caption. Therefore, if you want to use INPUT elements instead of BUTTON elements, either consider button values as not supported, or accept that they will also be the caption of your button.

Indexed buttons

Another situation are buttons that are repeated per element in a collection or array. That is for instance the case of the “Delete” button to delete a department:

If you add multiple departments, you will have multiple instances of this delete button. How can we detect which instance of the button was deleted ?

We could of course have used the value of the button to hold an index value. That’s OK for a single loop level, but collections can be nested in which case a single index value is not sufficient.

ASP.Net MVC solves collection element indexing issue by adding index values within the names of the HTML input control elements. For instance, the rendering of the name of a department (knowing that the model contains a collection of departments) is done using the following Razor expression:

@Html.EditorFor(m => m.Departments[d].Name)

Where ‘d’ is the for-loop index of the department.

This translates in the rendered HTML into:

<input id="Departments_0__Name" name="Departments[0].Name" type="text" value="Los Angeles" />

The name of the input field contains an index number between square brackets. Well, we’ll use the same trick to identify the instance of our delete button:

<button type="submit" name="DeleteDepartment[@(d)]">Delete</button>

Or, using an INPUT element:

<input type="submit" name="DeleteDepartment[@(d)]" value="Delete" />

To handle this button, we need an action method named “DeleteDepartment”, that takes an argument for the button index. Here it is:

[ButtonHandler(ArgumentNames = "departmentId")]
public ActionResult DeleteDepartment(Company model, int departmentId)
    // Delete the given department:
    return View(model);

When our button has arguments, we need to declare the arguments with the ArgumentNames parameter (which takes a comma-separated list of argument names). These arguments will then be used to bind the real method arguments to.

It is possible to have multiple arguments (for nested loops for instance) combined with a value on the button. Let’s take a look at an example that combines nested loops and button values.

Multiple indexes and values combined

The “+50”, “+100”, “-50”, “-100” buttons on the above screen dump are an example of a combination of multiple indexes combined with values. On the controller side, a single action method handles all those buttons.

Let’s first take a look at a simplified version of the Razor view containing these buttons:

@for (int d = 0; d < Model.Departments.Count; d++)



    for(int e = 0; e < Model.Departments[d].Employees.Count; e++)


        @Html.EditorFor(m => m.Departments[d].Employees[e].Name)
        Assigned bonus :
        @Html.EditorFor(m => m.Departments[d].Employees[e].Bonus)
        <button type="submit" name="UpdateBonus[@(d)][@(e)]" value="50">+50</button>
        <button type="submit" name="UpdateBonus[@(d)][@(e)]" value="100">+100</button>
        <button type="submit" name="UpdateBonus[@(d)][@(e)]" value="-50">-50</button>
        <button type="submit" name="UpdateBonus[@(d)][@(e)]" value="-100">-100</button>

The four buttons are placed within a nested loop. Therefore, the button name takes two indexes. The rendered HTML will be similar to (for the first employee of the fourth department):

   <input id="Departments_3__Employees_0__Name"


    type="text" value="Lauren Walker" />
   Assigned bonus :
   <input id="Departments_3__Employees_0__Bonus"


    type="text" value="0,00" />
   <button type="submit" name="UpdateBonus[3][0]" value="50">+50</button>
   <button type="submit" name="UpdateBonus[3][0]" value="100">+100</button>
   <button type="submit" name="UpdateBonus[3][0]" value="-50">-50</button>
   <button type="submit" name="UpdateBonus[3][0]" value="-100">-100</button>

The controller action method to handle the UpdateBonus button is:

[ButtonHandler(ArgumentNames = "departmentId, employeeId")]
public ActionResult UpdateBonus(Company model, int departmentId, int employeeId, decimal value)
    // Increase the bonus of the employee by lowering his departments budget:
    model.Departments[departmentId].Budget -= value;
    model.Departments[departmentId].Employees[employeeId].Bonus += value;
    return View(model);

Our button handler takes two index arguments and a value argument (as well as the model argument to hold the form postback).

ButtonHandler reference

The ButtonHandler attribute has following properties:


The name of the MVC action. By default, the action name is not checked, only the button name is checked. Alternatively, you can use the [ActionName] attribute.


The name of the button to be handled. By default this is the name of the action method.


A comma-separated ordered list of argument names matching arguments of the action method.


The name of the action method argument to bind the button value to.


Whether the button handler accepts Http GET requests. By default, GET requests are not accepted.

ButtonHandler code

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web.Mvc;
namespace MvcMultiButtonSampleApp
    /// <summary>
    /// An MVC ActionName Selector for actions handling form buttons.
    /// </summary>
    public class ButtonHandlerAttribute : ActionNameSelectorAttribute
        private readonly Regex ButtonNameParser = new Regex("^(?<name>.*?)(\\[(?<arg>.+?)\\])*$",
            RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled);
        private string argumentNames;
        private string[] arguments;
        /// <summary>
        /// Indicates this action handles actions for a button with the name of the action method.
        /// </summary>
        public ButtonHandlerAttribute()
            this.ValueArgumentName = "value";
        /// <summary>
        /// Whether GET-requests are allowed (by default not allowed).
        /// </summary>
        public bool AllowGetRequests { get; set; }
        /// <summary>
        /// Name of the MVC action.
        /// </summary>
        public string ActionName { get; set; }
        /// <summary>
        /// Name of the button (without arguments).
        /// </summary>
        public string ButtonName { get; set; }
        /// <summary>
        /// Comma-separated list of argument names to bind to the button arguments.
        /// </summary>
        public string ArgumentNames
                return this.argumentNames;
                this.argumentNames = value;
                if (String.IsNullOrWhiteSpace(value))
                    this.arguments = null;
                    this.arguments = value.Split(',').Select(s => s.Trim()).ToArray();
        /// <summary>
        /// Name of the method argument to bind to the button value.
        /// </summary>
        public string ValueArgumentName { get; set; }
        /// <summary>
        /// Determines whether the action name is valid in the specified controller context.
        /// </summary>
        public override bool IsValidName(ControllerContext controllerContext, string actionName, System.Reflection.MethodInfo methodInfo)
            // Reject GET requests if not allowed:
            if (!AllowGetRequests)
                if (controllerContext.HttpContext.Request.GetHttpMethodOverride().Equals("GET", StringComparison.OrdinalIgnoreCase))
                    return false;
            // Check ActionName if given:
            if (this.ActionName != null)
                if (!this.ActionName.Equals(actionName, StringComparison.OrdinalIgnoreCase))
                    return false;
            // Check button name:
            var values = new NameValueCollection();
            if ((this.arguments == null) || (this.arguments.Length == 0))
                // Buttonname has no args, perform an exact match:
                var buttonName = this.ButtonName ?? methodInfo.Name;
                // Return false if button not found:
                if (controllerContext.HttpContext.Request[buttonName] == null)
                    return false;
                // Button is found, add button value:
                if (this.ValueArgumentName != null)
                    values.Add(this.ValueArgumentName, controllerContext.HttpContext.Request[buttonName]);
                // Buttonnname has arguments, perform a match up to the first argument:
                var buttonName = this.ButtonName ?? methodInfo.Name;
                var buttonNamePrefix = buttonName + "[";

                string buttonFieldname = null;
                string[] args = null;
                foreach (var fieldname in controllerContext.HttpContext.Request.Form.AllKeys
                    if (fieldname.StartsWith(buttonNamePrefix, StringComparison.OrdinalIgnoreCase))
                        var match = ButtonNameParser.Match(fieldname);
                        if (match == null) continue;
                        args = match.Groups["arg"].Captures.OfType<Capture>().Select(c => c.Value).ToArray();
                        if (args.Length != this.arguments.Length) continue;
                        buttonFieldname = fieldname;
                // Return false if button not found:
                if (buttonFieldname == null)
                    return false;
                // Button is found, add button value:
                if (this.ValueArgumentName != null)
                    values.Add(this.ValueArgumentName, controllerContext.HttpContext.Request[buttonFieldname]);
                // Also add arguments:
                for(int i=0; i<this.arguments.Length; i++)
                    values.Add(this.arguments[i], args[i]);
            // Install a new ValueProvider for the found values:
            var valueProviders = new List<IValueProvider>();
            valueProviders.Add(new NameValueCollectionValueProvider(values, Thread.CurrentThread.CurrentCulture));
            controllerContext.Controller.ValueProvider = new ValueProviderCollection(valueProviders);
            // Return success:
            return true;

The sample

The sample that comes with this article contains a simple MVC application to distribute budget over employee bonusses. When you start the application, the company has $1000 of shareholder benefit and $500 reserved for employee bonusses. When you increase that budget, the shareholder benefit is lowered. When you add budget to a department, that budget is taken from the companies budget. When you increase the bonus of an employee, that amount is taken from the departments budget.

It’s just a sample app to try out multiple functional buttons on a form.

There’s no backend database.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Rudi Breedenraedt
Architect RealDolmen
Belgium Belgium
Rudi is a Software Architect at RealDolmen.

Comments and Discussions

QuestionClean and scalable way Pin
dvorakpj25-Nov-13 0:28
memberdvorakpj25-Nov-13 0:28 
SuggestionHandling multiple form in mvc Pin
kmrmanish8625-Jul-13 20:39
memberkmrmanish8625-Jul-13 20:39 
GeneralMy vote of 1 Pin
martonx30-Sep-12 9:30
membermartonx30-Sep-12 9:30 
QuestionUse HTML link like button Pin
martonx30-Sep-12 9:29
membermartonx30-Sep-12 9:29 
AnswerRe: Use HTML link like button Pin
Rudi Breedenraedt30-Sep-12 15:04
memberRudi Breedenraedt30-Sep-12 15:04 
I was hoping/expecting someone to ask me why not just use some javascript. It is indeed 'in' to use javascript/jquery/whatever javascript library nowadays to enhance web application behavior, and it is absolutely a good idea as avoiding a roundtrip to the server can only be beneficial for the servers load and webapps responsivity. So, why do I use the server side ?

Microsoft ASP.NET MVC is here to support the Model-View-Controller pattern. This design pattern considers it good practice to have the model manipulation part separated in the controller, and the model rendering part in the view. Now, ASP.NET MVC is about an MVC implementation where controllers are written in C# or VB.NET classes that run server side.

The idea of the article was to illustrate how a multi-button form could be dealth with in an ASP.NET MVC application, thus having controllers sitting server side, and requiring server side roundtrips no matter what is possible with javascript.

You may have noticed that the example app I used is a dummy one, but not an innocent dummy one: almost every button implies some business logic (yes, simple additions and substractions can be business logic) and changes the model on several places. Question is, from an architectural point of view, where do you want to have your business logic and manipulate your model ?

My dummy example didn't, but a real world case may require access to databases or other services when a button is pressed. Of course you could also use Ajax to avoid a form post and have javascript do the required manipulations.

The idea of handling multiple buttons in an ASP.NET MVC application, is to have the buttons as triggers, but still the controller (which in ASP.NET MVC resides server side) as object that performs the model manipulations and business logic (well, the business logic should probably be a layer deeper but initiated by the MVC controller as the whole ASP.NET MVC concept is about the user interface tier, not the business or application tier anyway).

It would have been a different article with a different title if I explained how to handle multiple buttons/actions in an app build with a javascript MVC framework like 'Javascript MVC' or Knockout.js. In that case I would indeed probably choose a link element and some javascript. But then, the title of my article would be different too. And the article much shorter indeed.

So what about performance ? Having server-side controllers is absolutely less performant than client-side scripting. That's why so many javascript frameworks exist nowadays: it's damn fast but mixing the view and the logic can be a nightmare from a maintenance point of view. Question is, do you need that performance ? What trade-offs do you make ? Is ASP.NET MVC the right framework for you ?
GeneralRe: Use HTML link like button Pin
JackMC3-Jan-13 10:10
memberJackMC3-Jan-13 10:10 
GeneralMy vote of 5 Pin
Quentin in SA27-Sep-12 18:55
memberQuentin in SA27-Sep-12 18:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Layout: fixed | fluid

Article Copyright 2012 by Rudi Breedenraedt
Everything else Copyright © CodeProject, 1999-2019

Server Web01
Version 2.8.190619.2