Razor Pages and Form Data

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 Request Object

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.

Parameter Binding

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).

Info

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) {...}

Model Binding

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;}    
}
Info

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.