ASP.NET Core Model Binding – Advanced

In this section, we use examples to explain the advanced concepts of model binding. We explain them from these dimensions:

1 Model binding array type

2 Model binding collection type

3 Model binding complex collection types

4 Model binding sources

1 Model binding array type

Using model binding we can bind array type parameters to the Action method. In order to verify this example, create a Places method in HomeController with the following code:

using AspNetCore.ModelBinding.Advanced.Models;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
namespace AspNetCore.ModelBinding.Advanced.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;
        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }
        public IActionResult Palces(string[] places) => View(places);
    }
}

Note that this Action method has a parameter of type string and is named places. The model binding mechanism will search for items named places in the form data, routing variables and query strings. If found, it will be bound to the parameters of the method.

Next, create a view file named Places in the Views->Home folder and add the following code:

@model string []
@{
    ViewData["Title"] = "Places";
}
<h2></h2>
@if (@Model.Length == 0)
{
    <form asp-action="Places" method="post">
        @for (int i = 1; i <= 3; i + + )
        {
            <div class="form-group">
                <label>Place @i</label>
                <input name="places" class="form-controller" />
            </div>
        }
        <button type="submit" class="btn btn-primary">Submit</button>
    </form>
}
else
{
    <table class="table table-sm table-bordered table-striped">
        @foreach (var place in Model)
        {
            <tr>
                <td>Place</td>
                <td>@place</td>
            </tr>
        }
    </table>
    <a asp-action="Places" class="btn btn-primary">Return</a>
}

The view contains a model of array type. The if-else block statement checks whether it is empty. It uses the code – @Model.Length == 0 to judge. If it is not empty, use a loop to traverse the model and display it. Otherwise, in this case Below, if the model is empty, the form will add 3 places and display 3 identical input boxes.

These 3 identical input elements have their own names places– They have the same parameter name in the action method – string[] places , so when the form is submitted, model binding will get the values from all input controls and create an array containing these values. This array is bound to the method parameters.

Run the project and enter the url initialization Places method – /Home/Places. Now the page page checks three html input elements. The code is as follows:

<div class="form-group">
                <label>Place 1</label>
                <input name="places" class="form-controller" />
            </div>
            <div class="form-group">
                <label>Place 2</label>
                <input name="places" class="form-controller" />
            </div>
            <div class="form-group">
                <label>Place 3</label>
                <input name="places" class="form-controller" />
            </div>

You can see that all 3 input elements have the same name places, and the parameter names of the action methods with this name are the same, so the model binding will apply an array binding.

Now enter 3 place names on the form and submit. Submitting these three values will be displayed in the browser:

7fe5e7e3c5433272594f2cc640d0028f.png

8df8137d25e20dada4e7d32a69865011.png

2 Model Binding Collection Type

Model binding can also bind collection types. Collection types are widely used in C#. Let’s learn about it through an example. Let’s update the previous example and change the parameter from an array type to a strongly typed collection.

Therefore, change the parameter type of the Places method from string[] to List

public IActionResult Places(List<string> places) => View(places);

Let’s slightly modify the Places.cshtml view file:

1 Change the model type to List

2 Use Model.Count in if code block

code show as below:

@model List<string>
@{
    ViewData["Title"] = "Places";
}
<h2></h2>
@if (@Model.Count == 0)
{
    <form asp-action="Places" method="post">
        @for (int i = 1; i <= 3; i + + )
        {
            <div class="form-group">
                <label>Place @i</label>
                <input name="places" class="form-controller" />
            </div>
        }
        <button type="submit" class="btn btn-primary">Submit</button>
    </form>
}
else
{
    <table class="table table-sm table-bordered table-striped">
        @foreach (var place in Model)
        {
            <tr>
                <td>Place</td>
                <td>@place</td>
            </tr>
        }
    </table>
    <a asp-action="Places" class="btn btn-primary">Return</a>
}

You can run and submit the form as before. When submitting, the 3 places text you added will be displayed in the browser.

3 Model binding to complex collection types

Now we use model binding to bind a collection of complex types, so create a PersonAddress class with two properties – City & Country

public class PersonAddress
{
    public string City { get; set; }
    public string Country { get; set; }
}

Next, enter the Home controller and add a new method named Address. The code of the specific method is as follows:

public IActionResult Address() => View();
[HttpPost]
public IActionResult Address(List<PersonAddress> address) => View(address);

Next, create a view named Address in the Views->Home folder with the following code:

@model List<PersonAddress>
@{
    ViewData["Title"] = "Address";
}
<h2>Address</h2>
@if (Model.Count == null)
{
    <form asp-action="Address" method="post">
        @for (int i = 0; i < 3; i + + )
        {
            <fieldset class="form-group">
                <legend>Address @(i + 1)</legend>
                <div class="form-group">
                    <label>City:</label>
                    <input name="[@i].City" class="form-control" />
                </div>
                <div class="form-group">
                    <label>Country:</label>
                    <input name="[@i].Country" class="form-control" />
                </div>
            </fieldset>
        }
        <button type="submit" class="btn btn-primary">Submit</button>
    </form>
}
else
{
    <table class="table table-sm table-bordered table-striped">
        <tr><th>City</th><th>Country</th></tr>
        @foreach (var address in Model)
        {
            <tr><td>@address.City</td><td>@address.Country</td></tr>
        }
    </table>
    <a asp-action="Address" class="btn btn-primary">Back</a>
}

The Model in the view contains a PersonAddress collection, which is a complex type collection. When no data is displayed in the model, it renders a form. The form contains 3 pairs of City and Country input box prefixes using array indexes. The specific code is as follows:

<fieldset class="form-group">
   <legend>Address 1</legend>
       <div class="form-group">
            <label>City:</label>
            <input name="[0].City" class="form-control" />
        </div>
        <div class="form-group">
             <label>Country:</label>
             <input name="[0].Country" class="form-control" />
        </div>
</fieldset>
<fieldset class="form-group">
   <legend>Address 2</legend>
   <div class="form-group">
        <label>City:</label>
        <input name="[1].City" class="form-control" />
    </div>
    <div class="form-group">
         <label>Country:</label>
         <input name="[1].Country" class="form-control" />
    </div>
</fieldset>
<fieldset class="form-group">
    <legend>Address 3</legend>
    <div class="form-group">
         <label>City:</label>
         <input name="[2].City" class="form-control" />
    </div>
    <div class="form-group">
          <label>Country:</label>
          <input name="[2].Country" class="form-control" />
    </div>
</fieldset>

These input fields are created by looping from i->0 to i->2. When the form is submitted, the model binding creates an element of the List collection using the int index value of the name attribute, with the name attribute prefixed by [0]. The value of the name attribute corresponds to the first PersonAddress object, the value of the name attribute prefixed with [1] corresponds to the second PersonAddress object, and finally the value of the name attribute prefixed with [2] corresponds to the third object, if you have more objects , you can use this pattern, which is a classic example for model binding a collection of complex types

Now run the application for testing, enter the URL – /Home/Address, enter the values for the 3 pairs of Citiy and Country and press the submit button, you will see these values displayed in the browser, the picture is shown below:

3c1fb7c709066dbb764b72f70baaf35a.png

4464349c7a03da4d98e973dbeffe4ea2.png

4 Model Binding Source

Different ways of model binding in ASP.NET Core:

1 Form data

2 Routing

3 Query string

Model binding first searches from the form data, then routes the data, and finally the query string

We can override this search behavior to force model binding to use data from a specific data source. We can do this through the following table of properties:

name

describe

FromQuery

Use this attribute to specify a query string as the data source for model binding.

FromHeader

Use this attribute to specify the request header as the data source for model binding.

FromBody

Use this attribute to specify the request body as the data source for model binding.

FromForm

Use this attribute to specify form data as the data source for model binding.

FromRoute

Use this attribute to specify routing data as the data source for model binding.

Why use model binding? Model binding technology is the simplest way to convert data in HTTP requests to controller methods. Data conversion is done in different ways like form, body, headers, routes & query strings, model binding Makes all these tasks smooth and the project reduces a lot of development time

FromForm Property

The [FromForm] attribute tells the model binding to get the value from the submitted form field. The model binding first searches the form data for the value, so in most cases we do not need to add this attribute.

Let us understand through an example, we have a class called Employee

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Create a method named FromFormExample in the Home controller, and add a FromForm attribute – [FromForm] Employee model in the parameters of the Post version method. This tells the model to bind the value of the parameter from the form field.

public IActionResult FromFormExample() => View();
[HttpPost]
public IActionResult FromFormExample([FromForm] Employee model)
{
    ViewBag.Message = "Employee data received";
    return View();
}

Now create the FromFormExample view in the Views->Home file. In this view we create an html form that accepts employee id and name.

@{
    ViewData["Title"] = "FromFormExample";
}
<h2>From Form</h2>
<h3>@ViewBag.Message</h3>
<form method="post">
    <div class="form-group">
        <label>Id:</label>
        <input name="Id" class="form-control" />
    </div>
    <div class="form-group">
        <label>Name:</label>
        <input name="Name" class="form-control" />
    </div>
    <button class="btn btn-primary">Submit</button>
</form>

Visit https://localhost:7145/Home/FromFormExample

Enter the employee id and name and press the submit button. The value of the form will be received in the parameters of the action method.

Let’s use another example where we use JQuery to add data to a form field and submit the form

So change the Post version of FromFormExample to return an Employee type, and return the value of the parameter at the end

[HttpPost]
public Employee FromFormExample([FromForm]Employee model) => model;

Now modify the FromFormExample view as follows:

@{
    ViewData["Title"] = "FromFormExample";
}
<h2>From Form</h2>
@section scripts {
    <script src="//i2.wp.com/ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.min.js"></script>
    <script>
        $(document).ready(function () {
            $("button").click(function (e) {
                data = new FormData();
                data.append("id", 5);
                data.append("name", "Donald Trump");
                $.ajax("/Home/FromFormExample", {
                    method: "post",
                    processData: false,
                    contentType: false,
                    data: data,
                    success: function (data) {
                        $("#empId").text(data.id);
                        $("#empName").text(data.name);
                    }
                });
            });
        });
     </script>
}
<table class="table table-sm table-bordered table-striped">
    <tr><th>Id:</th><td id="empId"></td></tr>
    <tr><th>Name:</th><td id="empName"></td></tr>
</table>
<button class="btn btn-primary">Submit</button>

The view code has an ajax() method that calls the FromFormExample method. This method requires data from the form fields (so it uses the [FromForm] attribute), so we add the values (employee id=5 and name=Donald Trump) in the FormData, See how the code below works:

data = new FormData();
data.append("Id", 5);
data.append("Name", "Donald Trump");

The value of the form data is added to the data parameter of the ajax method. Visit /Home/FromFormExample and click the button. The value is displayed in the HTML table. The picture is shown below:

736582c9d54830be8999518e60abb602.png

6acba67ae8194112abd8a1745a044c40.png

FromBody Property

Specify the FormBody attribute to specify that the model is bound to the value from the Request Body as the data source. To understand how this attribute works, add a method named Body to the Home controller.

public IActionResult Body() => View();
[HttpPost]
public Employee Body([FromBody]Employee model) => model;

We use the [FromBody] attribute to modify the parameters of the Post version of the Body method. That means that the model binding searches for the value of the Employee model from the request body. Note that the type returned by the method is of type Employee.

Create a Body view inside the Views->Home folder

@{
    ViewData["Title"] = "Body";
}
<h2>Body</h2>
 @section scripts {
    <script src="//i2.wp.com/ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.min.js"></script>
    <script>
        $(document).ready(function () {
            $("button").click(function (e) {
                $.ajax("/Home/Body", {
                    method: "post",
                    contentType: "application/json",
                    data: JSON.stringify({
                        id: 5,
                        name: "Donald Trump"
                    }),
                    success: function (data) {
                        $("#empId").text(data.id);
                        $("#empName").text(data.name);
                    }
                });
            });
        });
</script>
}
<table class="table table-sm table-bordered table-striped">
    <tr><th>Id:</th><td id="empId"></td></tr>
    <tr><th>Name:</th><td id="empName"></td></tr>
</table>
<button class="btn btn-primary">Submit</button>

In this view, when the button is clicked, an HTTP POST request containing JSON data is sent to a URL like – /Home/Body

Note that the JSON.stringify method of JavaScript is used to convert the value of Id & name into a JSON character and assign it to the data parameter of the JQuery AJAX method.

data: JSON.stringify({
  id: 5,
  name: "Donald Trump"
})

The Employee type parameter of the Body method receives this data and returns it to the view in JSON format.

Finally, the $.ajax() method calls the callback method in Success. We will receive this data and display it in the Table.

Let’s do a test, enter the URL- /Home/Body and click the Button, as shown in the figure below:

440d0659076162ff0a498bb6611b93ea.png

aa03f788dd4f97137f322d7d8c4ccb18.png

FromQuery Properties

FromQuery specifies that the model binding obtains the value from the query string. We have the Index method in the Home controller containing the following code:

public IActionResult Index(int id = 1)
{
    return View(repository[id]);
}

Now we go to – /Home/Index/1?id=2, the id parameter will get value as 1 instead of 2, because query sequence route value, next is query character creation, model binding searches route before search query character creation value

There are 3 segments in the URL data – /Home(1st segment)/Index(2nd segment)/1(3rd segment) contains the id value 1, so you will see data with Employee Id 1

Now, we can easily override the search behavior by using the [FromQuery] attribute, which will force the model binding to find the query string against the Id parameter

Therefore, update the Index method

public IActionResult Index([FromQuery] int id = 1)
{
    return View(repository[id]);
}

Using the same URL – /Home/Index/1?id=2, this time you will see the second employee displayed in the browser, this means that the model binding searches for the value in the query string, the code is as follows:

e150edf0066289cc7399d4dd20f92e41.png

FromHeader Properties

The FormHeader attribute specifies that the model binding obtains the value from HTTP headers. Add an action method named Header in the Home controller and display the following code:

public string Header([FromHeader]string accept) => $"Header: {accept}";

This method has a parameter named accept, which is bound from the header of the http request.

Run your application, go to – /Home/Header, you will see the value of accept in the browser:

Header:

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

Now, we check the value of the Request Header in the browser development tools, please check the following steps:

1 Press F12 to open the browser development tools, or use shortcut keys

2 Click the Network page and press F5 to reload the values

3 On the left you will see the Header text, click on it

See the picture below:

0a5ddbed793376012ebe8abc80814c67.png

4 You will get 4 options – Headers, Preview, Response, Timing. Click the Headers option and we will find the requested Headers. See the picture below:

b69b5dabdad686b406cd53fd357a9e5b.png

The requested Header has multiple attributes like

Accept
Accept-Encoding
Accept-Language
Cache-Control
Connection
Host
Upgrade-Insecure-Requests
User-Agent

Now, let us bind the User-Agent value, so configure the FromHeader attribute using the Name property and specify the name in the Header, in our case we get the User-Agent

Update Header method code:

public string Header([FromHeader(Name = "User-Agent")]string accept) => $"Header: {accept}";

Now reload the application and view the URL – /Home/Header. This time you will see the User-Agent value displayed in the browser:

5b5ca60a3482f5190cf743eecec43ff0.png

?

All values in the binding request headers

We can bind the values of all headers of the request by using FromHeader on the class attribute. Let us understand this feature through an example.

Create a FullHeader.cs class under the Models folder

using Microsoft.AspNetCore.Mvc;
namespace AspNetCore.ModelBinding.Advanced.Models
{
    public class FullHeader
    {
        [FromHeader]
        public string Accept { get; set; }
        [FromHeader(Name = "Accept-Encoding"))]
        public string AcceptEncoding { get; set; }
        [FromHeader(Name = "Accept-Language")]
        public string AcceptLanguage { get; set; }
        [FromHeader(Name = "Cache-Control")]
        public string CacheControl { get; set; }
        [FromHeader(Name = "Connection")]
        public string Connection { get; set; }
        [FromHeader(Name = "Host")]
        public string Host { get; set; }
        [FromHeader(Name = "Upgrade-Insecure-Requests")]
        public string UpgradeInsecureRequests { get; set; }
        [FromHeader(Name = "User-Agent")]
        public string UserAgent { get; set; }
    }
}

Next, create a FullHeader method in the Home controller:

public IActionResult FullHeader(FullHeader model) => View(model);

Finally, create a FullHeader view under the Views->Home folder:

@model FullHeader
@{
    ViewData["Title"] = "Full Header";
}
<h2>Header</h2>
<table class="table table-sm table-bordered table-striped">
    <tr><th>Accept:</th><td>@Model.Accept</td></tr>
    <tr><th>Accept-Encoding:</th><td>@Model.AcceptEncoding</td></tr>
    <tr><th>Accept-Language:</th><td>@Model.AcceptLanguage</td></tr>
    <tr><th>Cache-Control:</th><td>@Model.CacheControl</td></tr>
    <tr><th>Connection:</th><td>@Model.Connection</td></tr>
    <tr><th>Host:</th><td>@Model.Host</td></tr>
    <tr><th>Upgrade-Insecure-Requests:</th><td>@Model.UpgradeInsecureRequests</td></tr>
    <tr><th>UserAgent:</th><td>@Model.UserAgent</td></tr>
</table>

Run the application and enter the URL – /Home/FullHeader You will see all requested headers:

8532ab6431fb0dc3695439412fdc9d0a.png

This is a typical example of capturing all HTTP Headers values through model binding and displaying them into a table. Use this code in your project

FromRoute Properties

The [FromRoute] attribute will specify that the model binding binds the value from the route data. Let’s create an example to understand this concept. Create a new method in the Home controller called FromRouteExample. Note that the parameter has a [FromRoute] attribute. Bind routing data

public IActionResult FromRouteExample() => View();
[HttpPost]
public string FromRouteExample([FromRoute] string id) => id;

Create a FromRouteExample in the Views->Home folder

@{
    ViewData["Title"] = "FromRouteExample";
}
<h2>Body</h2>
@section scripts {
<script src="//i2.wp.com/ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.min.js"></script>
<script>
        $(document).ready(function () {
            $("button").click(function (e) {
                $.ajax("/Home/FromRouteExample/5",
                    {
                        method: "post",
                        success: function (data) {
                            $("#id").text(data);
                        }
                    });
            });
        });
</script>
}
<table class="table table-sm table-bordered table-striped">
    <tr><th>Id:</th><td id="id"></td></tr>
</table>
<button class="btn btn-primary">Submit</button>

When the ajax method calls this action, the value sent by the third URL is 5 (ajax method calls /Home/FromRouteExample/5). Because the [FromRoute] attribute is used, this value will be bound to the parameters of the method. Run the project and Enter –/Home/FromRouteExample, click the submit button, and you will see the value displayed in the html table as 5

693ea5c6f0472823287f10695d0d839f.png

Summary

In this section we mainly explain the advanced model binding in ASP.NET Core

Source code address

https://github.com/bingbing-gui/Asp.Net-Core-Skill/tree/master/Fundamentals/AspNetCore.ModelBinding/AspNetCore.ModelBinding.Advanced

references

https://www.yogihosting.com/aspnet-core-model-binding/