28. Using Model Binding Flashcards

1
Q

What is Model Binding?

A

Model binding is the process of creating the objects that action methods and page handlers require using data values obtained from the HTTP request.

Model binding is the process of creating .NET objects using the values from the HTTP request to provide easy access to the data required by action methods and Razor Pages.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Why isModel Binding useful?

A

Model binding lets controllers or page handlers declare method parameters or properties using C# types and automatically receive data from the request without having to inspect, parse, and process the data directly.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

How is Model Binding used?

A

In its simplest form, methods declare parameters or classes define properties whose names are used to retrieve data values from the HTTP request. The part of the request used to obtain the data can be configured by applying attributes to the method parameters or properties.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Are there any pitfalls or limitations to Model Binding?

A

The main pitfall is getting data from the wrong part of the request. I explain the way that requests are searched for data in the “Understanding Model Binding” section, and the search locations can be specified explicitly using the attributes that I describe in the “Specifying a Model Binding Source” section.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Are there any alternatives to Model Binding?

A

Data can be obtained without model binding using context objects. However, the result is more complicated code that is hard to read and maintain.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Model binding example?

A

You can see model binding at work by using the browser to request
http://localhost:5000/controllers/form/index/5.
This URL contains the value of the ProductId property of the Product object that I want to view, like this:
The number 5 in the URL corresponds to the id segment variable defined by the controller routing pattern and matches the name of the parameter defined by the Form controller’s Index action:

public async Task Index(long id = 1) {

A value for the id parameter is required before the MVC Framework can invoke the action method, and finding a suitable value is the responsibility of the model binding system.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

What do model binding systems rely on?

A

The model binding system relies on model binders, which are components responsible for providing data values from one part of the request or application. The default model binders look for data values in these four places:

•Form data (Data from a Form that the user has filled out)
•The request body (only for controllers decorated with ApiController)
•Routing segment variables
http://localhost:5000/controllers/Form/Index/5
•Query
http://localhost:5000/controllers/Form/Index?id=4

Each source of data is inspected in order until a value for the argument is found. The search stops after a suitable data value has been found.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Can ypu specify the source of model binding data, and if so how?

A

TipIn the “Specifying a Model Binding Source” section, I explain how you can specify the source of model binding data using attributes. This allows you to specify that a data value is obtained from, for example, the query string, even if there is also suitable data in the routing data.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Explain the binding of simple data types.

A

Listing below adds parameters to the SubmitForm action method defined by the Form controller method so that the model binder will be used to provide name and price values.

[HttpPost]
        public IActionResult SubmitForm(string name, decimal price) {
            TempData["name param"] = name;
            TempData["price param"] = price.ToString();
            return RedirectToAction(nameof(Results));
        }

The model binding system will be used to obtain name and price values when ASP.NET Core receives a request that will be processed by the SubmitForm action method. The use of parameters simplifies the action method and takes care of converting the request data into C# data types so that the price value will be converted to the C# decimal type before the action method is invoked. (I had to convert the decimal back to a string to store it as temp data in this example. I demonstrate more useful ways of dealing with form data in Chapter 31.)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Why does Data binding for simple types make it easy to extract single data items from the request?

A

Data binding for simple types makes it easy to extract single data items from the request without having to work through the context data to find out where it is defined.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

What is the problem when Binding Simple Data Types in Razor Pages?

A

Razor Pages can use model binding, but care must be taken to ensure that the value of the form element’s name attribute matches the name of the handler method parameter, which may not be the case if the asp-for attribute has been used to select a nested property. To ensure the names match, the name attribute can be defined explicitly like so:

The tag helper would have set the name attribute of the input element to Product.Name, which prevents the model binder from matching the values. Explicitly setting the name attribute overrides the tag helper and ensures the model binding process works correctly.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

What happens if data values cannot be located?

A

Model binding is a best-effort feature, which means the model binder will try to get values for method parameters but will still invoke the method if data values cannot be located.
An exception isn’t reported by the model binding system. Instead, it is reported when the Entity Framework Core query is executed.
When there is no value available for model binding, the action method tries to query the database with an id of zero. There is no such object, which causes sn error when Entity Framework Core tries to process the result.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

How do you deal with the issue if no data value is located?

A

Applications must be written to cope with default argument values, which can be done in several ways. You can add fallback values to the routing URL patterns used by controllers (as shown in Chapter 21) or pages (as shown in Chapter 23). You can assign default values when defining the parameter in the action or page handler method, or you can simply write methods that accommodate the default values without causing an like the following:

public async Task Index(long id) {
ViewBag.Categories = new SelectList(context.Categories, “CategoryId”, “Name”);
return View(“Form”, await context.Products.Include(p => p.Category)
.Include(p => p.Supplier).FirstOrDefaultAsync(p => p.ProductId == id));
}

The Entity Framework Core FirstOrDefaultAsync method will return null if there is no matching object in the database and won’t attempt to load related data. The tag helpers cope with null values and display empty fields,

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

How to differentiate between a missing value and any value provided by the user?

A

Some applications need to differentiate between a missing value and any value provided by the user. In these situations, a nullable parameter type can be used.

public async Task Index(long? id) {
ViewBag.Categories = new SelectList(context.Categories, “CategoryId”, “Name”);
return View(“Form”, await context.Products.Include(p => p.Category)
.Include(p => p.Supplier)
.FirstOrDefaultAsync(p => id == null || p.ProductId == id));
}

The id parameter will be null only if the request doesn’t contain a suitable value, which allows the expression passed to the FirstOrDefaultAsync method to default to the first object in the database when there is no value and to query for any other value.

Example:
http://localhost:5000/controllers/Form and http://localhost:5000/controllers/Form/index/0. The first URL contains no id value, so the first object in the database is selected. The second URL provides an id value of zero, which doesn’t correspond to any object in the database.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

What are complex types?

A

any type that cannot be parsed from a single string value

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

How does the model binding system deal with complex types?

A

The model binding system shines when dealing with complex types, which are any type that cannot be parsed from a single string value. The model binding process inspects the complex type and performs the binding process on each of the public properties it defines. This means that instead of dealing with individual values such as name and price, I can use the binder to create complete Product objects.

Example:
public IActionResult SubmitForm(Product product)
{
TempData[“product”] = System.Text.Json.JsonSerializer.Serialize(product);
return RedirectToAction(nameof(Results));
}

Before the action method is invoked, a new Product object is created, and the model binding process is applied to each of its public properties. The SubmitForm method is then invoked, using the Product object as its argument.

The Product object created by the model binding process is serialized as JSON data so that it can be stored as temp data, making it easy to see the request data.
{“ProductId”:0,”Name”:”Kayak”,”Price”:275.00,”CategoryId”:0,”Category”:null,”SupplierId”:0,”Supplier”:null}

17
Q

How do you bind to a property?

A

When the BindProperty attribute is used, the model binder uses the property name when locating data values, so the explicit name attributes added to the input element are not required. By default, BindProperty won’t bind data for GET requests, but this can be changed by setting the BindProperty attribute’s SupportsGet argument to true.
The BindProperties attribute can be applied to classes that require the model binding process for all the public properties they define, which can be more convenient than applying BindProperty to many individual properties. Decorate properties with the BindNever attribute to exclude them from model binding

[BindProperty]
public Product Product { get; set; }

public IActionResult OnPost() {
            TempData["product"] = System.Text.Json.JsonSerializer.Serialize(Product);
            return RedirectToPage("FormResults");
}
18
Q

What is Binding to a Property?

A

Decorating a property with the BindProperty attribute indicates that its properties should be subject to the model binding process, which means the OnPost handler method can get the data it requires without declaring a parameter.

19
Q

How to bind nested compex types?

A
If a property that is subject to model binding is defined using a complex type, then the model binding process is repeated using the property name as a prefix. For example, the Product class defines the Category property, whose type is the complex Category type. 
The name attribute combines the property names, separated by periods. In this case, the element is for the Name property of the object assigned to the view model’s Category property, so the name attribute is set to Category.Name. The input element tag helper will automatically use this format for the name attribute when the asp-for attribute is applied

<div>
Category Name

</div>

During the model binding process, a new Category object is created and assigned to the Category property of the Product object. The model binder locates the value for the Category object’s Name property

20
Q

How to Specify Custom Prefixes for Nested Complex Types?

A

There are occasions when the HTML you generate relates to one type of object but you want to bind it to another. This means that the prefixes containing the view won’t correspond to the structure that the model binder is expecting, and your data won’t be properly processed.
This problem is solved by applying the Bind attribute to the parameter and using the Prefix argument to specify a prefix for the model binder:


[HttpPost]
public IActionResult SubmitForm([Bind(Prefix =”Category”)] Category category) {
TempData[“category”] = System.Text.Json.JsonSerializer.Serialize(category);
return RedirectToAction(nameof(Results));
}

The syntax is awkward, but the attribute ensures the model binder can locate the data the action method requires. In this case, setting the prefix to Category ensures the correct data values are used to bind the Category parameter.

21
Q

Why do you need to selectively bind properties and how?

A

Some model classes define properties that are sensitive and for which the user should not be able to specify values. A user may be able to change the category for a Product object, for example, but should not be able to alter the price.
You might be tempted to simply create views that omit HTML elements for sensitive properties but that won’t prevent malicious users from crafting HTTP requests that contain values anyway, which is known as an over-binding attack. To prevent the model binder from using values for sensitive properties, the list of properties that should be bound can be specified:

public IActionResult SubmitForm([Bind("Name", "Category")] Product product) {
            TempData["name"] = product.Name;
            TempData["price"] = product.Price.ToString();
            TempData["category name"] = product.Category.Name;
            return RedirectToAction(nameof(Results));
}

I have returned to the Product type for the action method parameter, which has been decorated with the Bind attribute to specify the names of the properties that should be included in the model binding process. This example tells the model binding feature to look for values for the Name and Category properties, which excludes any other property from the process.
Even though the browser sends a value for the Price property as part of the HTTP POST request, it is ignored by the model binder.

22
Q

How to bind to arrays?

A

Model binding for an array requires setting the name attribute to the same value for all the elements that will provide an array value. This page displays three input elements, all of which have a name attribute value of Data. To allow the model binder to find the array values, I have decorated the page model’s Data property with the BindProperty attribute and used the Name argument.
When the HTML form is submitted, a new array is created and populated with the values from all three input elements, which are displayed to the user.
The contents of the Data array are displayed in a list using an @foreach expression

            <div class="form-group">
                Value #1

            </div>
            <div class="form-group">
                Value #2

            </div>
            <div class="form-group">
                    Value #3

            </div>
            Submit
            <a class="btn btn-secondary">Reset</a>

<ul>
@foreach (string s in Model.Data.Where(s => s != null)) {
<li>@s</li>
}
</ul>

@functions {
    public class BindingsModel : PageModel {
        [BindProperty(Name = "Data")]
        public string[] Data { get; set; } = Array.Empty();
    }
}
23
Q

How to Specify Index Positions for Array Values?

A

By default, arrays are populated in the order in which the form values are received from the browser, which will generally be the order in which the HTML elements are defined. The name attribute can be used to specify the position of values in the array if you need to override the default:

The index notation must be applied to all the HTML elements that provide array values, and there must not be any gaps in the numbering sequence.

24
Q

How to bind to simple collections?

A

The model binding process can create collections as well as arrays. For sequence collections, such as lists and sets, only the type of the property or parameter that is used by the model binder is changed.

public SortedSet Data { get; set; } = new SortedSet();

The model binding process will populate the set with the values from the input elements, which will be sorted alphabetically.

25
Q

How to bind to dictionaries?

A

For elements whose name attribute is expressed using the index notation, the model binder will use the index as the key when binding to a Dictionary, allowing a series of elements to be transformed into key/value pairs.

            <div class="form-group">
                Value #1

            </div>

            <div class="form-group">
                Value #2

            </div>

            <div class="form-group">
                    Value #3

            </div>

            Submit

            <a class="btn btn-secondary">Reset</a>

    <div class="col">

                @foreach (string key in Model.Data.Keys) {

                        @key@Model.Data[key]

                }

    </div>
@functions {
    public class BindingsModel : PageModel {
        [BindProperty(Name = "Data")]
        public Dictionary Data { get; set; }
            = new Dictionary();
    }
}

All elements that provide values for the collection must share a common prefix, which is Data in this example, followed by the key value in square brackets. The keys for this example are the strings first, second, and third, and will be used as the keys for the content the user provides in the text fields.
The keys and values from the form data will be displayed in a table.

26
Q

How to bind to collections of complex types?

A

Same as collections of simple types
The name attributes for the input elements use the array notation, followed by a period, followed by the name of the complex type properties they represent. To define elements for the Name and Price properties, this requires elements like this:


During the binding process, the model binder will attempt to locate values for all the public properties defined by the target type, repeating the process for each set of values in the form data.

27
Q

Explain the FromForm Model Binding Source Attribute

A

This attribute is used to select form data as the source of binding data. The name of the parameter is used to locate a form value by default, but this can be changed using the Name property, which allows a different name to be specified.

28
Q

Explain the FromRoute Model Binding Source Attribute

A

This attribute is used to select the routing system as the source of binding data. The name of the parameter is used to locate a route data value by default, but this can be changed using the Name property, which allows a different name to be specified.

29
Q

Explain the FromQuery Model Binding Source Attribute

A

This attribute is used to select the query string as the source of binding data. The name of the parameter is used to locate a query string value by default, but this can be changed using the Name property, which allows a different query string key to be specified.

public async Task Index([FromQuery] long? id)

30
Q

Explain the FromHeader Model Binding Source Attribute

A

This attribute is used to select a request header as the source of binding data. The name of the parameter is used as the header name by default, but this can be changed using the Name property, which allows a different header name to be specified.

31
Q

Explain the FromHeader Model Binding Source Attribute

A

This attribute is used to select a request header as the source of binding data. The name of the parameter is used as the header name by default, but this can be changed using the Name property, which allows a different header name to be specified.

public string Header([FromHeader]string accept) {
return $”Header: {accept}”;
}

The Header action method defines an accept parameter, the value for which will be taken from the Accept header in the current request and returned as the method result. Restart ASP.NET Core and request http://localhost:5000/controllers/form/header, and you will see a result like this:

Header: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp, image/apng,/;q=0.8,application/signed-exchange;v=b3

Configure the FromHeader attribute using the Name property to specify the name of the header:

public string Header([FromHeader(Name = “Accept-Language”)] string accept) {
return $”Header: {accept}”;
}

I can’t use Accept-Language as the name of a C# parameter, and the model binder won’t automatically convert a name like AcceptLanguage into Accept-Language so that it matches the header. Instead, I used the Name property to configure the attribute so that it matches the right header.

32
Q

Explain the FromBody Model Binding Source Attribute

A

This attribute is used to specify that the request body should be used as the source of binding data, which is required when you want to receive data from requests that are not form-encoded, such as in API controllers that provide web services.

Not all data sent by clients is sent as form data, such as when a JavaScript client sends JSON data to an API controller. The FromBody attribute specifies that the request body should be decoded and used as a source of model binding data.
The FromBody attribute isn’t required for controllers that are decorated with the ApiController attribute.

[HttpPost]
        [IgnoreAntiforgeryToken]
        public Product Body([FromBody] Product model) {
            return model;
}

Test Using following powershell command:
Invoke-RestMethod http://localhost:5000/controllers/form/body -Method POST -Body (@{ Name=”Soccer Boots”; Price=89.99} | ConvertTo-Json) -ContentType “application/json”

The JSON-encoded request body is used to model bind the action method parameter, which produces the following response:

productId  : 0
name       : Soccer Boots
price      : 89.99
categoryId : 0
category   :
supplierId : 0
supplier   :
33
Q

How to Select a Binding Source for a Property?

A

The use of the FromQuery attribute means the query string is used as the source of values for the model binder as it creates the Product array, which you can see by requesting http://localhost:5000/pages/bindings?data[0].name=Skis&data[0].price=500

public class BindingsModel : PageModel {
        [FromQuery(Name = "Data")]
        public Product[] Data { get; set; } = Array.Empty();
}

Note: In this example, I have used a GET request because it allows the query string to be easily set. Although it is harmless in such a simple example, care must be taken when sending GET requests that modify the state of the application. As noted previously, making changes in GET requests can lead to problems.

34
Q

How do you perform model binding manually?

A

Manual model binding is performed using the TryUpdateModelAsync method, which is provided by the PageModel and ControllerBase classes, which means it is available for both Razor Pages and MVC controllers.

 public async Task OnPostAsync([FromForm] bool bind) {
            if (bind) {
               await TryUpdateModelAsync(Data,
                   "data", p => p.Name, p => p.Price);
            }
        }

Manual model binding is performed using the TryUpdateModelAsync method, which is provided by the PageModel and ControllerBase classes, which means it is available for both Razor Pages and MVC controllers.
This example mixes automatic and manual model binding. The OnPostAsync method uses automatic model binding to receive a value for its bind parameter, which has been decorated with the FromForm attribute. If the value of the parameter is true, the TryUpdateModelAsync method is used to apply model binding. The arguments to the TryUpdateModelAsync method are the object that will be model bound, the prefix for the values, and a series of expressions that select the properties that will be included in the process, although there are other versions of the TryUpdateModelAsync method available.