In this Model binding in ASP.NET Core article, we will learn How to pass data from View to Controller. We learn what is Model binding is and how it works. The ASP.NET core allows us to bind data from various binding sources like HTML Forms using [FromForm], Route Values using [FromRoute], Query string using [FromQuery], Request body using [FromBody] and From Request Header using [FromHeader]. We will look at all these in this chapter
Here is the link to previous tutorials
- Model and ViewModel
- Passing ViewModel from Controller to View
- Building Forms in Views
- Creating Strongly typed Views
- Using Tag Helpers to Create Forms
Table of Contents
What is Model Binding
The Model binding is the process of mapping the data posted over an HTTP request to the parameters of the action method in the Controller.
The HTTP Request can contain data in various formats. The data can contain in the HTML form fields. It could be part of the route values. It could be part of the query string or it may contain in the body of the request.
ASP.NET Core model binding mechanism allows us easily bind those values to the parameters in the action method. These parameters can be of the primitive type or complex type.
Getting Form Data in Controller
In the tutorial on Tag Helpers, we created a simple Form, which accepted the Product detail. When the user clicked on the submit button it posted the data to the create controller action method.
In the above project, we created a ProductEditModel object, which contains the details of the product that needs to be created or edited
1 2 3 4 5 6 7 8 9 | public class ProductEditModel { public int ID{ get; set; } public string Name { get; set; } public decimal Rate { get; set; } public int Rating { get; set; } } |
A form is created to which contains three form fields. Name, Rate and Rating.
1 2 3 4 5 6 7 8 9 10 11 12 | <form action="/home/Create" method="post"> <label for="Name">Name</label> <input type="text" name="Name" /> <label for="Rate">Rate</label> <input type="text" name="Rate" /> <label for="Rating">Rating</label> <input type="text" name="Rating" /> <input type="submit" name="submit" /> </form> |
The Create action method in the HomeController.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | [HttpPost] public IActionResult Create(ProductEditModel model) { string message = ""; if (ModelState.IsValid) { message = "product " + model.Name + " created successfully" ; } else { message = "Failed to create the product. Please try again"; } return Content(message); } |
On the submission of the above form, the values in the form fields are automatically mapped to the ProductEditModel object of the action method of the controller.
1 2 3 | public IActionResult Create(ProductEditModel model) |
This automatically happens behind the scene by a Process called Model Binding. The Model binder looks for a match in the form fields, Route Values and query strings.
How Model Binding works
The image below illustrates how model binding works.
When the user clicks on the Submit button a Post request is sent to the server along with the Form Data, Query String, Route Parameters etc.
The MVCRouteHandler of the Routing Engine handles the incoming request and is responsible for invoking the action method create.
The Model binder kicks in just before the action method invocation. It looks for a match in the form data, query strings, and request parameters in the HTTP Request data.
Then, It tries to bind the values to the action parameter by Name.
For Example, the “name” form field is mapped to the “name” property of the ProductEditModel. Rate Form field is mapped to the Rate Property and henceforth.
For the Binding to work correctly
- The Property Name must match with the Request data
- Properties must be defined as public settable
Model Binder
The Model binder is responsible to populate the action method parameter. The Model Binder is created by the model binder provider.
The Model binder must implement the provider interface is IModelBinderProvider.
Which, means you can create your own Model binder or extend the existing one by implementing the IModelBinderProvider. The customer model binder must be registered in the ModelBinderProviders collection at the startup.cs as follows.
1 2 3 4 5 6 | services.AddMvc(options => { options.ModelBinderProviders.Add(new CustomModelBinderProvider()); }); |
ModelState
If Model binder fails to bind the incoming Request data to the corresponding model property, then it does not throw any error but fails silently. But it updates ModelState object with the list of errors and sets the isValid property to false.
Hence, checking for ModelState.isValid tells us whether the model binding succeeded or not.
For Example, in the above example clicking on the submit button without entering any data in any of the fields will result in validation failure and hence the ModelState.isValid becomes false.
Life without model binding
Before further exploring the Model binding, we need to understand how you can access data without using the model binding.
Accessing the HTML Forms directly
To do that we need to access the Request object to access the values the data.
Open the code, which we created in the previous tutorial,
Add the new action method NoModelBinding action method
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | [HttpPost] public IActionResult NoModelBinding() { ProductEditModel model = new ProductEditModel(); string message = ""; model.Name = Request.Form["Name"].ToString(); model.Rate = Convert.ToDecimal( Request.Form["Rate"]); model.Rating =Convert.ToInt32( Request.Form["Rateing"]); message = "product " + model.Name + " created successfully"; return Content(message); } |
And change to
1 2 3 | <form action="/home/NoModelBinding" method="post"> |
Accessing the query string directly
Similarly, you can access the query string values directly using the Request.Query collection, which is parsed from the Query String.
For Example Request.Query[“id”].ToString() returns the value of id query string.
The Request.QueryString.HasValue will let you know if the Query string is present in the URL and Request.QueryString.Value will return the raw query string.
Accessing the Request Headers directly
Similarly you can use the Request.Headers collection to access the values present the HTTP Headers
Accessing the Route Data directly
To Access the route you need to Override the OnActionExecuting method as shown below
1 2 3 4 5 6 7 8 9 | using Microsoft.AspNetCore.Mvc.Filters; public override void OnActionExecuting(ActionExecutingContext context) { string id = context.RouteData.Values["id"].ToString(); base.OnActionExecuting(context); } |
As you can see it takes a lot of code to access the values posted over HTTP Request. The ASP.NET Core Model binding hides all those intricacies for us.
Model Binding Sources
As mentioned earlier, the model binder looks for the data from the various data sources. Here is the list of data sources in the order that model binding looks through them
- HTML Form Values
- Route Values
- Query Strings
The Model binder can also look for the data from the following sources also, but to do that we need to specify the binding source explicitly.
- Request Body
- Request Header
- Services
Getting data from Form and query string
Let us try to bind action parameter to both Form and query string. FormAndQuery action method as shown below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | [HttpGet] public IActionResult FormAndQuery() { return View(); } [HttpPost] public IActionResult FormAndQuery(string name,ProductEditModel model) { string message = ""; if (ModelState.IsValid) { message = "Query string "+ name + " product " + model.Name + " Rate " + model.Rate + " Rating " + model.Rating ; } else { message = "Failed to create the product. Please try again"; } return Content(message); } |
Note that the Action Method FormAndQuery takes two arguments name and ProductEditModel.
1 2 3 | public IActionResult FormAndQuery(string name,ProductEditModel model) |
Next, create the view FormAndQuery and the following code.
1 2 3 4 5 6 7 8 9 10 11 12 | <form action="/home/FormAndQuery/?name=Test" method="post"> <label for="Name">Name</label> <input type="text" name="Name" /> <label for="Rate">Rate</label> <input type="text" name="Rate" /> <label for="Rating">Rating</label> <input type="text" name="Rating" /> <input type="submit" name="submit" /> </form> |
The form has a name field. We are also sending name=test as the query string to the controller action
1 2 3 | <form action="/home/FormAndQuery/?name=Test" method="post"> |
In the above example, the parameter “name” appears twice as the part of the Form and also part of the query string
When this form is submitted, the “name” parameter is always mapped to the Form field and not to the query string.
That is because the Model binder always uses the following order to map the data source fields to the parameter. And the first match wins.
- Form Values
- Route Values
- Query strings
Since the Form Values has a name field, the name parameter is always populated by it.
We can change this behaviour by decorating the name parameter by using the [FromQuery] attribute.
Apply the [FromQuery] attribute to the name parameter as shown below
public IActionResult FormAndQuery([FromQuery] string name,ProductEditModel model)
Now, if you submit the form, the name parameter gets the value from the query string, while productEditModel correctly populated from the form values
Controlling the Binding Source
In the previous example, we used [FromQuery] attribute to force model binder to change its default behaviour and use query string as the source for binding.
The ASP.NET Core gives us several such attributes to control and choose from what source we want to receive the binding data
- [FromForm]
- [FromRoute]
- [FromQuery]
- [FromBody]
- [FromHeader]
- [FromServices]
[FromForm]
The [FromForm] forces the model binder to bind the parameter to the fields of the HTML Forms.
1 2 3 4 5 6 | [HttpPost] public IActionResult Create([FromForm] ProductEditModel model) { } |
[FromRoute]
The [FromRoute] forces the model binder to bind that parameter to Route data from the current request.
Let us see how to use it with a live example.
Create a FromRoute action method, which accepts the id and ProductEditModel
We have two id parameter. The MVC default route has Id Parameter, which is optional. The ProductEditModel also has an id property
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | [HttpGet] public IActionResult FromRoute() { return View(); } [HttpPost] public IActionResult FromRoute(string id, ProductEditModel model) { string message = ""; if (ModelState.IsValid) { message = "Route " + id + " Product id " + model.id + " product " + model.Name + " Rate " + model.Rate + " Rating " + model.Rating; } else { message = "Failed to create the product. Please try again"; } return Content(message); } |
Create FromRoute view
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <form action="/home/FromRoute/Test" method="post"> <label for="id">id</label> <input type="text" name="id" /> <label for="Name">Name</label> <input type="text" name="Name" /> <label for="Rate">Rate</label> <input type="text" name="Rate" /> <label for="Rating">Rating</label> <input type="text" name="Rating" /> <input type="submit" name="submit" /> </form> |
Note that we are invoking the controller action by using the “test” as the route value.
1 2 3 | <form action="/home/FromRoute/Test" method="post"> |
Now, when you submit the form, the id parameter is always mapped to the id Form field and not from the route value.
Now Open the HomeController and apply [FromRoute] Attribute on the id Parameter as shown below.
1 2 3 | public IActionResult FromRoute([FromRoute] string id, ProductEditModel model) |
Now, when you submit the form the id is populated with “test”.
Binding Query string using [FromQuery]
[FromQuery] forces the model binder to bind the parameter to the value obtained from the query string.
Binding to Request body Using [FromBody]
[FromBody] forces the model binder to bind a parameter to data from the request body. The formatter is selected based on the content-type of the request.
The data in the request body come in the variety of formats including JSON, XML and many others. The Model binder looks at the content-type header to select the formatter to parse the data contained in the request body.
For example, when the content-type is “application/json”, the model binder uses the JsonInputFormatter class to parse the request body and map it to the parameter.
Binding from Request Header using [FromHeader]
[FromHeader] maps the request header values to the action parameter.
1 2 3 4 5 6 | public IActionResult FromHeader( [FromHeader] string Host) { return Content(Host); } |
Disabling Binding with [BindNever]
The Tells the model binder to never bind to the Property.
For Example,
1 2 3 4 | [BindNever] public int Rating { get; set; } |
Now, model binder ignore the Rating Property, even if the form has Rating field.
Forcing Binding with [BindRequired]
This is exactly opposite of BindNever. The field marked with BindRequired must always present in the form field and binding must occur, otherwise will Modelstate.isValid marked as false.
Summary
We looked at how you can pass data from view to controller using Model Binder, The Model binder automatically binds the fields to Model Property behind the scene. You can modify the default behaviour of the Model binder by using the various attributes like [FromForm], [FromQuery], [FromBody],[FromRoute],[FromHeader] , [FromServices] [BindNever] & [BindRequired] etc.
Thanks. Very useful