ASP.NET Core MVC Model Binding

This article explains what is model binding in ASP.NET Core MVC and how to use its different attributes like BindNever, BindRequired, FromHeader, FromQuery, FromRoute, FromForm, FromServices, FromBody, ModelBinder, and model binding to a list of complex objects.

What is MVC Model Binding

MVC controller action method uses Model binding to work directly with Model types, as model binding maps HTTP request data to action method parameters by name. MVC model binding will check for the value of each parameter by name and the name of the public settable property of the Model. These parameters can be primitive like string, int, or complex types. Model binders will map data between HTTP requests and models.

Model binding extract data from various sources like route, form fields of View, or query string, convert strings to .NET data type and provides this extracted data to controller and razor pages in method parameters.

Primitive type binding

The HTTP Get request attaches data to the query string part of the URL. MVC framework automatically converts query string to values to input values to the parameter of an action method. For example, ProductID - 1345 from the URL https://geekstore.com/getproductdetails/1345

public IActionResult GetProductDetails(int productID)
{   
    --code to fetch product details for productid - 1345
    return View();            
}

Binding to Complex Type

Model bindings also work with complex types in a similar way to binding primitive types. Model binder will automatically map input fields from MVC View to the properties of a complex type parameter. Model properties can be validated usingg MVC Model DataAnnotation.

Model classes

public class Product
{
    public int ProductID { get; set; }
    public string ProductName { get; set; }
    public decimal Price { get; set; }
    public Category category { get; set; }
}

public class Category 
{
    public int CategoryID { get; set; }
    public string CategoryName { get; set; }
    public decimal CategoryDiscount { get; set; }
}

Controller


[HttpPost]
public ActionResult Edit(Product product)
{   
    var categoryName = product.category.CategoryName;

    //code update product here..

    return View();
}    

MVC binds data from various parts of the request in the following order.

  1. Form values

    Form values have values from form fields from view and include in HTTP request using POST method and jQuery POST method.

  2. Route Values

    Route values provided by MVC routes using routing.

  3. Query strings

    The Query string that is included in the URL.

Customize Model binding behavior using attributes

MVC model binding has several attributes like [BindRequired] or [BindNever] to specify binding is a must or binding can be ignored for the property. You can also customize the data source of property to read values from HTTP Header, Query string, Form fields, or Route.

Following are Model binding attributes

  • BindRequired

    The model binder must bind the value of a property. If the binding is not found Model binder adds an error to the Model state.

  • BindNever

    The model binder should ignore the binding of this property.

  • FromHeader

    The model binder must bind this property from the Request header. If the binding is not available in the header add an error to the Model state.

  • FromQuery

    The model binder must get value from the query string for this property.

  • FromRoute

    The model binder must get value for this property from Routes.

  • FromForm

    The model binder must get value for this property using the Form field.

  • FromServices

    This attribute uses DI (Dependency Injection) to bind the parameter from the Service.

  • FromBody

    The model binder must get the binding value for this property from the request body. Request body can be formatted in JSON, XML, or any other formatter. The Default format for MVC is JSON (JsonInputFormatter).

  • ModelBinder

    This is used when you are having a custom model binder to override the default model binder, binding source, and name.

Model binding in ASP.NET Core MVC

We will create a simple application for a demo of how to use model binding in ASP.NET Core MVC. Follow the below steps.

  1. Create ASP.NET Core MVC Application

    As we are using.NET5, to complete this demo you need Visual Studio 16.8 for Windows and MAC users. Open your Visual Studio 2019 and create an ASP.NET MVC Core application.NET5. You can follow Getting Started with ASP.NET MVC Core and .NET 5 for more detailed steps to create an application with basic MVC features.

  2. Products MVC Area

    Add a new MVC Area by right-clicking on Areas -> Select Add -> Area -> From Add New scaffolded item dialog box select MVC Area -> Give Area name as Products.

    Using MVC area you can partition large web application into smaller parts. Products Area help you to logical grouping of Products related Models, Views, Controllers, and data. You will add Model, Controllers and Views for Product Area as shown in following diagram.

    ASP.NET MVC Area File structure

  3. Create Models

    In this step, you will create a Product Model and ProductSize enum. The complex binding will use ProductSize enum in the Product model.

    ProductSize Enum

    Right click on Areas -> Products -> Model folder and add a new class file with name ProductSize.cs. Add the following code to it.

     public enum ProductSize : ushort
    {
        Small = 1,
        Medium = 2,
        Large = 3,
        XtraLarge = 4,
    }
    

    Product Model

    Right click on Areas -> Products -> Model folder and add a new class file with name Product.cs. Add the following properties to it.

    namespace MVC5Tutorial.Areas.Products.Models
    {
        public class Product
        {
            public int ProductID { get; set; }
    
            public string Name { get; set; }
    
            public string Code { get; set; }
    
            public int ReorderQty { get; set; }
    
            public int Qty { get; set; }
    
            public decimal ListPrice { get; set; }
    
            public ProductSize Size { get; set; }
        }
    }
                

    If your database is ready with the required design you can generate model classes using Entity Framework Core Database First Tutorial.

  4. Repository Interface and Classes

    The Repository pattern implements separation of concerns by abstraction. Using the repository interface, the Controller does not require to depend on a specific implementation of the repository, the controller will only require to know the contract. Manual instantiations of repository classes are not required instead Dependency Injection can be used.

    IProductRepository interface

    Add a Interface with name IProductRepository in Areas -> Products -> Models folder. This interface will have Products and a default property. Products are read-only property that returns a list of Product Model objects. Default property returns a Product model as per the ProductID value provided.

    This list will be used to bind multiple model objects and show it in view as a list. Add following code to IProductRepository.cs file.

    namespace MVC5Tutorial.Areas.Products.Models
    {
        public interface IProductRepository
        {
            IEnumerable<Product> Products { get; }
            Product this[int ProductID] { get; set; }
        }
    }
                

    ProductRepository class

    Add a new class to Areas -> Products -> Model folder with name ProductRepository.cs. ProductRepository class will implement the IProductRepository interface to have loose coupling between repository class and controller.

    Add following code to ProductRepository class. This code creates Model classes in the same application however in enterprise application it is preferred to create EF Core Models in separate-assembly.

    using System.Collections.Generic;
    
    namespace MVC5Tutorial.Areas.Products.Models
    {
        public class ProductRepository : IProductRepository
        {
            public IEnumerable<Product> Products => ProductList.Values;
            public Product this[int id]
            {
                get
                {
                    return ProductList.ContainsKey(id) ? ProductList[id] : null;
                }
                set
                {
                    ProductList[id] = value;
                }
            }
    
            private Dictionary<int, Product> ProductList = new Dictionary<int, Product>
            {
                [1] = new Product
                {
                    ProductID = 1,
                    Name = "Classic Vest, S",
                    Code = "VE-C304-S",
                    ReorderQty = 30,
                    Qty = 40,
                    ListPrice = 80,
                    Size = ProductSize.Small
                },
                [2] = new Product
                {
                    ProductID = 2,
                    Name = "Classic Vest, L",
                    Code = "VE-C304-L",
                    ReorderQty = 10,
                    Qty = 50,
                    ListPrice = 65,
                    Size = ProductSize.Large
                },
                [3] = new Product
                {
                    ProductID = 3,
                    Name = "Mountain Bike Socks, M",
                    Code = "SO-B909-M",
                    ReorderQty = 25,
                    Qty = 20,
                    ListPrice = 40,
                    Size = ProductSize.Medium
                },
                [4] = new Product
                {
                    ProductID = 4,
                    Name = "Long-Sleeve Logo Jersey, XL",
                    Code = "LJ-0192-X",
                    ReorderQty = 15,
                    Qty = 55,
                    ListPrice = 62,
                    Size = ProductSize.XtraLarge
                },
                [5] = new Product
                {
                    ProductID = 5,
                    Name = "Women's Mountain Shorts, S",
                    Code = "SH-W890-S",
                    ReorderQty = 10,
                    Qty = 18,
                    ListPrice = 62,
                    Size = ProductSize.Small
                }
            };
        }
    }
    
    
  5. Register Repository with Interface for DI

    Using Dependency Injection you can decouple the different pieces of the application. ASP.NET Core injects objects of dependency of class/service through constructor or method by using built-in IoC Container. The built-in container is represented by IServiceProvider implementation and classes will be added to IServiceCollection.

    There are three ways to register service type through .NET Core built-in IoC container. Singleton, Transient, Scoped. The method which you used to register service type decides the lifetime of the service instance.

    As our data is static, for this tutorial we will use Singleton, which will create only one instance of service and all requests will be handled by the same instance.

    Open Startup.cs file and add the following code in the ConfigureServices method.

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IProductRepository, ProductRepository>();
        services.AddControllersWithViews();
    }
    
  6. Model binding with list of object

    In this step, you will bind a list of Model objects returned by the Repository class. In ProductRepository we have defined Products property that returns a static list of Products.

    Add ProductController

    Add a controller under Areas -> Products -> Controllers folder by right click on Controllers folder select Add Controller - > MVC Controller - Empty -> Enter name -> ProductController.

    We have registered dependency injection in the previous step. In ProductController define a variable with name repository and set it by injecting dependency in constructor. Add following code to ProductController.cs file.

    namespace MVC5Tutorial.Areas.Products.Controllers
    {   
        [Area("products")]
        [Route("/product")]
        public class ProductController : Controller
        {
            private readonly IProductRepository repository;
            
            public ProductController(IProductRepository repo)
            {
                repository = repo;
            }
        }
    }
    

    Actionmethod to List All Products

    Add a MVC Actionmethod to the ProductController with name ListAllProducts. This ActionMethod will read repository property Products to get list of Product Model objects.

    Add following code to ProductController.cs file.

    [Route("/ListAllProducts")]
    public IActionResult ListAllProducts()
    {
        IEnumerable<Product> products =  repository.Products;
        return View(products);
    }
    

    The entire code of ProductController can be view at GitHub.

    Add View to display list of products

    Right click on ListAllProducts action method and select Add View -> Razor View Empty -> Name it as ListAllProducts.cshtml. Add following code to it, that shows list of products in table structure binded in controller.

    
    @model IEnumerable<MVC5Tutorial.Areas.Products.Models.Product>
    
    @{
        ViewData["Title"] = "Index";
        Layout = "~/Views/Shared/_Layout.cshtml";
    }
    
    <h2>List All Products</h2>
    
    <div class="table-responsive">
    <table class="table table-bordered table-condensed
            table-striped table-hover sortable">
        
        @if (Model.Count() == 0)
        {
            <tr>
                <td colspan="10">No Record's found.</td>
            </tr>
        }
        else
        {
            <thead>
            <tr class="danger">
                <th>ID</th>
                <th>Name</th>
                <th>Code</th>
                <th>Qty</th>
                <th>Reorder Qty</th>
                <th>List Price</th>
                <th>Size</th>
                <th>Edit</th>
            </tr>
            </thead>
    
            foreach (var item in Model)
            {
                <tr>
                    <td>@item.ProductID</td>
                    <td>@item.Name</td>
                    <td>@item.Code</td>
                    <td>@item.Qty</td>
                    <td>@item.ReorderQty</td>
                    <td>@item.ListPrice</td>
                    <td>@item.Size</td>
                    <td><a href="/GetProductDetails/@(@item.ProductID)">Details</a></td>
                </tr>
            }
        }
    </table>
    </div>
    
    

    Run your ASP.NET Core MVC application and navigate to https://localhost:<port number> /ListAllProducts. You will see a list of product objects rendered in the table.

    ASP.NET Core MVC Model binding to list object

  7. HttpGet ActionMethod to show product details

    In the previous step, you displayed a list of Product Model objects and a Details link with each product to show product details. In this step, you will add a HttpGet Action Method that gets ProductID from URL as Route value.

    Controller ActionMethod will get details of the product from the repository depending on the route value provided for ProductID. Add following ActionMethod with GetProductDetails in ProductController.cs file.

    [Route("/GetProductDetails/{id}")]
    public IActionResult GetProductDetails(int id)
    {
        return View(repository[id]);
    }
                

    Add Details View

    Add a new view with name GetProductDetails.cshtml and add following HTML to it. This view displays data from only one object of Product model.

    @model MVC5Tutorial.Areas.Products.Models.Product
    
    @{
        ViewData["Title"] = "GetProductDetails";
        Layout = "~/Views/Shared/_Layout.cshtml";
    }
    
    <h2>Product Details: @Model.Name</h2>
    <div>
    <table class="table table-sm table-bordered table-striped">
        <tr><th>ID: </th><td>@Model.ProductID</td></tr>
        <tr><th>Name: </th><td>@Model.Name</td></tr>
        <tr><th>Code: </th><td>@Model.Code</td></tr>
        <tr><th>Qty: </th><td>@Model.Qty</td></tr>
        <tr><th>Reorder Qty: </th><td>@Model.ReorderQty</td></tr>
        <tr><th>Price: </th><td>@Model.ListPrice</td></tr>
        <tr><th>Size: </th><td>@Model.Size</td></tr>
    </table>
    </div>
    
    

    This shows model binding data as:

    ASP.NET Core MVC Model Binding

Source code on Git hub Source Code on Github

Speak your mind
Please login to post your comment!