Model binding is the process of taking incoming request data (like form posts, route data, query strings, etc.) and creating a strongly-typed .NET object from it. This object is typically passed as a parameter to an action method in an MVC controller or a Razor Page handler method.
When a request arrives, ASP.NET Core's model binder attempts to match incoming data from various sources to the properties of the target model object. The default model binder supports binding from:
The model binder looks for properties on the target model that have names matching the names of the incoming data. For example, if you have a model with a property named UserName
, the model binder will look for a form field, query string parameter, or route parameter named UserName
.
You can explicitly specify which sources the model binder should use for a particular parameter using the [Bind{Source}]
attributes.
[FromQuery]
: Binds from the query string.[FromRoute]
: Binds from route data.[FromForm]
: Binds from form values.[FromBody]
: Binds from the request body (typically JSON).[FromHeader]
: Binds from HTTP headers.Tip: When using [FromBody]
for complex types, it's common to bind a single parameter representing the entire request payload, often a JSON object.
Consider an MVC controller:
using Microsoft.AspNetCore.Mvc;
public class ProductsController : Controller
{
public IActionResult Index([FromQuery] int categoryId)
{
// categoryId will be bound from the query string, e.g., /Products?categoryId=123
return View();
}
[HttpPost]
public IActionResult Create([FromBody] Product product)
{
// 'product' will be bound from the JSON request body
// Example JSON body: { "Name": "Gadget", "Price": 19.99 }
if (ModelState.IsValid)
{
// Save product to database...
return Ok(product);
}
return BadRequest(ModelState);
}
public IActionResult Edit(int id, [FromForm] ProductDetails details)
{
// 'id' is from the route, 'details' is from form values
// Example URL: /Products/Edit/5
// Example Form Data: Name=Super Gadget&Description=A really cool item
return View();
}
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public class ProductDetails
{
public string Name { get; set; }
public string Description { get; set; }
}
In Razor Pages, model binding works similarly for handler methods:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
public class MyPageModel : PageModel
{
public string Message { get; set; }
public void OnGet(string searchString)
{
// searchString will be bound from the query string, e.g., /MyPage?searchString=books
Message = $"You searched for: {searchString}";
}
public IActionResult OnPost([FromBody] Order order)
{
// 'order' will be bound from the JSON request body
if (ModelState.IsValid)
{
// Process order...
return new JsonResult(new { success = true, orderDetails = order });
}
return BadRequest(ModelState);
}
}
public class Order
{
public int OrderId { get; set; }
public List<string> Items { get; set; }
}
Model binding supports complex types (objects with properties) and collections (arrays, lists).
When binding a complex type, the model binder recursively binds its properties. For instance, if you have a Person
model with Address
property (which is another complex type), the binder will look for data like Address.Street
and Address.City
.
For collections, data can be provided using indexed names. For example, for a list of strings named Tags
, you might see form data like Tags[0]=asp.net
, Tags[1]=core
.
Important: Ensure that your complex type properties and collection elements have appropriate public setters for the model binder to work correctly.
Model binding is often followed by model validation. You can decorate your model classes with data annotations (e.g., [Required]
, [StringLength]
, [Range]
) to define validation rules.
ASP.NET Core automatically integrates with these attributes. If validation fails, the ModelState.IsValid
property will be false, and validation errors are accessible via ModelState
.
using System.ComponentModel.DataAnnotations;
public class Product
{
public int Id { get; set; }
[Required(ErrorMessage = "Product name is required.")]
[StringLength(100, ErrorMessage = "Product name cannot exceed 100 characters.")]
public string Name { get; set; }
[Range(0.01, 1000.00, ErrorMessage = "Price must be between 0.01 and 1000.00.")]
public decimal Price { get; set; }
}
For advanced scenarios, you can create custom model binders or model binder providers.
IModelBinder
interface to gain full control over how a specific type is bound.IModelBinderProvider
interface to influence which binder is chosen for a given type or context.These are typically registered in the ConfigureServices
method of your Startup.cs
file.