Technology | Skill Level |
---|---|
ASP.NET | Beginner |
C# | Beginner |
MVC | Beginner |
This guide will demonstrate and explain how you can use ASP.NET MVC to easily create HTML pages with multiple submit buttons, using as little code as possible, and by leveraging the MVC default model binding and controller actions. No JavaScript is required for these techniques.
For developers looking for a quick answer, we will begin with examples of the code required in the Razor view and Controller for two multi-button techniques.
Next, we'll take a closer look at the implementation details, security considerations, and ways to pick the best technique.
This guide will focus on the ASP.NET MVC default model binding and controller actions. JavaScript techniques will be covered in a separate guide.
You should have:
If you want to try out the examples or see a complete working demonstration of both techniques, you can fork or download the accompanying sample project from GitHub.
There are two good ways to implement multiple submit buttons, focusing on just the essential code. More detailed information and more elements of the sample code follow.
Like the first method, this approach still uses multiple <input>
tags of type="submit"
, with the same 'name' attribute and different 'value' attributes.
It adds additional attributes to the <input>
tag that are implemented in HML5; formaction
and formmethod
.
Each button invokes a separate controller action different than the default controller action for the page.
Similar to the first technique, this approach uses multiple <input>
tags with the same name
attribute values and different values (text) for the value
attribute. It also adds two attributes which were implemented in HTML5:
formaction
- Specifies what action to take when the button is pressed.
formmethod
- Specifies whether this is an HttpPost or an HttpGet action.The value of the formaction
attribute is the name of the controller action to be called on the button push, either "TermsAccept" or "TermsDecline" in the example. See the Form Action section below for an explanation of why it's a good idea to use the Razor @Url.Action()
method to supply the URL.
The value of formmethod
should be "post" for HttpPost.
The value (text) of the value
attribute provides the text for the button, as in the first technique.
1<input type="submit" name="response" value="Accept" [email protected]("TermsAccept") formmethod="post" class="btn btn-primary" />
2<input type="submit" name="response" value="Decline" [email protected]("TermsDecline") formmethod="post" class="btn btn-default" />
As in the first technique, the Bootstrap styling is optional.
formaction
Attribute ValuesUsing the HTML5 formaction
attribute provides the ability to create separate controller actions for each button. Each of the submit buttons shown above has a controller action that corresponds to the formaction
value.
A parameter is not needed for the value
attribute of the buttons created on the page with the <input>
tags.
1[HttpPost]
2[ValidateAntiForgeryToken]
3public ActionResult TermsAccept([Bind(Include = "UserID,Username")] User user)
4{
5 if (ModelState.IsValid)
6 {
7 user.TermsAcceptedOn = DateTime.Now;
8 user.TermsStatus = "Accepted";
9 }
10 return View("Index", user);
11}
12
13[HttpPost]
14[ValidateAntiForgeryToken]
15public ActionResult TermsDecline([Bind(Include = "UserID,Username")] User user)
16{
17 if (ModelState.IsValid)
18 {
19 user.TermsStatus = "Declined";
20 }
21 return RedirectToAction("Index", "Home");
22}
This technique provides more control, flexibility, and separation of concerns than using the default controller action for the page does. Some of the capabilities which can be written specifically for the task include:
Note that this technique requires HTML 5 support, so it is not compatible with certain browsers. Any browser that has been updated since 2011 or 2012 supports this functionality. See caniuse.com for browser-specific information.
Also note that, in the example above, the method TermsDecline
binds the same fields in the User
object as TermsAccept
, but it doesn't have to. The second method could also be:
1[HttpPost]
2[ValidateAntiForgeryToken]
3public ActionResult TermsDecline()
4{
5 return RedirectToAction("Index", "Home");
6}
We can see that implementing a controller action for each button is not burdensome as one might expect.
Now that you have had the chance to peek under the hood, let's break it down even further.
These are two widely applicable techniques for implementing multiple submit buttons on web pages using the native capabilities of ASP.NET. Minimal code is required and none of it has to be JavaScript.
The following are some developer notes about various aspects of each technique.
This approach requires you to keep the value
attribute of each <input>
tag in sync with cases of the switch
statement in your controller. These values are case-sensitive when they are strings.
It also requires you to add a parameter to the controller method that has the same, case-sensitive, name as you give to the group of <input>
buttons.
Numbers are valid parameters: You can use numeric values for the value
parameter, in which case you won't be using double-quotes around the value. Your button text will be the number you've assigned to value
for each button, so this is a use case that's limited to collecting numeric pieces of information through button pushes (unless you have a reason for assigning ordinal values to buttons, which isn't very communicative in most situations). Keep in mind that if you're going to use numbers there are some constraints on the data types that are compatible with the switch statement.
This technique requires you to create separate controller actions for each button. The name of controller action must match the formaction
attribute for the associated <input>
tag. The value (text) of the value
attribute doesn't have to match anything, so it's easier to change the text for the <input>
button; no corresponding changes have to be made in the controller and the text on the button can be unrelated to the name of the controller action.
It's a good idea to use @Html.Action(controllerActionName)
to specify the name of the controller action where "controllerActionName" is the actual name of your controller action (like "TermsAccept" in the example above).
1<input type="submit" name="response" value="Decline" [email protected]("TermsAccept") formmethod="post" class="btn btn-default" />
Here's why, if you're interested in the details:
When you specify the value for the
formaction
attribute you're providing a URL, which can be a fully qualified path, likehref="http://www.example.com/formresult"
or a relative path likelogin/formresult
.In this technique you're specifying the path to the controller action. In the example above, it would be easy to just plug in the controller action name (
formaction="TermsAccept"
) and most of the time the relative path would resolve correctly.But when you're using the default page for an MVC route, like "index.cshtml" the relative route alone won't resolve correctly. The Razor HtmlHelper takes care of determining the correct route whether your current path is
/Terms
or/Terms/Index
.
On the server side, the controller methods that you write for each submit button should be decorated with [HttpPost]
and [ValidateAntiForgeryToken]
just like a default POST method would be.
Both techniques are compatible with the ASP.NET AntiForgeryToken, which helps prevent cross-site request forgery attacks. On the client side, be sure to use the @Html.AntiForgeryToken()
HTML Antiforgery Helper. Decorate the controller action with [ValidateAntiForgeryToken]
to implement the server side of this safeguard.
In both techniques, binding the controller action only to the elements of the data model you actually need to process helps reduce the surface area for attacks. You often only need to bind to a unique identifier and the data submitted on the form; data included in the model to display to the user doesn't need to be bound.
Technique 1 uses the default HttpPost controller action for the page no matter how many buttons are on the page, so your switch
statement should always include a default
case (see Default Controller Action). Your other cases will process only the buttons you've identified; a default action can respond to any exogenous inputs that might come your way.
Technique 2 is also compatible with HtmlHelper.Antiforgerytoken. By providing separate controller actions for each button, you can bind data fields more selectively and reduce the attack surface area associated with each action.
With separate methods, you can also isolate the code associated with each server response more completely. Error handling and redirects can also be specific to the action.
The unique features of each technique present relative advantages and disadvantages depending on the specific requirements for the code.
Technique 1 is likely to be the better choice when:
The appropriate text for the buttons provides a good value for evaluating which button is pushed (like "Accept" and "Decline").
The text for the button, and therefore the value
attribute of the <input>
tag, are unlikely to change.
The switch
statement, or other logic in the default POST controller, can be kept relatively short and simple (since it's a good idea to keep your controller logic short and simple).
The controller will have one return
statement for normal execution.
value
attribute, form data model, or both.On the other hand, Technique 2 is the better approach when:
The text for the buttons is long, contains special characters, is likely to change, or provides a poor set of values to evaluate in the controller.
The code in the controllers is dissimilar.
The buttons require different return actions from the controllers.
Data can be conveyed to the controller through the form's data model.
It's possible to create buttons with different names, in which case the HTML in the View looks like this:
1<input type="submit" name="save" value="Save" />
2<input type="submit" name="cancel" value="Cancel" />
And the default controller looks like this:
1public ActionResult ProcessForm(User user, string save, string cancel)
2{
3 if(!string.IsNullOrEmpty(save))
4 // Do stuff.
5 if (!string.IsNullOrEmpty(cancel))
6 // Do other stuff.
7 return View("SomePage", user);
8}
This is ugly, and it is likely to result in more code. In the end, you probably should avoid this approach. Although your buttons will have different values for the name
attribute, they will still have different value
attributes, and you'll have to create a controller method parameter for each button.
This might make sense if you have two buttons and their names are short, but their text is long, like:
name | value |
---|---|
save | Forward unto the next millennium! |
cancel | Cancel |
But long button text violates most user interface design guidelines, so you probably shouldn't be doing that, either.
Bleh.
You can implement multiple submit buttons in JavaScript. Pluralsight has excellent guides on learning JavaScript and creating buttons.
You can use Ajax also. However, in that scenario, you will need to implement something to prevent cross-site request forgery (CSRF) attacks.
Pluralsight has strong course offerings and complete learning tracks for Microsoft-based technologies, including ASP.NET MVC and C#. Some of the most relevant are:
Building Applications with ASP.NET MVC 4 by Scott Allen, updated 8 Nov 2012. Don't let the age of the course dissuade you: the fundamentals have changed little and this is a good launching point if you are new(ish) to MVC.
ASP.NET MVC 5 Fundamentals by Scott Allen, updated 5 Nov 2013. See especially Chapter 4, Bootstrap, for info on styling your form and its buttons.
Microsoft has made a start at a new generation of developer documentation at docs.microsoft.com and it includes a quick tutorial: Getting Started with ASP.NET MVC 5.
You can get a complete, ASP.NET MVC 5 project that corresponds to the examples in this guide from GitHub.