Return HTML with Razor Pages in a ASP.NET Core Minimal API Project
One of my favorite things about working with ASP.NET Core is the framework gives you the ability to...
Model binding in ASP.NET is a convenient way to convert data from an HTTP request into an object to be used in your application code. In this article, I will review the process of binding data using Razor pages. This review is focused on the model binding process of converting the request data to .NET types with the assumption that data has been successfully sent from the client. I am a big fan of Razor Pages, and the model binding system makes working with request data painless.
The three methods to bind data from a request to a page model.
I will review each method in order and provide examples. Let’s get started!
The first approach is a good option if you need Request data to perform some logic within a given page handler and don’t need to set any properties on your class. Let’s look at an example.
POST Request
We get the id
as a page handler parameter in the preceding code. The id
route parameter gets converted to an int during the modeling binding process. Now that we have access to the id
in the OnPost
handler, we can use it to write any logic in the handler’s scope.
It’s worth noting that we would get the same result if the id
was passed as a query string parameter.
POST Request URL
Or even as form data:
POST Request Body
I will cover more about binding sources and how the model binder chooses which object to bind to later. I will also share how you can override the default behavior.
Let’s move on to another way to declare which objects should be included in the model binding process.
What if we want to set the id
as a property on the PageModel
class? One option would be to create an id
property and then manually set the id
property using the id
page handler parameter like so:
This works, but there is a better way to update the id
property that will be less verbose, especially if we have many page handler parameters. We can use the BindProperty
attribute to rewrite the preceding code.
So far, I’ve shown you two different ways to bind day to a PageModel
. We can use multiple methods of binding our data. For example, we can bind some data via page handler parameters and some data with the BindProperty
attribute. Suppose we send the following post request:
Request URL
Request Body
I placed a breakpoint set at the beginning of the scope of the page handler so that we could observe the values of the properties.
Using Visual Studio debugger, we can observe that the id
has a value of 23
, and the Email
and Message
properties have been updated with the values from the request body.
You can access bound data as page handler parameters on GET
requests.
You can use the BindPropertyAttribute
to bind data on GET
requests, but you must opt into this behavior for security purposes.
The code looks very similar to the POST
request, with the only difference being the SupportsGet=true
parameter.
This property enables binding for all properties on the page model. 👀
I have never used this method, but it’s good to know this feature is available if needed. I prefer the page handler parameter or BindPropertyAttribute
as it offers more control. What are your thoughts on this approach? Let me know in the comments.
I have demonstrated how model binding works with Razor pages, considering there are three ways to bind data from a request. It is essential to understand the order of precedence for model binding.
Here are the three ways data will be bound to the model in order, with form values taking the highest priority.
Consider the following example POST
request.
Form Body
URL
Page Handler
What is the value of firstname
and lastname
after a successful request to the OnPost
handler? This is not a trick question. If you said firstname=Will
and lastname=Pickeral
, you are correct!. Not because my name is actually Will Pickeral, but because Form values take priority over route parameters, and route parameters take priority over query parameters in the model bounding process.
So far, I have only mentioned model binding with simple types. Most of the time, I need to bind to a property with a complex type such as with the example below.
The model binder will set the FirstName
and LastName
properties on an instance of the Person
class and assign
the object to the Person
property on the IndexModel. We can now use the Person
property to perform any logic in our class.
I typically will use the BindPropertyAttribute
and follow the pattern described by Andrew Lock in is book ASP.NET in Action,
in which all binding properties live in an InputModel
nested class and then only bind to the InputModel
. I have found
this pattern to be very clean and fun to work with.
Here is an example:
You cannot decorate a property with BindProperty
on the Input property and add BindProperty(SupportsGet=True)
to a property on the InputModel
.
Remember that if you follow the InputModel
pattern, you do not include any properties you want to bind on a GET
request. Or you if find yourself wondering why your data won’t bind, make sure you are not making this mistake. It happened to me, which is why I am sharing this tip with you.
You can move those properties to the PageModel
class alongside your Input
property, and it will bind on the GET
request as expected.
In the preceding code, we decorate the CompanyName
property with BindProperty(SupportsGet=true)
within the InputModel
class, which is already decorated with the BindProperty
attribute.
When submitting a form, the model binder looks at the name
attribute value on an HTML input element to determine which object to bind the data to. If you use the asp-for
attribute, by default the name will be set to the property name, which works because the model binder will successfully bind to that value.
In some cases, you will need to override the name value. The model binder will not bind the value to a property on the class unless you also change the name of your property to match the name
attribute of the input element. The value is not case sensitive, so a property name FirstName
will bind to an input element value with a name value of firstname
.
Consider we had the following form for the Contact page.
Which would render in the browser as:
The Input.Email
and Input.Message
properties will bind as expected because the form name
values’ matches the binding object’s property names.
Let’s manually set the name
attribute on the email input element in our Razor page like so,
We override the default name
value. The model binder would not find a property on the binding object that matches the name emailAddress
, so this value would not bind.
Keep this in mind when you have to set the name value manually for specific use cases, such as when working with radio buttons.
The framework does provide additional Binding Attributes not covered in this article to provide more control over the binding source. Here are a few examples.
Although this article did not cover model validation, it is essential to remember to validate all user input. Razor Pages makes this process easy by allowing the developer to declaratively set model validation on the input model. The validation results will be available after the model binding process before the page handler is executed. Learn more about validation in Razor pages.
This article reviewed the three ways to set the model binding in Razor pages: page handler parameters, the BindPropertyAttribute
, and the BindPropertiesAttribute
. I focused some of the ways I have worked with model binding in ASP.NET Razor pages and shared some lessons learned. Check out the official Microsoft documentation to learn more about Model Binding In ASP.NET. Thanks for reading!