ASP.NET Core in Action 16 Binding and validating requests with Razor Pages

16 Binding and validating requests with Razor Pages

This chapter covers

• Using request values to create binding models
• Customizing the model-binding process
• Validating user input using DataAnnotations attributes

In chapter 7 we looked at the process of model binding and validation in minimal APIs. In this chapter we look at the Razor Pages equivalent: extracting values from a request using model binding and validating user input.

In the first half of this chapter, we look at using binding models to retrieve those parameters from the request so that you can use them in your Razor Pages by creating C# objects. These objects are passed to your Razor Page handlers as method parameters or are set as properties on your Razor Page PageModel.

Once your code is executing in a page handler method, you can’t simply use the binding model without any further thought. Any time you’re using data provided by a user, you need to validate it! The second half of the chapter focuses on how to validate your binding models with Razor Pages.

We covered model binding and validation for minimal APIs in chapter 7, and conceptually, binding and validation are the same for Razor Pages. However, the details and mechanics of both binding and validation are quite different for Razor Pages.

The binding models populated by the Razor Pages infrastructure are passed to page handlers when they execute. Once the page handler has run, you’re all set up to use the output models in ASP.NET Core’s implementation of Model-View-Controller (MVC): the view models and API models. These are used to generate a response to the user’s request. We’ll cover them in chapters 19 and 20.

Before we go any further, let’s recap the MVC design pattern and how binding models fit into ASP.NET Core.

16.1 Understanding the models in Razor Pages and MVC

In this section I describe how binding models fit into the MVC design pattern we covered in chapter 13. I describe the difference between binding models and the other “model” concepts in the MVC pattern and how they’re each used in ASP.NET Core.

MVC is all about the separation of concerns. The premise is that isolating each aspect of your application to focus on a single responsibility reduces the interdependencies in your system. This separation makes it easier to make changes without affecting other parts of your application.

The classic MVC design pattern has three independent components:

• Model—The data to display and the methods for updating this data
• View—Displays a representation of data that makes up the model
• Controller—Calls methods on the model and selects a view

In this representation, there’s only one model, the application model, which represents all the business logic for the application as well as how to update and modify its internal state. ASP.NET Core has multiple models, which takes the single-responsibility principle (SRP) one step further than some views of MVC.

In chapter 13 we looked at an example of a to-do list application that can show all the to-do items for a given category and username. With this application, you make a request to a URL that’s routed using todo/listcategory/{category}/{username}. This returns a response showing all the relevant to-do items, as shown in figure 16.1.

alt text

Figure 16.1 A basic to-do list application that displays to-do list items. A user can filter the list of items by changing the category and username parameters in the URL.

The application uses the same MVC constructs you’ve already seen, such as routing to a Razor Page handler, as well as various models. Figure 16.2 shows how a request to this application maps to the MVC design pattern and how it generates the final response, including additional details around the model binding and validation of the request.

alt text

Figure 16.2 The MVC pattern in ASP.NET Core handling a request to view a subset of items in a to-do list Razor Pages application

ASP.NET Core Razor Pages uses several models, most of which are plain old CLR objects (POCOs), and the application model, which is more of a concept around a collection of services. Each of the models in ASP.NET Core is responsible for handling a different aspect of the overall request:

• Binding model—The binding model is all the information that’s provided by the user when making a request, as well as additional contextual data. This includes things like route parameters parsed from the URL, the query string, and form or JavaScript Object Notation (JSON) data in the request body. The binding model itself is one or more POCO objects that you define. Binding models in Razor Pages are typically defined by creating a public property on the page’s PageModel and decorating it with the [BindProperty] attribute. They can also be passed to a page handler as parameters.
For this example, the binding model would include the name of the category, open, and the username, Andrew. The Razor Pages infrastructure inspects the binding model before the page handler executes to check whether the provided values are valid, though the page handler executes even if they’re not, as you’ll see when we discuss validation in section 16.3.

• Application model—The application model isn’t really an ASP.NET Core model at all. It’s typically a whole group of different services and classes and is more of a concept—anything needed to perform some sort of business action in your application. It may include the domain model (which represents the thing your app is trying to describe) and database models (which represent the data stored in a database), as well as any other, additional services.
In the to-do list application, the application model would contain the complete list of to-do items, probably stored in a database, and would know how to find only those to-do items in the open category assigned to Andrew.

• Page model—The PageModel of a Razor Page serves two main functions: it acts as the controller for the application by exposing page handler methods, and it acts as the view model for a Razor view. All the data required for the view to generate a response is exposed on the PageModel, such as the list of to-dos in the open category assigned to Andrew.

The PageModel base class that you derive your Razor Pages from contains various helper properties and methods. One of these, the ModelState property, contains the result of the model validation as a series of key-value pairs. You’ll learn more about validation and the ModelState property in section 16.3.
These models make up the bulk of any Razor Pages application, handling the input, business logic, and output of each page handler. Imagine you have an e-commerce application that allows users to search for clothes by sending requests to the /search/{query} URL, where {query} holds their search term:

• Binding model—This would take the {query} route parameter from the URL and any values posted in the request body (maybe a sort order, or the number of items to show), and bind them to a C# class, which typically acts as a throwaway data transport class. This would be set as a property on the PageModel when the page handler is invoked.

• Application model—This is the services and classes that perform the logic. When invoked by the page handler, this model would load all the clothes that match the query, applying the necessary sorting and filters, and return the results to the controller.

• Page model—The values provided by the application model would be set as properties on the Razor Page’s PageModel, along with other metadata, such as the total number of items available or whether the user can currently check out. The Razor view would use this data to render the Razor view to HTML.

The important point about all these models is that their responsibilities are well defined and distinct. Keeping them separate and avoiding reuse helps ensure that your application stays agile and easy to update.

The obvious exception to this separation is the PageModel, as it is where the binding models and page handlers are defined, and it also holds the data required for rendering the view. Some people may consider the apparent lack of separation to be sacrilege, but it’s not generally a problem. The lines of demarcation are pretty apparent. So long as you don’t try to, for example, invoke a page handler from inside a Razor view, you shouldn’t run into any problems!

Now that you’ve been properly introduced to the various models in ASP.NET Core, it’s time to focus on how to use them. This chapter looks at the binding models that are built from incoming requests—how are they created, and where do the values come from?

16.2 From request to model: Making the request useful

In this section you will learn

• How ASP.NET Core creates binding models from a request
• How to bind simple types, like int and string, as well as complex classes
• How to choose which parts of a request are used in the binding model

By now, you should be familiar with how ASP.NET Core handles a request by executing a page handler on a Razor Page. Page handlers are normal C# methods, so the ASP.NET Core framework needs to be able to call them in the usual way. The process of extracting values from the request and creating C# objects from them is called model binding.

Any publicly settable properties on your Razor Page’s PageModel (in the .cshtml.cs file for your Razor Page), that are decorated with the [BindProperty] attribute are created from the incoming request using model binding, as shown in listing 16.1. Similarly, if your page handler method has any parameters, these are also created using model binding.

Warning Properties decorated with [BindProperty] must have a public setter; otherwise, binding will silently fail.

Listing 16.1 Model binding requests to properties in a Razor Page

public class IndexModel: PageModel
{
[BindProperty] ❶
public string Category { get; set; } ❶
[BindProperty(SupportsGet = true)] ❷
public string Username { get; set; } ❷
public void OnGet()
{
}
public void OnPost(ProductModel model) ❸
{
}
}

❶ Properties decorated with [BindProperty] take part in model binding.
❷ Properties are not model-bound for GET requests unless you use SupportsGet.
❸ Parameters to page handlers are also model-bound when that handler is selected.

As described in chapter 15 and shown in the preceding listing, PageModel properties are not model-bound for GET requests, even if you add the [BindProperty] attribute. For security reasons, only requests using verbs like POST and PUT are bound. If you do want to bind GET requests, you can set the SupportsGet property on the [BindProperty] attribute to opt in to model binding.

Which part is the binding model?
Listing 16.1 shows a Razor Page that uses multiple binding models: the Category property, the Username property, and the ProductModel property (in the OnPost handler) are all model-bound.

Using multiple models in this way is fine, but I prefer to use an approach that keeps all the model binding in a single, nested class, which I often call InputModel. With this approach, the Razor Page in listing 16.1 could be written as follows:

public class IndexModel: PageModel
{
    [BindProperty]
    public InputModel Input { get; set; }
    public void OnGet()
    {
    }

    public class InputModel
    {
        public string Category { get; set; }
        public string Username { get; set; }
        public ProductModel Model { get; set; }
    }
}

This approach has some organizational benefits that you’ll learn more about in section 16.4.

ASP.NET Core automatically populates your binding models for you using properties of the request, such as the request URL, any headers sent in the HTTP request, any data explicitly POSTed in the request body, and so on.

NOTE In this chapter I describe how to bind your models to an incoming request, but I don’t show how Razor Pages uses your binding models to help generate that request using HTML forms. In chapter 17 you’ll learn about Razor syntax, which renders HTML, and in chapter 18 you’ll learn about Razor Tag Helpers, which generate form fields based on your binding model.

By default, ASP.NET Core uses three different binding sources when creating your binding models in Razor Pages. It looks through each of these in order and takes the first value it finds (if any) that matches the name of the binding model:

• Form values—Sent in the body of an HTTP request when a form is sent to the server using a POST
• Route values—Obtained from URL segments or through default values after matching a route, as you saw in chapter 14
• Query string values—Passed at the end of the URL, not used during routing

Warning Even though conceptually similar, the Razor Page binding process works quite differently from the approach used by minimal APIs.

The model binding process for Razor Pages is shown in figure 16.3. The model binder checks each binding source to see whether it contains a value that could be set on the model. Alternatively, the model can choose the specific source the value should come from, as you’ll see in section 16.2.3. Once each property is bound, the model is validated and is set as a property on the PageModel or passed as a parameter to the page handler. You’ll learn about the validation process in the second half of this chapter.

alt text

Figure 16.3 Model binding involves mapping values from binding sources, which correspond to different parts of a request.

NOTE In Razor Pages, different properties of a complex model can be model-bound to different sources. This differs from minimal APIs, where the whole object would be bound from a single source, and “partial” binding is not possible. Razor Pages also bind to form bodies by default, while minimal APIs cannot. These differences are partly for historical reasons and partly because minimal APIs opts for performance over convenience in this respect.

PageModel properties or page handler parameters?
There are three ways to use model binding in Razor Pages:

• Decorate properties on your PageModel with the [BindProperty] attribute.

• Add parameters to your page handler method.

• Decorate the whole PageModel with [BindProperties].

Which of these approaches should you choose?

This answer to this question is largely a matter of taste. Setting properties on the PageModel and marking them with [BindProperty] is the approach you’ll see most often in examples. If you use this approach, you’ll be able to access the binding model when the view is rendered, as you’ll see in chapters 17 and 18.

The second approach, adding parameters to page handler methods, provides more separation between the different MVC stages, because you won’t be able to access the parameters outside the page handler. On the downside, if you do need to display those values in the Razor view, you’ll have to copy the parameters across manually to properties that can be accessed in the view.

I avoid the final approach, decorating the PageModel itself with [BindProperties]. With this approach, every property on your PageModel takes part in model binding. I don’t like the indirection this gives and the risk of accidentally binding properties I didn’t want to be model-bound.

The approach I choose tends to depend on the specific Razor Page I’m building. If I’m creating a form, I will favor the [BindProperty] approach, as I typically need access to the request values inside the Razor view. For simple pages, where the binding model is a product ID, for example, I tend to favor the page handler parameter approach for its simplicity, especially if the handler is for a GET request. I give some more specific advice on my approach in section 16.4.

Figure 16.4 shows an example of a request creating the ProductModel method argument using model binding for the example shown at the start of this section:

public void OnPost(ProductModel product)

alt text

Figure 16.4 Using model binding to create an instance of a model that’s used to execute a Razor Page

The Id property has been bound from a URL route parameter, but the Name and SellPrice properties have been bound from the request body. The big advantage of using model binding is that you don’t have to write the code to parse requests and map the data yourself. This sort of code is typically repetitive and error-prone, so using the built-in conventional approach lets you focus on the important aspects of your application: the business requirements.

Tip Model binding is great for reducing repetitive code. Take advantage of it whenever possible, and you’ll rarely find yourself having to access the Request object directly.

If you need to, the capabilities are there to let you completely customize the way model binding works, but it’s relatively rare that you’ll find yourself needing to dig too deep into this. For the majority of cases, it works as is, as you’ll see in the remainder of this section.

16.2.1 Binding simple types

We’ll start our journey into model binding by considering a simple Razor Page handler. The next listing shows a simple Razor Page that takes one number as a method parameter and squares it by multiplying the number by itself.

Listing 16.2 A Razor Page accepting a simple parameter

public class CalculateSquareModel : PageModel
{
public void OnGet(int number) ❶
{
Square = number * number; ❷
}
public int Square { get; set; } ❸
}

❶ The method parameter is the binding model.
❷ A more complex example would do this work in an external service, in the application model.
❸ The result is exposed as a property and is used by the view to generate a response.

In chapters 6 and 14, you learned about routing and how it selects a Razor Page to execute. You can update the route template for the Razor Page to be "CalculateSquare/{number}" by adding a {number} segment to the Razor Page’s @page directive in the .cshtml file:

@page "{number}"

When a client requests the URL /CalculateSquare/5, the Razor Page framework uses routing to parse it for route parameters. This produces the route value pair

number=5

The Razor Page’s OnGet page handler contains a single parameter—an integer called number—which is your binding model. When ASP.NET Core executes this page handler method, it will spot the expected parameter, flick through the route values associated with the request, and find the number=5 pair. Then it can bind the number parameter to this route value and execute the method. The page handler method itself doesn’t care where this value came from; it goes along its merry way, calculating the square of the value and setting it on the Square property.

The key thing to appreciate is that you didn’t have to write any extra code to try to extract the number from the URL when the method executed. All you needed to do was create a method parameter (or public property) with the right name and let model binding do its magic.

Route values aren’t the only values the Razor Pages model binder can use to create your binding models. As you saw previously, the framework will look through three default binding sources to find a match for your binding models:

• Form values
• Route values
• Query string values

Each of these binding sources store values as name-value pairs. If none of the binding sources contains the required value, the binding model is set to a new, default instance of the type instead. The exact value the binding model will have in this case depends on the type of the variable:

• For value types, the value will be default(T). For an int parameter this would be 0, and for a bool it would be false.
• For reference types, the type is created using the default (parameterless) constructor. For custom types like ProductModel, that will create a new object. For nullable types like int? or bool?, the value will be null.
• For string types, the value will be null.

Warning It’s important to consider the behavior of your page handler when model binding fails to bind your method parameters. If none of the binding sources contains the value, the value passed to the method could be null or could unexpectedly have a default value (for value types).

Listing 16.2 showed how to bind a single method parameter. Let’s take the next logical step and look at how you’d bind multiple method parameters.

Let’s say you’re building a currency converter application. As the first step you need to create a method in which the user provides a value in one currency, and you must convert it to another. You first create a Razor Page called Convert.cshtml and then customize the route template for the page using the @page directive to use an absolute path containing two route values:

@page "/{currencyIn}/{currencyOut}"

Then you create a page handler that accepts the three values you need, as shown in the following listing.

Listing 16.3 A Razor Page handler accepting multiple binding parameters

public class ConvertModel : PageModel
{
    public void OnGet(
        string currencyIn,
        string currencyOut,
        int qty
)
    {
        /* method implementation */
    }
}

As you can see, there are three different parameters to bind. The question is, where will the values come from and what will they be set to? The answer is, it depends! Table 16.1 shows a whole variety of possibilities. All these examples use the same route template and page handler, but depending on the data sent, different values will be bound. The actual values might differ from what you expect, as the available binding sources offer conflicting values!

Table 16.1 Binding request data to page handler parameters from multiple binding sources

URL (route values) HTTP body data (form values) Parameter values bound
/GBP/USD - currencyIn=GBP
currencyOut=USD qty=0
/GBP/USD?currencyIn=CAD QTY=50 currencyIn=GBP
currencyOut=USD qty=50
/GBP/USD?qty=100 qty=50 currencyIn=GBP
currencyOut=USD qty=50
/GBP/USD?qty=100 currencyIn=CAD&
currencyOut=EUR&qty=50
currencyIn=CAD
currencyOut=EUR qty=50

For each example, be sure you understand why the bound values have the values that they do. In the first example, the qty value isn’t found in the form data, in the route values, or in the query string, so it has the default value of 0. In each of the other examples, the request contains one or more duplicated values; in these cases, it’s important to bear in mind the order in which the model binder consults the binding sources. By default, form values will take precedence over other binding sources, including route values!

NOTE The default model binder isn’t case-sensitive, so a binding value of QTY=50 will happily bind to the qty parameter.

Although this may seem a little overwhelming, it’s relatively unusual to be binding from all these different sources at once. It’s more common to have your values all come from the request body as form values, maybe with an ID from URL route values. This scenario serves as more of a cautionary tale about the knots you can twist yourself into if you’re not sure how things work under the hood.

In these examples, you happily bound the qty integer property to incoming values, but as I mentioned earlier, the values stored in binding sources are all strings. What types can you convert a string to?

The model binder will convert pretty much any primitive .NET type such as int, float, decimal (and string obviously), any custom type that has a TryParse method (like minimal APIs, as you saw in chapter 7) plus anything that has a TypeConverter.

NOTE TypeConverters can be found in the System.ComponentModel.TypeConverter package. You can read more about them in Microsoft’s “Type conversion in .NET” documentation: http://mng.bz/A0GK.

There are a few other special cases that can be converted from a string, such as Type, but thinking of it as built-in types only will get you a long way there!

16.2.2 Binding complex types

If it seems like only being able to bind simple built-in types is a bit limiting, you’re right! Luckily, that’s not the case for the model binder. Although it can only convert strings directly to those simple types, it’s also able to bind complex types by traversing any properties your binding models expose, binding each of those properties to strings instead.

If this doesn’t make you happy straight off the bat, let’s look at how you’d have to build your page handlers if simple types were your only option. Imagine a user of your currency converter application has reached a checkout page and is going to exchange some currency. Great! All you need now is to collect their name, email address, and phone number. Unfortunately, your page handler method would have to look something like this:

public IActionResult OnPost(string firstName, string lastName, string phoneNumber, string email)

Yuck! Four parameters might not seem that bad right now, but what happens when the requirements change and you need to collect other details? The method signature will keep growing. The model binder will bind the values quite happily, but it’s not exactly clean code. Using the [BindProperty] approach doesn’t really help either; you still have to clutter your PageModel with lots of properties and attributes!

Simplifying method parameters by binding to complex objects

A common pattern for any C# code when you have many method parameters is to extract a class that encapsulates the data the method requires. If extra parameters need to be added, you can add a new property to this class. This class becomes your binding model, and it might look something like the following listing.

Listing 16.4 A binding model for capturing a user’s details

public class UserBindingModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string PhoneNumber { get; set; }
}

NOTE In this book I primarily use class instead of record for my binding models, but you can use record if you prefer. I find the terseness that the record positional syntax provides is lost if you want to add attributes to properties, such as to add validation attributes, as you’ll see in section 16.3. You can see the required syntax for positional property attributes in the documentation at http://mng.bz/Kex0.

With this model, you can update your page handler’s method signature to

public IActionResult OnPost(UserBindingModel user)

Alternatively, using the [BindProperty] approach, create a property on the PageModel:

[BindProperty]
public UserBindingModel User { get; set; }

Now you can simplify the page handler signature even further:

public IActionResult OnPost()

Functionally, the model binder treats this new complex type a little differently. Rather than look for parameters with a value that matches the parameter name (user, or User for the property), the model binder creates a new instance of the model using new UserBindingModel().

NOTE You don’t have to use custom classes for your methods; it depends on your requirements. If your page handler needs only a single integer, it makes more sense to bind to the simple parameter.

Next, the model binder loops through all the properties your binding model has, such as FirstName and LastName in listing 16.4. For each of these properties, it consults the collection of binding sources and attempts to find a name-value pair that matches. If it finds one, it sets the value on the property and moves on to the next.

Tip Although the name of the model isn’t necessary in this example, the model binder will also look for properties prefixed with the name of the property, such as user.FirstName and user.LastName for a property called User. You can use this approach when you have multiple complex parameters to a page handler or multiple complex [BindProperty] properties. In general, for simplicity, you should avoid this situation if possible. As for all model binding, the casing of the prefix does not matter.

Once all the properties that can be bound on the binding model are set, the model is passed to the page handler (or the [BindProperty] property is set), and the handler is executed as usual. The behavior from this point on is identical to when you have lots of individual parameters—you’ll end up with the same values set on your binding model—but the code is cleaner and easier to work with.

Tip For a class to be model-bound, it must have a default public constructor. You can bind only properties that are public and settable.

With this technique you can bind complex hierarchical models whose properties are themselves complex models. As long as each property exposes a type that can be model-bound, the binder can traverse it with ease.

Binding collections and dictionaries
As well as binding to ordinary custom classes and primitives, you can bind to collections, lists, and dictionaries. Imagine you had a page in which a user selected all the currencies they were interested in; you’d display the rates for all those selected, as shown in figure 16.5.

alt text

Figure 16.5 The select list in the currency converter application sends a list of selected currencies to the application. Model binding binds the selected currencies and customizes the view for the user to show the equivalent cost in the selected currencies.

To achieve this, you could create a page handler that accepts a List type, such as

public void OnPost(List<string> currencies);

You could then POST data to this method by providing values in several different formats:

• currencies[index]—Where currencies is the name of the parameter to bind and index is the index of the item to bind, such as currencies[0]= GBP&currencies[1]=USD.
• [index]—If you’re binding to a single list (as in this example), you can omit the name of the parameter, such as [0]=GBP&[1]=USD.
• currencies—Alternatively, you can omit the index and send currencies as the key for every value, such as currencies=GBP&currencies=USD.

The key values can come from route values and query values, but it’s far more common to POST them in a form. Dictionaries can use similar binding, where the dictionary key replaces the index both when the parameter is named and when it’s omitted.

Tip In the previous example I showed a collection using the built-in string type, but you can also bind collections of complex type, such as a List.

If this all seems a bit confusing, don’t feel too alarmed. If you’re building a traditional web application and using Razor views to generate HTML, the framework will take care of generating the correct names for you. As you’ll see in chapter 18, the Razor view ensures that any form data you POST is generated in the correct format.

Binding file uploads with IFormFile
Razor Pages supports users uploading files by exposing the IFormFile and IFormFileCollection interfaces. You can use these interfaces as your binding model, either as a method parameter to your page handler or using the [BindProperty] approach, and they will be populated with the details of the file upload:

public void OnPost(IFormFile file);

If you need to accept multiple files, you can use IFormFileCollection, IEnumerable, or List:

public void OnPost(IEnumerable<IFormFile> file);

You already learned how to use IFormFile in chapter 7 when you looked at minimal API binding. The process is the same for Razor Pages. I’ll reiterate one point here: if you don’t need users to upload files, great! There are so many potential threats to consider when handling files—from malicious attacks, to accidental denial-of-service vulnerabilities—that I avoid them whenever possible.

For the vast majority of Razor Pages, the default configuration of model binding for simple and complex types works perfectly well, but you may find some situations where you need to take a bit more control. Luckily, that’s perfectly possible, and you can completely override the process if necessary by replacing the ModelBinders used in the guts of the framework.

However, it’s rare to need that level of customization. I’ve found it’s more common to want to specify which binding source to use for a page’s binding instead.

16.2.3 Choosing a binding source

As you’ve already seen, by default the ASP.NET Core model binder attempts to bind your binding models from three binding sources: form data, route data, and the query string.

Occasionally, you may find it necessary to specifically declare which binding source to bind to. In other cases, these three sources won’t be sufficient at all. The most common scenarios are when you want to bind a method parameter to a request header value or when the body of a request contains JSON-formatted data that you want to bind to a parameter. In these cases, you can decorate your binding models with attributes that say where to bind from, as shown in the following listing.

Listing 16.5 Choosing a binding source for model binding

public class PhotosModel: PageModel
{
    public void OnPost(
        [FromHeader] string userId,     ❶
        [FromBody] List<Photo> photos)      ❷
    {
        /* method implementation */
    }
}

❶ The userId is bound from an HTTP header in the request.
❷ The list of photo objects is bound to the body of the request, typically in JSON format.

In this example, a page handler updates a collection of photos with a user ID. There are method parameters for the ID of the user to be tagged in the photos, userId, and a list of Photo objects to tag, photos.

Rather than binding these method parameters using the standard binding sources, I’ve added attributes to each parameter, indicating the binding source to use. The [FromHeader] attribute has been applied to the userId parameter. This tells the model binder to bind the value to an HTTP request header value called userId.

We’re also binding a list of photos to the body of the HTTP request by using the [FromBody] attribute. This tells the binder to read JSON from the body of the request and bind it to the List method parameter.

Warning Developers coming from .NET Framework and the legacy version of ASP.NET should take note that the [FromBody] attribute is explicitly required when binding to JSON requests in Razor Pages. This differs from the legacy ASP.NET behavior, in which no attribute was required.

You aren’t limited to binding JSON data from the request body. You can use other formats too, depending on which InputFormatters you configure the framework to use. By default, only a JSON input formatter is configured. You’ll see how to add an XML formatter in chapter 20, when I discuss web APIs.

Tip Automatic binding of multiple formats from the request body is one of the features specific to Razor Pages and MVC controllers, which is missing from minimal APIs.

You can use a few different attributes to override the defaults and to specify a binding source for each binding model (or each property on the binding model). These are the same attributes you used in chapter 7 with minimal APIs:

• [FromHeader]—Bind to a header value.
• [FromQuery]—Bind to a query string value.
• [FromRoute]—Bind to route parameters.
• [FromForm]—Bind to form data posted in the body of the request. This attribute is not available in minimal APIs.
• [FromBody]—Bind to the request’s body content.

You can apply each of these to any number of handler method parameters or properties, as you saw in listing 16.5, with the exception of the [FromBody] attribute. Only one value may be decorated with the [FromBody] attribute. Also, as form data is sent in the body of a request, the [FromBody] and [FromForm] attributes are effectively mutually exclusive.

Tip Only one parameter may use the [FromBody] attribute. This attribute consumes the incoming request as HTTP request bodies can be safely read only once.

As well as these attributes for specifying binding sources, there are a few attributes for customizing the binding process even further:

• [BindNever]—The model binder will skip this parameter completely. You can use this attribute to prevent mass assignment, as discussed in these two posts on my blog: http://mng.bz/QvfG and http://mng.bz/Vd90.
• [BindRequired]—If the parameter was not provided or was empty, the binder will add a validation error.
• [FromServices]—This is used to indicate the parameter should be provided using dependency injection (DI). This attribute isn’t required in most cases, as .NET 7 is smart enough to know that a parameter is a service registered in DI, but you can be explicit if you prefer.

In addition, you have the [ModelBinder] attribute, which puts you into “God mode” with respect to model binding. With this attribute, you can specify the exact binding source, override the name of the parameter to bind to, and specify the type of binding to perform. It’ll be rare that you need this one, but when you do, at least it’s there!

By combining all these attributes, you should find you’re able to configure the model binder to bind to pretty much any request data your page handler wants to use. In general, though, you’ll probably find you rarely need to use them; the defaults should work well for you in most cases.

That brings us to the end of this section on model binding. At the end of the model binding process, your page handler should have access to a populated binding model, and it’s ready to execute its logic. But before you use that user input for anything, you must always validate your data, which is the focus of the second half of this chapter. Razor Pages automatically does validation for you out-of-the-box, but you have to actually check the results.

16.3 Validating binding models

In this section I discuss how validation works in Razor Pages. You already learned how important it is to validate user input in chapter 7, as well as how you can use DataAnnotation attributes to declaratively describe your validation requirements of a model. In this section you’ll learn how to reuse this knowledge to validate your Razor Page binding models. The good news is that validation is built into the Razor Pages framework.

16.3.1 Validation in Razor Pages

In chapter 7 you learned that validation is an essential part of any web application. Nevertheless, minimal APIs don’t have any direct support for validation in the framework; you have to layer it on top using filters and additional packages.

In Razor Pages, validation is built in. Validation occurs automatically after model binding but before the page handler executes, as you saw in figure 16.2. Figure 16.6 shows a more compact view of where model validation fits in this process, demonstrating how a request to a checkout page that requests a user’s personal details is bound and validated.

alt text

Figure 16.6 Validation occurs after model binding but before the page handler executes. The page handler executes whether or not validation is successful.

As discussed in chapter 7, validation isn’t only about protecting against security threats, it’s also about ensuring that

• Data is formatted correctly. (Email fields have a valid email format.)
• Numbers are in a particular range. (You can’t buy -1 copies of a product.)
• Required values are provided while others are optional. (Name may be required, but phone number is optional.)
• Values conform to your business requirements. (You can’t convert a currency to itself, it needs to be converted to a different currency.)

It might seem like some of these can be dealt with easily enough in the browser. For example, if a user is selecting a currency to convert to, don’t let them pick the same currency; and we’ve all seen the “please enter a valid email address” messages.

Unfortunately, although this client-side validation is useful for users, as it gives them instant feedback, you can never rely on it, as it will always be possible to bypass these browser protections. It’s always necessary to validate the data as it arrives at your web application using server-side validation.

Warning Always validate user input on the server side of your application.

If that feels a little redundant, like you’ll be duplicating logic and code between your client and server applications, I’m afraid you’re right. It’s one of the unfortunate aspects of web development; the duplication is a necessary evil. Fortunately, ASP.NET Core provides several features to try to reduce this burden.

Tip Blazor, the new C# single-page application (SPA) framework, promises to solve some of these problems. For details, see http://mng.bz/9D51 and Blazor in Action, by Chris Sainty (Manning, 2021).

If you had to write this validation code fresh for every app, it would be tedious and likely error-prone. Luckily, you can use DataAnnotations attributes to declaratively describe the validation requirements for your binding models. The following listing, first shown in chapter 7, shows how you can decorate a binding model with various validation attributes. This expands on the example you saw earlier in listing 16.4.

Listing 16.6 Adding DataAnnotations to a binding model to provide metadata

public class UserBindingModel
{
[Required] ❶
[StringLength(100)] ❷
[Display(Name = "Your name")] ❸
public string FirstName { get; set; }
[Required]
[StringLength(100)]
[Display(Name = "Last name")]
public string LastName { get; set; }
[Required]
[EmailAddress] ❹
public string Email { get; set; }
[Phone] ❺
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
}

❶ Values marked Required must be provided.
❷ The StringLengthAttribute sets the maximum length for the property.
❸ Customizes the name used to describe the property
❹ Validates that the value of Email is a valid email address
❺ Validates that the value of PhoneNumber has a valid telephone format

For validation requirements that don’t lend themselves to attributes, such as when the validity of one property depends on the value of another, you can implement IValidatableObject, as described in chapter 7. Alternatively, you can use a different validation framework, such as FluentValidation, as you’ll see in chapter 32.

Whichever validation approach you use, it’s important to remember that these techniques don’t protect your application by themselves. The Razor Pages framework automatically executes the validation code after model binding, but it doesn’t do anything different if validation fails! In the next section we’ll look at how to check the validation result on the server and handle the case where validation has failed.

16.3.2 Validating on the server for safety

Validation of the binding model occurs before the page handler executes, but note that the handler always executes, whether the validation failed or succeeded. It’s the responsibility of the page handler to check the result of the validation.

NOTE Validation happens automatically, but handling validation failures is the responsibility of the page handler.

The Razor Pages framework stores the output of the validation attempt in a property on the PageModel called ModelState. This property is a ModelStateDictionary object, which contains a list of all the validation errors that occurred after model binding, as well as some utility properties for working with it.

As an example, listing 16.7 shows the OnPost page handler for the Checkout.cshtml Razor Page. The Input property is marked for binding and uses the UserBindingModel type shown previously in listing 16.6. This page handler doesn’t do anything with the data currently, but the pattern of checking ModelState early in the method is the key takeaway here.

Listing 16.7 Checking model state to view the validation result

public class CheckoutModel : PageModel ❶
{
[BindProperty] ❷
public UserBindingModel Input { get; set; } ❷
public IActionResult OnPost() ❸
{
if (!ModelState.IsValid) ❹
{
return Page(); ❺
}
/* Save to the database, update user, return success */ ❻
return RedirectToPage("Success");
}
}

❶ The ModelState property is available on the PageModel base class.
❷ The Input property contains the model-bound data.
❸ The binding model is validated before the page handler is executed.
❹ If there were validation errors, IsValid will be false.
❺ Validation failed, so redisplay the form with errors and finish the method early.
❻ Validation passed, so it’s safe to use the data provided in the model.

If the ModelState property indicates that an error occurred, the method immediately calls the Page() helper method. This returns a PageResult that ultimately generates HTML to return to the user, as you saw in chapter 15. The view uses the (invalid) values provided in the Input property to repopulate the form when it’s displayed, as shown in figure 16.7. Also, helpful messages for the user are added automatically, using the validation errors in the ModelState property.

alt text

Figure 16.7 When validation fails, you can redisplay the form to display ModelState validation errors to the user. Note that the Your Name field has no associated validation errors, unlike the other fields.

NOTE The error messages displayed on the form are the default values for each validation attribute. You can customize the message by setting the ErrorMessage property on any of the validation attributes. For example, you could customize a [Required] attribute using [Required(ErrorMessage="Required")].

If the request is successful, the page handler returns a RedirectToPageResult (using the RedirectToPage() helper method) that redirects the user to the Success.cshtml Razor Page. This pattern of returning a redirect response after a successful POST is called the POST-REDIRECT-GET pattern.

POST-REDIRECT-GET
The POST-REDIRECT-GET design pattern is a web development pattern that prevents users from accidentally submitting the same form multiple times. Users typically submit a form using the standard browser POST mechanism, sending data to the server. This is the normal way by which you might take a payment, for example.

If a server takes the naive approach and responds with a 200 OK response and some HTML to display, the user will still be on the same URL. If the user refreshes their browser, they will be making an additional POST to the server, potentially making another payment! Browsers have some mechanisms to prevent this, such as in the following figure, but the user experience isn’t desirable.

alt text

Refreshing a browser window after a POST causes a warning message to be shown to the user

The POST-REDIRECT-GET pattern says that in response to a successful POST, you should return a REDIRECT response to a new URL, which will be followed by the browser making a GET to the new URL. If the user refreshes their browser now, they’ll be refreshing the final GET call to the new URL. No additional POST is made, so no additional payments or side effects should occur.

This pattern is easy to achieve in ASP.NET Core applications using the pattern shown in listing 16.7. By returning a RedirectToPageResult after a successful POST, your application will be safe if the user refreshes the page in their browser.

You might be wondering why ASP.NET Core doesn’t handle invalid requests for you automatically; if validation has failed, and you have the result, why does the page handler get executed at all? Isn’t there a risk that you might forget to check the validation result?

This is true, and in some cases the best thing to do is to make the generation of the validation check and response automatic. In fact, this is exactly the approach we will use for web APIs using MVC controllers with the [ApiController] attribute when we cover them in chapter 20.

For Razor Pages apps, however, you typically still want to generate an HTML response, even when validation failed. This allows the user to see the problem and potentially correct it. This is much harder to make automatic.

For example, you might find you need to load additional data before you can redisplay the Razor Page, such as loading a list of available currencies. That becomes simpler and more explicit with the ModelState.IsValid pattern. Trying to do that automatically would likely end up with you fighting against edge cases and workarounds.

Also, by including the IsValid check explicitly in your page handlers, it’s easier to control what happens when additional validation checks fail. For example, if the user tries to update a product, the DataAnnotation validation won’t know whether a product with the requested ID exists, only whether the ID has the correct format. By moving the validation to the handler method, you can treat data and business rule validation failures in the same way.

Tip You can also add extra validation errors to the collection, such as business rule validation errors that come from a different system. You can add errors to ModelState by calling AddModelError(), which will be displayed to users on the form alongside the DataAnnotation attribute errors.

I hope I’ve hammered home how important it is to validate user input in ASP.NET Core, but just in case: VALIDATE! There, we’re good. Having said that, performing validation only on the server can leave users with a slightly poor experience. How many times have you filled out a form online, submitted it, gone to get a snack, and come back to find out you mistyped something and have to redo it? Wouldn’t it be nicer to have that feedback immediately?

16.3.3 Validating on the client for user experience

You can add client-side validation to your application in a few different ways. HTML5 has several built-in validation behaviors that many browsers use. If you display an email address field on a page and use the “email” HTML input type, the browser automatically stops you from submitting an invalid format, as shown in figure 16.8. Your application doesn’t control this validation; it’s built into modern HTML5 browsers.

NOTE HTML5 constraint validation support varies by browser. For details on the available constraints, see the Mozilla documentation (http://mng.bz/daX3) and https://caniuse.com/#feat=constraint-validation.

alt text

Figure 16.8 By default, modern browsers automatically validate fields of the email type before a form is submitted.

The alternative approach to HTML validation is to perform client-side validation by running JavaScript on the page and checking the values the user entered before submitting the form. This is the most common approach used in Razor Pages.

I’ll go into detail on how to generate the client-side validation helpers in chapter 18, where you’ll see the DataAnnotation attributes come to the fore once again. By decorating a view model with these attributes, you provide the necessary metadata to the Razor engine for it to generate the appropriate validation HTML.

With this approach, the user sees any errors with their form immediately, even before the request is sent to the server, as shown in figure 16.9. This gives a much shorter feedback cycle, providing a better user experience.

alt text

Figure 16.9 With client-side validation, clicking Submit triggers validation to be shown in the browser before the request is sent to the server. As shown in the right pane, no request is sent.

If you’re building an SPA, the onus is on the client-side framework to validate the data on the client side before posting it to the API. The API must still validate the data when it arrives at the server, but the client-side framework is responsible for providing the smooth user experience.

When you use Razor Pages to generate your HTML, you get much of this validation code for free. Razor Pages automatically configures client-side validation for most of the built-in attributes without requiring additional work, as you’ll see in chapter 18. Unfortunately, if you’ve used custom ValidationAttributes, these will run only on the server by default; you need to do some additional wiring up of the attribute to make it work on the client side too. Despite this, custom validation attributes can be useful for handling common validation scenarios in your application, as you’ll see in chapter 31.

The model binding framework in ASP.NET Core gives you a lot of options on how to organize your Razor Pages: page handler parameters or PageModel properties; one binding model or multiple; options for where to define your binding model classes. In the next section I give some advice on how I like to organize my Razor Pages.

16.4 Organizing your binding models in Razor Pages

In this section I give some general advice on how I like to configure the binding models in my Razor Pages. If you follow the patterns in this section, your Razor Pages will follow a consistent layout, making it easier for others to understand how each Razor Page in your app works.

NOTE This advice is just personal preference, so feel free to adapt it if there are aspects you don’t agree with. The important thing is to understand why I make each suggestion, and to take that on board. Where appropriate, I deviate from these guidelines too!

Model binding in ASP.NET Core has a lot of equivalent approaches to take, so there is no “correct” way to do it. Listing 16.8 shows an example of how I would design a simple Razor Page. This Razor Page displays a form for a product with a given ID and allows you to edit the details using a POST request. It’s a much longer sample than we’ve looked at so far, but I highlight the important points.

Listing 16.8 Designing an edit product Razor Page

public class EditProductModel : PageModel
{
private readonly ProductService _productService; ❶
public EditProductModel(ProductService productService) ❶
{ ❶
_productService = productService; ❶
} ❶
[BindProperty] ❷
public InputModel Input { get; set; } ❷
public IActionResult OnGet(int id) ❸
{
var product = _productService.GetProduct(id); ❹
Input = new InputModel ❺
{ ❺
Name = product.ProductName, ❺
Price = product.SellPrice, ❺
}; ❺
return Page(); ❺
}
public IActionResult OnPost(int id) ❻
{
if (!ModelState.IsValid) ❼
{ ❼
return Page(); ❼
} ❼
_productService.UpdateProduct(id, Input.Name, Input.Price); ❽
return RedirectToPage("Index"); ❾
}
public class InputModel ❿
{ ❿
[Required] ❿
public string Name { get; set; } ❿
[Range(0, int.MaxValue)] ❿
public decimal Price { get; set; } ❿
} ❿
}

❶ The ProductService is injected using DI and provides access to the application model.
❷ A single property is marked with BindProperty.
❸ The id parameter is model-bound from the route template for both OnGet and OnPost handlers.
❹ Loads the product details from the application model
❺ Builds an instance of the InputModel for editing in the form from the existing product’s details
❻ The id parameter is model-bound from the route template for both OnGet and OnPost handlers.
❼ If the request was not valid, redisplays the form without saving
❽ Updates the product in the application model using the ProductService
❾ Redirects to a new page using the POST-REDIRECT-GET pattern
❿ Defines the InputModel as a nested class in the Razor Page

This page shows the PageModel for a typical “edit form.” These are common in many line-of-business applications, among others, and it’s a scenario that Razor Pages works well for. You’ll see how to create the HTML side of forms in chapter 18.

NOTE The purpose of this example is to highlight the model-binding approach. The code is overly simplistic from a logic point of view. For example, it doesn’t check that the product with the provided ID exists or include any error handling.

This form shows several patterns related to model binding that I try to adhere to when building Razor Pages:

• Bind only a single property with [BindProperty]. I favor having a single property decorated with [BindProperty] for model binding in general. When more than one value needs to be bound, I create a separate class, InputModel, to hold the values, and I decorate that single property with [BindProperty]. Decorating a single property like this makes it harder to forget to add the attribute, and it means all your Razor Pages use the same pattern.

• Define your binding model as a nested class. I define the InputModel as a nested class inside my Razor Page. The binding model is normally highly specific to that single page, so doing this keeps everything you’re working on together. Additionally, I normally use that exact class name, InputModel, for all my pages. Again, this adds consistency to your Razor Pages.

• Don’t use [BindProperties]. In addition to the [BindProperty] attribute, there is a [BindProperties] attribute (note the different spelling) that can be applied to the Razor Page PageModel directly. This will cause all properties in your model to be model-bound, which can leave you open to overposting attacks if you’re not careful. I suggest you don’t use the [BindProperties] attribute and stick to binding a single property with [BindProperty] instead.

• Accept route parameters in the page handler. For simple route parameters, such as the id passed into the OnGet and OnPost handlers in listing 16.8, I add parameters to the page handler method itself. This avoids the clunky SupportsGet=true syntax for GET requests.
• Always validate before using data. I said it before, so I’ll say it again: validate user input!

That concludes this look at model binding in Razor Pages. You saw how the ASP.NET Core framework uses model binding to simplify the process of extracting values from a request and turning them into normal .NET objects you can work with quickly. The most important aspect of this chapter is the focus on validation. This is a common concern for all web applications, and the use of DataAnnotations can make it easy to add validation to your models.

In the next chapter we’ll continue our journey through Razor Pages by looking at how to create views. In particular, you’ll learn how to generate HTML in response to a request using the Razor templating engine.

16.5 Summary

Razor Pages uses three distinct models, each responsible for a different aspect of a request. The binding model encapsulates data sent as part of a request. The application model represents the state of the application. The PageModel is the backing class for the Razor Page, and it exposes the data used by the Razor view to generate a response.

Model binding extracts values from a request and uses them to create .NET objects the page handler can use when they execute. Any properties on the PageModel marked with the [BindProperty] attribute and method parameters of the page handlers will take part in model binding.

By default, there are three binding sources for Razor Pages: POSTed form values, route values, and the query string. The binder will interrogate these sources in order when trying to bind your binding models.

When binding values to models, the names of the parameters and properties aren’t case-sensitive.

You can bind to simple types or to the properties of complex types. Simple types must be convertible from strings to be bound automatically, such as numbers, dates, Boolean values, and custom types with a TryParse method.

To bind complex types, the types must have a default constructor and public, settable properties. The Razor Pages model binder binds each property of a complex type using values from the binding sources.

You can bind collections and dictionaries using the [index]=value and [key] =value syntax, respectively.

You can customize the binding source for a binding model using [From*] attributes applied to the method, such as [FromHeader] and [FromBody]. These can be used to bind to nondefault binding sources, such as headers or JSON body content. The [FromBody] attribute is always required when binding to a JSON body.

Validation is necessary to check for security threats. Check that data is formatted correctly and confirm that it conforms to expected values and that it meets your business rules.

Validation in Razor Pages occurs automatically after model binding, but you must manually check the result of the validation and act accordingly in your page handler by interrogating the ModelState.IsValid property.

Client-side validation provides a better user experience than server-side validation alone, but you should always use server-side validation. Client-side validation typically uses JavaScript and attributes applied to your HTML elements to validate form values.

Leave a Reply

Your email address will not be published. Required fields are marked *