Advanced Model Binding Concepts in ASP.NET Core

Advanced Model Binding Concepts in ASP.NET Core

In my previous tutorial on Model Binding technique in ASP.NET Core I covered some basis of this technique. Now I will cover some of the advanced and powerful features of this technique.

Model Binding to Arrays

You can do Model Binding to Action Methods that have array type parameters. To demonstrate this feature, create a ‘Places’ Action method in the Home controller as shown by the code given below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using ModelBindingValidation.Models;

namespace ModelBindingValidation.Controllers
{
    public class HomeController : Controller
    {
        // Removed for clarity

        public ViewResult Places(string[] places) => View(places);
    }
}

The Places action has a parameter of string array parameter called ‘places’. The model binding feature will look for the item called ‘placed’ (in form data values, routing variables and query strings) and create an array that contains those values.

Create the ‘Places’ View inside the ‘/Views/Home’ folder and add the following code to it:

@model string[]
@{
    Layout = "_Layout";
    ViewData["Title"] = "Places";
}

<h2>Places</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 id="places" name="places" class="form-control" />
            </div>
        }
        <button type="submit" class="btn btn-primary">Submit</button>
    </form>
}
else
{
    <table class="table table-sm table-bordered table-striped">
        @foreach (string place in Model)
        {
            <tr><th>Place:</th><td>@place</td></tr>
        }
    </table>
    <a asp-action="Places" class="btn btn-primary">Back</a>
}

The View will display all the places in the model by looping through each of them. If the model is empty then a form is displayed to add 3 places in an identical input element –

<span class="code"><input id="places" name="places" class="form-control" /></span>

The 3 identical input elements have their names like the parameter name of the Places action method. SO when the form is submitted then the Model Binding feature will extract the values of all elements that have name called ‘places’ and create an array containing these values. The array is then passed on to the parameter of the action method.

Run your application and submit the form given in the URL – ‘/Home/Places’, on sumitting you will see the 3 values displayed on the browser. See the image given below:

model binding array

Model Binding to Collection

Model binding also supports collection classes. I update the previous example from string array to strongly typed list.

First change the parameter of the ‘Places’ action from string[] to List:

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

Then do a 2 slight changes on the ‘Places’ view as shown below:

@model List<string>
@{
    Layout = "_Layout";
    ViewData["Title"] = "Places";
}

<h2>Places</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 id="places" name="places" class="form-control" />
            </div>
        }
        <button type="submit" class="btn btn-primary">Submit</button>
    </form>
}
else
{
    <table class="table table-sm table-bordered table-striped">
        @foreach (string place in Model)
        {
            <tr><th>Place:</th><td>@place</td></tr>
        }
    </table>
    <a asp-action="Places" class="btn btn-primary">Back</a>
}

Now the Model Binding will work for collection for the ‘Places’ action method.

Model Binding to Collections of Complex Types

Now I will show how to do Model Binding to Collections of Complex Types. Here multiple objects of ‘PersonAddress’ class will be involved in Model Binding in a single request.

Go to the Home Controller and add a new action method named ‘Address’ to it. The code of this action method is given below:

public ViewResult Address(List<PersonAddress> address) =>
                  View(address);

Next create ‘PersonAddress’ view inside the ‘Views/Home’ folder with the code as shown below:

@model List<PersonAddress>
@{
    Layout = "_Layout";
    ViewData["Title"] = "Address";
}

<h2>Address</h2>

@if (Model.Count() == 0)
{
    <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 view renders a form if there are no items in the model. The form contains 3 pairs of City and Country elements whose names are prefixed with an array index as shown in the code below:

<input name="[0].City" class="form-control" />
<input name="[0].Country" class="form-control" />

<input name="[1].City" class="form-control" />
<input name="[1].Country" class="form-control" />

<input name="[2].City" class="form-control" />
<input name="[2].Country" class="form-control" />

On the submission of the form, the model binding process uses the array index prefixes in the name attributes to obtain values for the List.

The names prefixed with [0] are used for the first PersonAddress object, those prefixed with [1] are used for the second object, and finally those prefixed with [2] are used for the third object.

Now test it by running your project and going to the URL – ‘/Home/Address’. Fill and form and press the submit button, you will see your added values getting displayed on your browser, as shown by the image given below:

model binding collections of complex types

Specifying a Model Binding Source to Select

As stated earlier, the Model Binding process searches for data in 3 places by default:

1. Form data
2. Routing system
3. Query string value

You can override this search behaviour and force Model Binding process to use only a specified source for binding data. This can be done by using the attributes that are described in the table below:

Name Description
FromForm This attribute is used to select form data as the only source of binding data.
FromRoute This attribute is used to select routing system as the only source of binding data.
FromQuery This attribute is used to select the query string as the only source of binding data.
FromHeader This attribute is used to select a request header as the only source of binding data.
FromBody This attribute is used to specify that the request body should be used as the only source of binding data.

In the above attributes the name of the parameter is used to locate a value by default, but this can be changed using the Name property, which allows a different name to be specified.

Using the ‘FromQuery’ Attribute

The Index Action of the Home Controller code is given below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using ModelBindingValidation.Models;

namespace ModelBindingValidation.Controllers
{
    public class HomeController : Controller
    {
        public ViewResult Index(int? id)
        {
            return View(repository[Convert.ToInt32(id)]);
        }
        // Remove for clarity 
    }
}

Now when you go to the URL – ‘/Home/Index/1?id=2’. You will see the Employee id 1 record because the Model Binding feature will search for the id value in the routing data before than query string values.

In the routing data it finds the 3rd segment of the URL (/Home(1st segment)/Index(2st segment)/1(3st segment)) contains the value of id, which is 1. So you will see the Employee id 1 record.

Now I can easily override this search behaviour by using the FromQuery attribute. It will force Model Binding feature to look only in query string values for the value of Id.

So update the Index Action method to:

public ViewResult Index([FromQuery]int? id)
{
    return View(repository[Convert.ToInt32(id)]);
}

Rerun the application and go to the same URL – ‘/Home/Index/1?id=2’. This time you will see the 2nd employee record on the browser. This means the model binding feature searched the value of ‘id’ in only query string values. See the below image:

[FromQuery] attribute

Using the ‘FromHeader’ Attribute

With the FormHeader Attribute I can direct the Model Binding feature to select a request header as the only source of binding data.

Add an action method called ‘Header’ to the Home Controller as shown in the code below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using ModelBindingValidation.Models;

namespace ModelBindingValidation.Controllers
{
    public class HomeController : Controller
    {
        public string Header([FromHeader]string accept) => $"Header: {accept}";                           
        
        // Remove for clarity 
    }
}

This action has an accept parameter the value of it will be bind from the Accept header in the current request and then will be returned by the action method.

Run your application and go to URL – ‘/Home/Header’. You will see the ‘Accept’ header value in the browser as shown below:

Header: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

Now let us check the Request Header values in the browser. For this, follow the given steps:

1. Open the Chrome Developer Tools by pressing the F12 key or use shortcut Ctrl+Shift+I.

2. Click the Network tab. Press F5 key it asks for reload to capture values.

3. On the left side you will see the ‘Header’ text. Click on it, see image below:

request response in browser

4. You will get 4 tabs – ‘Headers’, ‘Preview’, ‘Response’, ‘Timing’. Click on the ‘Headers’ tab and scroll down to find the Request Headers section. See the below image:

request headers in browser

The Request Headers have any properties like – Accept, Accept-Encoding, Accept-Language, Cache-Control, Connection, Host, Upgrade-Insecure-Requests and User-Agent.

Now let us bind the User-Agent value. For this configure the FromHeader attribute using the Name property to specify the name of the header, which in my case is User-Agent.

The updated code of the Header action method is:

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

Now reload your application and view the same URL – ‘/Home/Header’. This time you will see the User-Agent value being displayed on the browser as shown below:

Header: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36

Binding all the Request Headers in a class

You can bind all the Request Header values by applying the FromHeader attribute to the properties of a model class.

Create Model class and name it ‘FullHeader’:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace ModelBindingValidation.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 an action named ‘FullHeader’ in the Home Controller:

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

Finally create the ‘FullHeader’ view inside the ‘Views/Home’ folder:

@model FullHeader
@{
    Layout = "_Layout";
    ViewData["Title"] = "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>

Now check it by running your application and go to the URL – ‘/Home/FullHeader’ where you will see all the Request Headers values as shown in the image below:

binding all request headers in class

Using the ‘FromBody’ Attribute

The FromBody attribute is used to specify that the Model Binding Process should only use the Request Body as the source of model binding.

For understanding how this attribute works, add new actions called Body to the Home Controller. See the code below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using ModelBindingValidation.Models;

namespace ModelBindingValidation.Controllers
{
    public class HomeController : Controller
    {
        // Removed for clarity

        public ViewResult Body() => View();

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

I have decorated the parameter for the HttpPost version of the ‘Body’ method with the FromBody attribute. It means that only the request body content will be used for model binding. Also note that the return type of the action method is an ‘Employee’ class.

Now create ‘Body’ View inside the ‘Views/Home’ folder with contents as shown below:

@{
    Layout = "_Layout";
    ViewData["Title"] = "Body";
}

<h2>Body</h2>

@section scripts {
    <script src="http://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, I send Http Post request containing JSON data to the URL – ‘/Home/Body’.

The Body action method receives this data in its Employee type parameter, and then returns it back in JSON format.

Finally in the Success Callback method of the $.ajax() method I an receiving this data and showing it inside the td elements.

You can check it’s working by going to the URL – ‘/Home/Body’ and clicking the button.

The image given below shows the working:

[frombody] attribute

You can download the source code using the below link:

Download

Conclusion

This completes all about Model Binding topic in ASP.NET Core. Now you can use this technique and create very powerful data driven applications.

Share this article -

yogihosting

ABOUT THE AUTHOR

This article has been written by the Technical Staff of YogiHosting. Check out other articles on "ASP.NET Core, jQuery, EF Core, SEO, jQuery, HTML" and more.