ASP.NET Core 2.0 MVC Model Binding






1.44/5 (2 votes)
How does model binding work in ASP.NET Core MVC. Continue reading...
Problem
How does model binding work in ASP.NET Core MVC.
Solution
In an empty project, change Startup
class to add services and middleware for MVC:
public void ConfigureServices(
IServiceCollection services)
{
services.AddMvc();
}
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Add the following code to HomeController
, demonstrating binding of simple types:
// Bind: route, query, form
public IActionResult Simple(int id)
{
return Content($"Simple-Default: {id}");
}
// Bind: query
public IActionResult SimpleQuery([FromQuery]int id)
{
return Content($"Simple-Query: {id}");
}
// Bind: form
public IActionResult SimpleForm([FromForm]int id)
{
return Content($"Simple-Form: {id}");
}
// Bind:
public IActionResult SimpleBodyWithoutModel([FromBody]int id)
{
return Content($"Simple-Body-Primitive: {id}");
}
// Bind: body
public IActionResult SimpleBodyWithModel([FromBody]SimpleBodyInputModel model)
{
return Content($"Simple-Body-Model: {model.Id}");
}
// Bind: header
public IActionResult SimpleHeader(
[FromHeader]string host,
[FromHeader(Name = "User-Agent")]string userAgent)
{
return Content($"Simple-Header: {host}, {userAgent}");
}
Add the following code to HomeController
, demonstrating binding of complex types:
// Bind: query, form
public IActionResult Complex(GreetingInputModel model)
{
return Content($"Complex-Default: {model.Type} {model.To}");
}
// Bind: query
public IActionResult ComplexQuery([FromQuery]GreetingInputModel model)
{
return Content($"Complex-Query: {model.Type} {model.To}");
}
// Bind: form
public IActionResult ComplexForm([FromForm]GreetingInputModel model)
{
return Content($"Complex-Form: {model.Type} {model.To}");
}
// Bind: body
public IActionResult ComplexBody([FromBody]GreetingInputModel model)
{
return Content($"Complex-Body: {model.Type} {model.To}");
}
// Bind: header
public IActionResult ComplexHeader(HeaderInputModel model)
{
return Content($"Complex-Header: {model.Host}, {model.UserAgent}");
}
Add the following code to HomeController
, demonstrating binding of collections:
// Bind: query
public IActionResult Collection(IEnumerable values)
{
return Content($"Collection-Simple: {values.Count()}");
}
// Bind: body
public IActionResult CollectionComplex([FromBody]CollectionInputModel model)
{
return Content($"Collection-Complex: {model.Values.Count()}");
}
Add the following code to HomeController
, demonstrating binding from multiple sources:
// Order: form > route > query
public IActionResult Multiple(int id)
{
return Content($"Multiple: {id}");
}
Discussion
In a previous post on Routing, I showed how MVC map URLs to controller and action to execute. We touched on the topic of model binding; mechanism through which MVC binds routing template tokens to action parameters.
Routing middleware deconstructs the HTTP request into RouteData
class and then binds its values to action parameters by name. Below is a screenshot showing the Keys and Values contained within RouteData
class:
The action parameters can be simple or complex types. For complex types, model binding uses reflection to bind data using .
convention. The type must contain a default constructor and public
writable properties. See how query string
values populate a complex type:
Note that failure in binding doesn’t throw an exception. Also the process of binding stops as soon as a match is found for a parameter (see Multiple Sources section below).
Binding with Attributes
Attributes on action parameters can be used to override the default binding behaviour:
- Override binding behaviour
[BindRequired]
: Add model state error if binding fails[BindNever]
: Ignore the binding of parameter
- Override binding source
[FromHeader]
: Use HTTP header[FromQuery]
: Use URL query string[FromRoute]
: Use routing values[FromForm]
: Use form values (via HTTP POST)[FromServices]
: Inject value using dependency injection[FromBody]
: Use HTTP request body, based on configured formatter (e.g. JSON, XML). Only one action parameter can have this attribute.
- Supply custom binding
[ModelBinder]
: Provide custom model binder
Binding Behaviour
When binding attributes are not supplied, the default behaviour is:
- Simple Types: binding source can be route values, query string and form data
- Complex Types: binding source can be query string and form data
Binding Header
When binding to HTTP headers, few points to note are:
For complex types, the [FromHeader]
attribute is applied to the type’s properties. If the HTTP header contain characters illegal for variable names, they can be supplied via attributes Name
property:
For simple types:
Binding Body
HTTP response body can be read into a complex type. By default, JsonInputFormatter
class is used to handle binding of request body. The input formatters are used based on Content-Type
property of HTTP request header. Below is a sample request with JSON body:
In order to use XML as input formatters, modify ConfigureServices
:
public void ConfigureServices(
IServiceCollection services)
{
services.AddMvc()
.AddXmlSerializerFormatters();
}
Now in addition to JSON, you could send XML in request
body (remember to change the Content-Type
to application/xml
):
Binding Collections
Binding works with collection too:
public class CollectionInputModel
{
public IEnumerable Values { get; set; }
}
public IActionResult CollectionComplex([FromBody]CollectionInputModel model)
{
return Content($"Collection-Complex: {model.Values.Count()}");
}
Multiple Sources
MVC tries to bind data from other parts of HTTP request in the following sequence:
Form
: form data submitted via HTTPPOST
Route
: route values deconstructed by routing middlewareQuery
: query string segment of URL