Form Data
Talk Back to your Server
Talk Back to your Server
Now that we have explored some ideas about getting data from the web server, let’s turn our attention to sending data to the webserver. One of the earliest approaches for doing so is to use a HTML form sent as a HTTP Request, which we’ll take a look at in this chapter.
Some key terms to learn in this chapter are:
Nullable<T>
and ?
And the HTML tags:
<form>
<input>
<textarea>
One of the earliest (and still widely used) mechanisms for transferring data from a browser (client) to the server is a form. The <form>
is a specific HTML element that contains input fields and buttons the user can interact with.
<input>
ElementPerhaps the most important - and versatile - of these is the <input>
element. By setting its type
attribute, we can represent a wide range of possible inputs, as is demonstrated by this table taken from the MDN Web Docs:
Type | Description | Basic Examples | |
---|---|---|---|
button | A push button with no default behavior displaying the value of the value attribute, empty by default. |
| |
checkbox | A check box allowing single values to be selected/deselected. |
| |
color | A control for specifying a color; opening a color picker when active in supporting browsers. |
| |
date | A control for entering a date (year, month, and day, with no time). Opens a date picker or numeric wheels for year, month, day when active in supporting browsers. |
| |
datetime-local | A control for entering a date and time, with no time zone. Opens a date picker or numeric wheels for date- and time-components when active in supporting browsers. |
| |
A field for editing an email address. Looks like a text input, but has validation parameters and relevant keyboard in supporting browsers and devices with dynamic keyboards. |
| ||
file | A control that lets the user select a file. Use the accept attribute to define the types of files that the control can select. |
| |
hidden | A control that is not displayed but whose value is submitted to the server. There is an example in the last column, but it's hidden! |
| ā Itās here! |
image | A graphical submit button. Displays an image defined by the src attribute. The alt attribute displays if the image src is missing. |
| }}"/> |
number | A control for entering a number. Displays a spinner and adds default validation when supported. Displays a numeric keypad in some devices with dynamic keypads. |
| |
password | A single-line text field whose value is obscured. Will alert user if site is not secure. |
| |
radio | A radio button, allowing a single value to be selected out of multiple choices with the same name value. |
| |
range | A control for entering a number whose exact value is not important. Displays as a range widget defaulting to the middle value. Used in conjunction min and max to define the range of acceptable values. |
| |
reset | A button that resets the contents of the form to default values. Not recommended. |
| |
search | A single-line text field for entering search strings. Line-breaks are automatically removed from the input value. May include a delete icon in supporting browsers that can be used to clear the field. Displays a search icon instead of enter key on some devices with dynamic keypads. |
| |
submit | A button that submits the form. |
| |
tel | A control for entering a telephone number. Displays a telephone keypad in some devices with dynamic keypads. |
| |
text | The default value. A single-line text field. Line-breaks are automatically removed from the input value. |
| |
time | A control for entering a time value with no time zone. |
| |
url | A field for entering a URL. Looks like a text input, but has validation parameters and relevant keyboard in supporting browsers and devices with dynamic keyboards. |
|
Regardless of the type, the <input>
element also has a name
and value
property. The name
is similar to a variable name, in that it is used to identify the input’s value when we serialize the form (more about that later), and the value
is the value the input currently is (this starts as the value you specify in the HTML, but it changes when the user edits it).
Additionally, checkboxes and radio buttons have a boolean ischecked
property. These indicate if the box/button is checked or not (and that the box/button’s value
should be submitted).
<textarea>
ElementThe <textarea>
element represents a multi-line text input. Similar to terminal programs, this is represented by columns and rows, the numbers of which are set by the cols
and rows
attributes, respectively. Thus:
<textarea cols=40 rows=5></textarea>
Would look like:
As with inputs, a <textarea>
has a name
and value
attribute.
<select>
ElementThe <select>
element, along with <option>
and <optgroup>
make drop-down selection boxes. The <select>
takes a name attribute, while each <option>
provides a different value. The <options>
can further be nested in <optgroup>
s with their own labels. The <select>
also has a multiple
attribute (to allow selecting multiple options), and size
which determines how many options should be displayed at once (with scrolling if more are available).
For example:
<select id="dino-select">
<optgroup label="Theropods">
<option>Tyrannosaurus</option>
<option>Velociraptor</option>
<option>Deinonychus</option>
</optgroup>
<optgroup label="Sauropods">
<option>Diplodocus</option>
<option>Saltasaurus</option>
<option>Apatosaurus</option>
</optgroup>
</select>
Displays as:
<label>
ElementA <label>
element represents a caption for an element in the form. It can be tied to a specific input using its for
attribute, by setting its value to the id
attribute of the associated input. This allows screen readers to identify the label as belonging to the input, and also allows browsers to give focus or activate the input element when the label is clicked.
For example, if you create a checkbox with a label:
<fieldset style="display:flex; align-items:center;">
<input type="checkbox" id="example"/>
<label for="example">Is Checked</label>
</fieldset>
Clicking the label will toggle the checkbox!
<fieldset>
ElementThe <fieldset>
element is used to group related form parts together, which can be captioned with a <legend>
. It also has a for
attribute which can be set to the id
of a form on the page to associate with, so that the fieldset will be serialized with the form (this is not necessary if the fieldset is inside the form). Setting the fieldset’s disabled
attribute will also disable all elements inside of it.
For example:
<fieldset>
<legend>Who is your favorite muppet?</legend>
<input type="radio" name="muppet" id="kermit">
<label for="kermit">Kermit</label>
</input>
<input type="radio" name="muppet" id="animal">
<label for="animal">Animal</label>
</input>
<input type="radio" name="muppet" id="piggy">
<label for="piggy">Miss Piggy</label>
</input>
<input type="radio" name="muppet" id="gonzo">
<label for="gonzo">Gonzo</label>
</input>
</fieldset>
Would render:
<form>
ElementFinally, the <form>
element wraps around all the <input>
, <textarea>
, and <select>
elements, and gathers them along with any contained within associated <fieldset>
s to submit in a serialized form. This is done when an <input type="submit">
is clicked within the form, when the enter key is pressed and the form has focus, or by calling the submit()
method on the form with JavaScript.
There are a couple of special attributes we should know for the <form>
element:
action
- the URL this form should be submitted to. Defaults to the URL the form was served from.enctype
- the encoding strategy used, discussed in the next section. Possible values are:application/x-www-form-urlencoded
- the defaultmultipart/form-data
- must be used to submit filestext/plain
- useful for debuggingmethod
- the HTTP method to submit the form using, most often GET or POSTWhen the form is submitted, the form is serialized using the enctype
attribute and submitted using the HTTP method
to the URL specified by the action
attribute. Let’s take a deeper look at this process next.
Form data is simply serialized key/value pairs pulled from a form and encoded using one of the three possible encoding strategies, and submitted using the specified method (usually GET or POST).
So when we submit a form containing two text inputs for first and last name:
<form method="post">
<label for="First">First Name:</label>
<input type="text" name="First"/>
<label for="Last">Last Name:</label>
<input type="text" name="Last"/>
<input type="Submit" value="Save Name"/>
</form>
And enter the values “Frank” and “Jones”, the form is serialized as the key-value pairs:
{
"First": "Frank",
"Last": "Jones"
}
Here we are displaying the key-value pairs as JSON for legibility, but how the pairs are encoded depends on the encoding strategy as discussed below.
If a form contains multiple inputs with the same name, they are serialized as an array, i.e. the form:
<form method="post">
<label>Enter three numbers:</label>
<input type="number" name="Number"/>
<input type="number" name="Number"/>
<input type="number" name="Number"/>
<input type="Submit" value="Save Numbers"/>
</form>
Would be serialized as:
{
"Number" : [2, 3, 4]
}
Finally, toggle inputs like checkboxes and radio buttons only submit a value when checked, i.e. given the following form:
<form method="post">
<label>Do the thing:</label>
<input type="checkbox" name="DoTheThing" value="thing"/>
<input type="Submit" value="Save Numbers"/>
</form>
Would serialize as:
{}
When the checkbox is not checked, or:
{
"DoTheThing": "thing"
}
When the checkbox is checked.
Now that we’ve discussed how inputs are serialized into key/value or key/array of value pairs, let’s turn our attention to the method used to submit the form, and then look at each encoding strategy in turn.
The HTTP method determines if the form data is sent as part of the url of the request, or in the body of the request.
With a GET request, the serialized form data is appended to the url as a query or search parameter. This takes the form of a question mark: ?
followed by the serialized form data. In addition, the serialized data must be url-encoded to ensure the URL remains valid, as it may contain reserved characters (i.e. the characters :
,/
,?
,#
, &
, and =
) have special meanings in URLs, so the encoded data can’t contain them).
For example, searching using Google uses a GET request, so when we type “Razor Pages” into Google and click search, it makes a request against the URL: https://www.google.com/search?q=razor+pages
(Note it adds additional form data fields for a variety of purposes).
A GET request is appropriate when the data you are submitting is small, like search terms or filter values. Once you start submitting larger amounts of data (like parameters to build an object from), you’ll probably want to switch to POST requests. Also, remember the form data for GET requests are visible in the URL, so you’ll want to use POST requests when seeing those values might be confusing or annoying to users.
Finally, passwords should NEVER be sent using a GET request, as doing so makes them visible in the URL.
The default method for form submission is a GET request, so if you don’t specify the method
parameter, the form will be submitted using this method.
A POST request is submitted as the body of the request. This is the most appropriate method for large submissions, submissions with data you don’t want visibly displayed in the browser, and it is the only way to submit form data including files (which must be encoded using “multipart/form-data” as described below).
There are two primary strategies for submitting data to a server from HTML forms (note that you can submit data to servers using other strategies when submitting from a program - we’ll discuss some of these in the next chapter). These strategies are x-www-form-urlencoded
and multipart/form-data
. We’ll take a look at how these encode data next.
The default encoding method is application/x-www-form-urlencoded
, which encodes the form data as a string consisting of key/value pairs. Each pair is joined by a =
symbol, and pairs are in turn joined by &
symbols. The key and value strings are further encoded using percent encoding (URL encoding), which replaces special characters with a code beginning with a percent sign (i.e. &
is encoded to %26
). This prevents misinterpretations of the key and value as additional pairs, etc. Percent encoding is also used to encode URL segments (hence the name URL encoding).
Thus, the form:
<form>
<input type="text" name="Name" value="Grover"/>
<select name="Color">
<option value="Red">Red</option>
<option selected="true" value="Blue">Blue</option>
<option value="Green">Green</option>
</select>
<input type="number" name="Age" value="36"/>
</form>
Would be encoded as:
Name=Grover&Color=Blue&Age=36
The HTTPUtility class in the System.Web
namespace contains helpful methods for encoding and decoding URL strings.
URL-Encoded form data can be submitted with either a GET or POST request. With a GET request, the form data is included in the URL’s query (search) string, i.e. our form above might be sent to:
www.sesamestreet.com/muppets/find?Name=Grover&Color=Blue&Age=36
Which helps explain why the entire seralized form data needs to be URL encoded - it is included as part of the url!
When submitted as a post request, the string of form data is the body of the request.
The encoding for multipart/form-data
is a bit more involved, as it needs to deal with encoding both regular form values and binary file data. It deals with this challenge by separating each key/value pair by a sequence of bytes known as a boundary, which does not appear in any of the files. This boundary can then be used to split the body back into its constituent parts when parsing. Each part of the body consists of its own head and body sections, with the body of most elements simply their value, while the body of file inputs is the file data encoded in base64. Thus, the form:
<form>
<input type="text" name="Name" value="Grover"/>
<select name="Color">
<option value="Red">Red</option>
<option selected="true" value="Blue">Blue</option>
<option value="Green">Green</option>
</select>
<input type="number" name="Age" value="36"/>
<input type="file" name="Image" value="Grover.jpg" />
</form>
Would be encoded into a POST request as:
POST /test HTTP/1.1
Host: foo.example
Content-Type: multipart/form-data;boundary="boundary"
--boundary
Content-Disposition: form-data; name="Name"
Grover
--boundary
Content-Disposition: form-data; name="Color"
Blue
--boundary
Content-Disposition: form-data; name="Age"
36
--boundary
Content-Disposition: form-data; name="Image"; filename="Grover.jpg"
/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjI...
--boundary--
Files can only be submitted using multipart/form-data
encoding. If you attempt to use application/x-www-form-urlencoded
, only the file name will be submitted as the value. Also, as multipart/form-data
is always submitted as the body of the request, it can only be submitted as part of a POST request, never a GET. So a form containing a file
input should always specify:
<form enctype="multipart/form-data" method="POST">
While C# does provide utilities for parsing URL-encoded strings, the functionality of parsing incoming form data is built into the ASP.NET response handling. Thus, when writing a Razor page application, we don’t need to perform decoding on the form data - it has already been done for us. There are several strategies built into Razor Pages to access this information:
The first of these is the HttpRequest object, which is available as the Request
property within a Page
. This object provides access to the QueryString
and Form
, as well as Cookies
, ServerVariables
and more.
If the form was submitted as a GET request, then the Request.QueryString
is a collection of key-value pairs used like a dictionary, i.e. to access the value of the input with name “Color”, we would use: Request.QueryString["Color"]
.
Similarly, the Form
also exposes the form content as a collection of key-value pairs, so we could access a POST request’s input with the name “Color” value with Request.Form["Color"]
.
Finally, the request also allows for checking both collections using its own accessor property, i.e. Request["Color"]
would provide the submitted value for the input “Color” if it was sent with either a GET or POST request.
A second approach to accessing form data in ASP.NET is Parameter Binding. You’ve already seen the OnGet()
method of the PageModel
class. This is invoked every time a GET request to our server matches the page it is associated with. We can also supply methods for other HTTP actions, i.e. POST, PUT, DELETE map to OnPost()
, OnPut()
, and OnDelete()
respectively. For each of these methods, we can use parameter binding to automatically parse and convert form data into parameter variables.
In its simplest form, we simply declare parameters whose type and name match those of our form. Thus, for the form:
<form>
<input type="text" name="Name" value="Grover"/>
<select name="Color">
<option value="Red">Red</option>
<option selected="true" value="Blue">Blue</option>
<option value="Green">Green</option>
</select>
<input type="number" name="Age" value="36"/>
</form>
We could add several parameters to our OnGet()
corresponding the names and types of the form fields:
OnGet(string Name, string Color, int Age){
// Name would be "Grover"
// Color would be "Blue"
// Number would be 36
}
The form values are automatically converted and bound to the corresponding parameters of the OnGet
method. If the form does not contain the corresponding parameter, then it is assigned the default value (for value types) or null (for reference types).
There are times you may not want to use default values for value types. For example, in the form above, if the Age
property is not specified, it will default to 0
. If we instead wanted it to be null
, we could use the Nullable<T>
type:
OnGet(string Name, string Color, Nullable<int> Age) {...}
This allows Age
to be null
, in addition to all its normal possible values. You can also specify a nullable type with the ?
shorthand, i.e.:
OnGet(string Name, string Color, int? Age) {...}
A third approach is Model Binding, where decorators are used on public properties of the PageModel
class to indicate that they should be bound to form data, i.e.:
public class Muppet : PageModel {
/// <summary>The muppet's name</summary>
[BindProperty]
public string Name { get; set; }
/// <summary>The muppet's color</summary>
[BindProperty]
public string Color { get; set; }
///<summary>The muppet's age</summary>
[BindProperty]
public int Age {get; set;}
}
When set up this way, the properties will be populated with the corresponding form data on POST, PUT, and PATCH requests. By default, they will not be populated on GET requests, though you can override this behavior with SupportsGet
:
/// <summary>The muppet's name</summary>
[BindProperty(SupportGet = true)]
public string Name { get; set; }
Finally, we can indicate all properties of the model class should be bound with a single [BindsProperties]
decorator on the class, i.e.:
[BindProperties(SupportsGet = true)]
public class Muppet : PageModel {
/// <summary>The muppet's name</summary>
public string Name { get; set; }
/// <summary>The muppet's color</summary>
public string Color { get; set; }
///<summary>The muppet's age</summary>
public int Age {get; set;}
}
You might be wondering why ModelBinding does not work with GET requests by default. In the previous section, we discussed when to use GET or POST requests to submit form data - we use GET requests for smaller data we don’t mind displaying the URL, like search terms or filter values. We use POST requests for large data, especially data we will use to populate an object. So when using Model Binding, using the POST method is most appropriate. Microsoft chose to reinforce this practice through the choice to not bind GET requests by default.
Validation refers to the process of making sure the submitted data matches our expectations. Validation can be done client-side or server-side. For example, we can use the built-in HTML form validation properties to enforce rules, like a number that must be positive:
<input type="number" min="0" name="Age" required>
If a user attempts to submit a form containing this input, and the value is less than 0, the browser will display an error message instead of submitting. In addition, the psuedo-css class :invalid
will be applied to the element.
We can also mark inputs as required using the required
attribute. The browser will refuse to submit the form until all required inputs are completed. Inputs with a required
attribute also receive the :required
pseudo-class, allowing you to assign specific styles to them.
You can read more about HTML Form validation on MDN.
Client-side validation is a good idea, because is minimizes invalid requests against our web application. However, we cannot always depend on it, so we also need to implement server-side validation. We can write custom logic for doing this, but Razor Pages also supports special validation decorators for bound properties. For example, the corresponding validation for the input above would be:
[BindProperty]
[Required]
[Range(0, int.MaxValue)]
public int Age { get; set; }
The available validation decorators are:
[CreditCard]
: Validates that the property has a credit card format. Requires jQuery Validation Additional Methods.[Compare]
: Validates that two properties in a model match.[EmailAddress]
: Validates that the property has an email format.[Phone]
: Validates that the property has a telephone number format.[Range]
: Validates that the property value falls within a specified range.[RegularExpression]
: Validates that the property value matches a specified regular expression.[Required]
: Validates that the field is not null. See [Required] attribute for details about this attribute’s behavior.[StringLength]
: Validates that a string property value doesn’t exceed a specified length limit.[Url]
: Validates that the property has a URL format.If validation fails, then the PageModel
’s IsValid
attribute is false.
You can read more about server-side validation with Razor pages in the Microsoft Documentation.
In this chapter we looked at how data is handled in web applications. We saw how forms can be used to submit data to our server, and examined several common encoding strategies. We also saw how we can retrieve this data in our Razor Pages - through the Request
object, or by parameter or model binding. Finally, we discussed validating submitted values, on both the client and server side of a HTTP request.
You should now be able to handle creating web forms and processing the submitted data.