Model Binding in ASP.NET Core from Beginners to Advanced

Model Binding in ASP.NET Core from Beginners to Advanced

Models contain or represent the data that users work with. Models are basically C# classes that are filled with data from a database or a data source by the Controllers. The 2 most important topics about Models are:

  • 1. Model Binding – a process of extracting data from HTTP request and providing them to the action method’s arguments.
  • 2. Model Validation – a process to validate the Model properties so that invalid entries are not entered in the database.

In this tutorial you will learn about Model Binding process in details.

Create the Example Project

For this tutorial use ASP.NET Core Web Application (.NET Core) template to create a new Empty Project and name it as ModelBindingValidation. Remember to select the framework as .NET Core and latest version of ASP.NET Core.

Models & Repository

Create Models folder in your application’s root and add an EmployeeDetails.cs class to it, use it to define classes and an enum as given below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ModelBindingValidation.Models
{
    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime DOB { get; set; }
        public Address HomeAddress { get; set; }
        public Role Role { get; set; }
    }

    public class Address
    {
        public string HouseNumber { get; set; }
        public string Street { get; set; }
        public string City { get; set; }
        public string PostalCode { get; set; }
        public string Country { get; set; }
    }

    public enum Role
    {
        Admin,
        Designer,
        Manager
    }
}

Next, create a class file called Repository.cs to the Models folder, and here define an Interface and implementation class as shown below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ModelBindingValidation.Models
{
    public interface IRepository
    {
        IEnumerable<Employee> Employee { get; }
        Employee this[int id] { get; set; }
    }

    public class EmployeeRepository : IRepository
    {
        private Dictionary<int, Employee> employee = new Dictionary<int, Employee>
        {
            [1] = new Employee
            {
                Id = 1,
                Name = "John",
                DOB = new DateTime(1980, 12, 25),
                Role = Role.Admin
            },
            [2] = new Employee
            {
                Id = 2,
                Name = "Michael",
                DOB = new DateTime(1981, 5, 13),
                Role = Role.Designer
            },
            [3] = new Employee
            {
                Id = 3,
                Name = "Rachael",
                DOB = new DateTime(1982, 11, 25),
                Role = Role.Designer
            },
            [4] = new Employee
            {
                Id = 4,
                Name = "Anna",
                DOB = new DateTime(1983, 1, 20),
                Role = Role.Manager
            }
        };

        public IEnumerable<Employee> Employee => employee.Values;

        public Employee this[int id]
        {
            get
            {
                return employee.ContainsKey(id) ? employee[id] : null;
            }
            set
            {
                employee[id] = value;
            }
        }
    }
}

Controllers & Views

Create the Controllers folder on the root of your application and add a controller by name HomeController to it. The controller depends upon the dependency injection feature to receive the EmployeeRepository class.

The HomeController.cs file’s code is shown 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
    {
        private IRepository repository;
        public HomeController(IRepository repo)
        {
            repository = repo;
        }
        public IActionResult Index(int id)
        {
            return View(repository[id]);
        }
    }
}

Create Views folder on the root of the project, next add Home folder inside the Views folder and add Index.cshtml view file inside this Home folder. The View receives a model of type Employee and binds some of the model properties inside a Html table.

The Index View code is:

@model Employee
@{
    Layout = "_Layout";
    ViewData["Title"] = "Index";
}
 
<h2>Employee</h2>
 
<table class="table table-sm table-bordered table-striped">
    <tr><th>Id:</th><td>@Model.Id</td></tr>
    <tr><th>Name:</th><td>@Model.Name</td></tr>
    <tr><th>Date of Birth:</th><td>@Model.DOB.ToShortDateString()</td></tr>
    <tr><th>Role:</th><td>@Model.Role</td></tr>
</table>

Next add a Razor Layout called _Layout.cshtml, inside the View ➤ Shared folder. It’s code is:

<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link href="~/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
    @RenderSection("scripts", false)
</head>
<body>
    <div class="m-1 p-1">
        @RenderBody()
    </div>
</body>
</html>

The layout reference Bootstrap CSS file and also contains an optional scripts section. If you want to learn how to add Bootstrap package in Visual Studio then read How to Install Bootstrap Package in ASP.NET Core Application in Visual Studio.

Next add Razor View Imports file called _ViewImports.cshtml, for importing namespaces and Tag Helpers.

Add the following code to this Razor View Imports file.

@using ModelBindingValidation.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Configure the Project

Use the Startup.cs class to do the configuration of your project. Here I have I have created a service for the EmployeeRepository class so that the controller can gain access to this class using dependency injection.

The Startup class code is given below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ModelBindingValidation.Models;

namespace ModelBindingValidation
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IRepository, EmployeeRepository>();
            services.AddControllersWithViews();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

What is Model Binding

Model Binding is a process of ASP.NET Core framework to Extract Data from HTTP Requests and provide them to the arguments of Action Method. Let us see the first example of Model Binding process.

So run your project and go to URL – /Home/Index/1. You will see the record of the employee number 1 gets displayed on the browser. This is shown in the image below:

model binding employee record with id 1

Here Model Binding process comes to play because the URL I requested contained the value of Id of the employee (given in the 3rd segment of the URL which is 1).

/Home/Index/1

According to the url routes given on the Startup.cs, the 3rd segment of the URL is for the Id segment.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

Now, you can also see that the Index Action method is having the Id in it’s argument.

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

So ASP.NET Core extracts the id’s value from the URL (which is 1) and provides it to the id argument of the action method. So ASP.NET Core extracts the id’s value from the URL (which is 1) and provides it to the id argument of the action method.

The framework looks for action method’s argument (here int ‘id’) value in the following 3 places:

  • 1. Form data values
  • 2. Routing variables
  • 3. Query strings
You should read how Form Data Binding works at Controllers Request Property – Request.Form

Each place is inspected starting from Form data values, then Routing variable and finally with Query String. In this case the framework does not find the value of Id in Form data values, it then checked the routing variables where there is routing segment called id. So the search ended here, without the query string search performed.

The order of search is important. If you open the URL – /Home/Index/2?id=1 then you will see the record of Employee id 2 record, as shown in the image below:

model binding employee record with id 2

The reason why Employee id 2 is displayed is because the framework finds the value of id (i.e 2) in the routing variable before the value of id (i.e. 1) in the query sting so value of 2 is provided to the action method’s argument.

If you open the URL – Home/Index?id=3 then the Employee id 3 is displayed as shown in the image below:

model binding employee record with id 3

In this case the framework does not find the id value in the URL segment so it moves to search the query string where it found the id value of 3.

You can learn complete URL Routing technique from my another tutorial called Learn ASP.NET Core Convention-Based URL Routing

Default Binding Values

You may wonder what will happen if ASP.NET Core framework does not find the values of the action method’s argument in any of the three locations – Form data values, Routing variables & Query strings. In that case it will provide the default values based on the type of the action method’s argument. These are:

  • 0 for int
  • “” for string
  • 01-01-0001 00:00:00 for DateTime
  • 0 for float

In order to test the default binding value, change the Index action method to as shown in the below code:

public IActionResult Index(int id)
{
    if (id == 0)
        id = 1;
    return View(repository[Convert.ToInt32(id)]);
}

Now put a breakpoint on the action method then run your application and go to URL – /Home/Index. When your break-point touches, put the mouse pointer over the id variable to check it’s value. You will see the value 0, as shown by the image given below:

Default Binding Values

Since there is no employee with id 0 therefore I am making it 1. This will help me to prevent runtime error.

Obviously you can also use the Try-Catch block to deal with runtime errors. I have covered this topic in my another tutorial called How to use Try Catch Block to Catch Exceptions

Note that the default values of nullable types are null.

Now change the Index action to have a nullable int argument as shown in the code below:

public IActionResult Index(int? id)
{
    if (id == null)
        id = 1;
    return View(repository[Convert.ToInt32(id)]);
}

Run your application and go to URL – /Home/Index and you will find the value of id is null.

Model Binding for Simple Types

When Binding Simple Types the framework convert the values into the types of action method’s arguments. The Simple Types are – string, int, bool, float, datetime, decimal, etc.

Let us see it with an example of the Index Action which is shown below:

public IActionResult Index(int? id)
{
    if (id == null)
        id = 1;
    return View(repository[Convert.ToInt32(id)]);
}

The id argument of the Index action is an int type therefore ASP.NET Core framework automatically parses the value of the id segment variable into an int value.

Case 1 : For URL – /Home/Index/1

When you request URL which contains id value as int like – /Home/Index/1 then the framework successfully parse value of 1 to int and so the id argument of the action method receives 1.

Case 2 : For URL – /Home/Index/John

Now if you request a URL where the id value is a string like – /Home/Index/John then in this case the framework will try to parse John to int value. It fails to parse so id argument of the action method receives null value.

Model Binding for Complex Types

When the argument of the action method is a complex type like a class object then Model Binding process gets all the public properties of the complex type and performs the binding for each of them.

The Model Binding process looks for the values of these public properties in the following 3 places:

  • 1. Form data values
  • 2. Routing variables
  • 3. Query strings

Let me demonstrate how this works by an example.

Add Create Action Methods on the Home Controller. The codes for these 2 actions are 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
    {
        private IRepository repository;
        public HomeController(IRepository repo)
        {
            repository = repo;
        }
        public IActionResult Index(int? id)
        {
            if (id == null)
                id = 1;
            return View(repository[Convert.ToInt32(id)]);
        }

        public IActionResult Create() => View();

        [HttpPost]
        public IActionResult Create(Employee model) => View("Index", model);
    }
}

The HTTP GET version of the Create Action Method selects the default View and did not pass any model to it.

The HTTP POST version of the Create Action Method has an argument of Employee class type (which is a complex type).

The Model Binding feature will extract the value of the Employee class’s public properties from the HTTP request and provide it to the arguments of the action method. The action method then passes the Employee object as Model to the default view.

Next create the Create View inside the Views ➤ Home folder. It’s code is given below:

@model Employee
@{
    Layout = "_Layout";
    ViewData["Title"] = "Create Employee";
}
 
<h2>Create Employee</h2>
 
<form asp-action="Create" method="post">
    <div class="form-group">
        <label asp-for="Id"></label>
        <input asp-for="Id" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="Name"></label>
        <input asp-for="Name" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="DOB"></label>
        <input asp-for="DOB" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="Role"></label>
        <select asp-for="Role" class="form-control"
                asp-items="@new SelectList(Enum.GetNames(typeof(Role)))"></select>
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

The View contains a form and binds some of the public properties of the Employee model. When the form is submitted these values are posted to the HTTP POST version of the Create Action method.

The create action requires an Employee objects in it’s parameter. So the Model Binding process first look for the value of each public property (Id, Name, DOB, Role) of the Employee object in the form data.

The form data contains these values as these are set up using asp-for tag helper on input elements. Example – the id property is bound to the input control as shown below.

<input asp-for="Id" class="form-control" />

In the same way the Name property is bound as shown below.

<input asp-for="Name" class="form-control" />

Therefore the Model Binding process put these values to the properties of Employee object which is given in the argument of the create action method. This way the Employee argument of the create action gets the value from the View.

The topic of Tag Helper is given at Built-In Tag Helpers in ASP.NET Core

The Post version of the Create Action invokes the Index View and passes to it the Employee object as a Model. Now run your application and go to URL – /Home/Create. Fill all form values and click the submit button and you will see you filled values getting displayed on the browser.

The below 2 images shows the filling and submitting of the form:

model binding complex type 1
model binding complex type 2

Model Binding for Complex Type that contains another Complex Type

The Employee class contains a public property called HomeAddress and this property is of Address type. So this makes it a Complex Type.

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime DOB { get; set; }
    public Address HomeAddress { get; set; }
    public Role Role { get; set; }
}
 
public class Address
{
    public string HouseNumber { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string PostalCode { get; set; }
    public string Country { get; set; }
}

When binding the HomeAddress property the Model Binding process does the same thing like before:

  • 1. Finds out all the public properties of the HomeAddress (i.e HouseNumber, Street, City, PostalCode, Country).
  • 2. Search the values of these public properties in the form data. Note that routing & query sting variables cannot contain complex types.

So to make the Model Binding process to extract HouseNumber property value you will use asp-for="HomeAddress.HouseNumber" tag helper in the view.

In the below updated code of the Create View I am binding all the five properties of the HomeAddress type. See the highlighted lines.

@model Employee
@{
    Layout = "_Layout";
    ViewData["Title"] = "Create Employee";
}

<h2>Create Employee</h2>

<form asp-action="Create" method="post">
    <div class="form-group">
        <label asp-for="Id"></label>
        <input asp-for="Id" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="Name"></label>
        <input asp-for="Name" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="DOB"></label>
        <input asp-for="DOB" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="Role"></label>
        <select asp-for="Role" class="form-control"
                asp-items="@new SelectList(Enum.GetNames(typeof(Role)))"></select>
    </div>
    <div class="form-group">
        <label asp-for="HomeAddress.HouseNumber"></label>
        <input asp-for="HomeAddress.HouseNumber" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="HomeAddress.City"></label>
        <input asp-for="HomeAddress.City" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="HomeAddress.Street"></label>
        <input asp-for="HomeAddress.Street" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="HomeAddress.PostalCode"></label>
        <input asp-for="HomeAddress.PostalCode" class="form-control" />
    </div>
    <div class="form-group">
        <label asp-for="HomeAddress.Country"></label>
        <input asp-for="HomeAddress.Country" class="form-control" />
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

Next edit the Index View to display the properties of the HomeAddress as shown by the code below:

@model Employee
@{
    Layout = "_Layout";
    ViewData["Title"] = "Index";
}

<h2>Employee</h2>

<table class="table table-sm table-bordered table-striped">
    <tr><th>Id:</th><td>@Model.Id</td></tr>
    <tr><th>Name:</th><td>@Model.Name</td></tr>
    <tr><th>Date of Birth:</th><td>@Model.DOB.ToShortDateString()</td></tr>
    <tr><th>Role:</th><td>@Model.Role</td></tr>
    <tr><th>House No:</th><td>@Model.HomeAddress?.HouseNumber</td></tr>
    <tr><th>Street:</th><td>@Model.HomeAddress?.Street</td></tr>
    <tr><th>City:</th><td>@Model.HomeAddress?.City</td></tr>
    <tr><th>Postal Code:</th><td>@Model.HomeAddress?.PostalCode</td></tr>
    <tr><th>Country:</th><td>@Model.HomeAddress?.Country</td></tr>
</table>

Now run your application and go to URL – /Home/Create. Fill and submit the form and you will find the properties of the address type getting displayed, as shown in the image below:

model binding complex type inside another complex type
Do you want to learn Database operations by Entity Framework Core then check my series on it – EF Core tutorials
Check the Source Code

Notice the html source code generated for these HouseNumber property in the Create View, which is given below:

<div class="form-group">
    <label for="HomeAddress_HouseNumber">HouseNumber</label>
    <input class="form-control" type="text" id="HomeAddress_HouseNumber" name="HomeAddress.HouseNumber" value="" />
</div>
<div class="form-group">
    <label for="HomeAddress_City">City</label>
    <input class="form-control" type="text" id="HomeAddress_City" name="HomeAddress.City" value="" />
</div>
<div class="form-group">
    <label for="HomeAddress_Street">Street</label>
    <input class="form-control" type="text" id="HomeAddress_Street" name="HomeAddress.Street" value="" />
</div>
<div class="form-group">
    <label for="HomeAddress_PostalCode">PostalCode</label>
    <input class="form-control" type="text" id="HomeAddress_PostalCode" name="HomeAddress.PostalCode" value="" />
</div>
<div class="form-group">
    <label for="HomeAddress_Country">Country</label>
    <input class="form-control" type="text" id="HomeAddress_Country" name="HomeAddress.Country" value="" />
</div>

The HouseNumber input control gets the name value as HomeAddress.HouseNumber while the id value becomes HomeAddress_HouseNumber. In the same way the City input control gets the name value as HomeAddress.City while the id value becomes HomeAddress_City, and so on for other controls.

You can also get the HouseNumber value in the action method by using Request.Form method as shown below:

string houseNo = Request.Form["HomeAddress.HouseNumber"];

And this is all the same for other values.

Custom Binding using [Bind(Prefix)] Attribute

If you want to bind to a different object than what the Model Binding feature is looking. In that case you will have to use the [Bind(Prefix)] Attribute.

Let me give an example for this feature.

Create a new Model class called PersonAddress.cs with the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ModelBindingValidation.Models
{
    public class PersonAddress
    {
        public string City { get; set; }
        public string Country { get; set; }
    }
}

Next, add a new action called DisplayPerson to the HomeController class.

public IActionResult DisplayPerson(PersonAddress personAddress)
{
    return View(personAddress);
}

Next, change the value of asp-action tag helper in the Create View to target the DisplayPerson action as shown below:

@model Employee
@{
    Layout = "_Layout";
    ViewData["Title"] = "Create Employee";
}
 
<h2>Create Employee</h2>
 
<form asp-action="DisplayPerson" method="post">
    ...
</form>

Now this means when you fill and submit the form in the Create View then the data will be posted to the DisplayPerson action.

Also create the DisplayPerson view inside the Views ➤ Home folder. This View has the model of type PersonAddress and displays the City and Country given in the Model object.

The code for the DisplayPerson view is given below:

@model PersonAddress
@{
    Layout = "_Layout";
    ViewData["Title"] = "Person";
}
 
<h2>Person</h2>
 
<table class="table table-sm table-bordered table-striped">
    <tr><th>City:</th><td>@Model.City</td></tr>
    <tr><th>Country:</th><td>@Model.Country</td></tr>
</table>

Now run your application and go to the URL – /Home/Create. Fill the complete form and click the submit button.

You will notice that the city and country values do not get displayed, see the image below:

empty data in model

The reason for this problem is that the Model Binding Process failed since name attribute of the city and country input controls have string called HomeAddress prefixed as shown by the below code.

<input class="form-control" type="text" id="HomeAddress_City" name="HomeAddress.City" value="">
<input class="form-control" type="text" id="HomeAddress_Country" name="HomeAddress.Country" value="">

Here the model binding process is not looking for these names (HomeAddress.City and HomeAddress.Country) in the form data. It is actually looking for only City and Country names in the form data.

To fix this problem apply the [Bind(Prefix)] Attribute in the action method’s parameter. This specifies the prefix that should be used during model binding.

So change the DisplayPerson action method code as shown below:

public IActionResult DisplayPerson([Bind(Prefix = nameof(Employee.HomeAddress))] PersonAddress personAddress)
{
    return View(personAddress);
}

Now once again submit the form and this time you will find the City and Country values being display on the browser, see the below image:

bind prefix attribute

Binding Selective Properties only

If there are some sensitive properties that should ‘not’ be bind then use the first argument of the Bind method to specify all the properties that should be bind i.e. you leave out all the sensitive properties which you don’t want to show.

Give the first argument of the Bind() method all the names of the properties (in a comma-separated list) that should be only included in the model binding process. Therefore those properties that are left will not be bind at all.

Change the DisplayPerson action method code to include only the city property.

public IActionResult DisplayPerson([Bind(nameof(PersonAddress.City), Prefix = nameof(Employee.HomeAddress))] PersonAddress personAddress)
{
    return View(personAddress);
}

Test it by filing and submitting the form, you will see that only the City value is displayed, see the image below:

only city gets bind

The same code can be recreated by decorating the Country property of the PersonAddress class with the [BindNever] attribute. The BindNever specifies the property which the Model Binding Process should not bind at all.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
 
namespace ModelBindingValidation.Models
{
    public class PersonAddress
    {
        public string City { get; set; }
 
        [BindNever]
        public string Country { get; set; }
    }
}

You can download the source code using the below link:

Download

Conclusion

Model binding is a well-designed bridge between the HTTP request and the C# action methods. It makes it easy for developers to work with data on views. Use this feature to make high quality codes for your application.

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.