Model Binding in ASP.NET Core
Note
This isn't the latest version of this article. For the current release, see the .NET 9 version of this article.
Warning
This version of ASP.NET Core is no longer supported. For more information, see the .NET and .NET Core Support Policy. For the current release, see the .NET 9 version of this article.
Important
This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.
For the current release, see the .NET 9 version of this article.
This article explains what model binding is, how it works, and how to customize its behavior.
What is Model binding
Controllers and Razor pages work with data that comes from HTTP requests. For example, route data may provide a record key, and posted form fields may provide values for the properties of the model. Writing code to retrieve each of these values and convert them from strings to .NET types would be tedious and error-prone. Model binding automates this process. The model binding system:
- Retrieves data from various sources such as route data, form fields, and query strings.
- Provides the data to controllers and Razor pages in method parameters and public properties.
- Converts string data to .NET types.
- Updates properties of complex types.
Example
Suppose you have the following action method:
[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)
And the app receives a request with this URL:
https://contoso.com/api/pets/2?DogsOnly=true
Model binding goes through the following steps after the routing system selects the action method:
- Finds the first parameter of
GetById
, an integer namedid
. - Looks through the available sources in the HTTP request and finds
id
= "2" in route data. - Converts the string "2" into integer 2.
- Finds the next parameter of
GetById
, a boolean nameddogsOnly
. - Looks through the sources and finds "DogsOnly=true" in the query string. Name matching is not case-sensitive.
- Converts the string "true" into boolean
true
.
The framework then calls the GetById
method, passing in 2 for the id
parameter, and true
for the dogsOnly
parameter.
In the preceding example, the model binding targets are method parameters that are simple types. Targets may also be the properties of a complex type. After each property is successfully bound, model validation occurs for that property. The record of what data is bound to the model, and any binding or validation errors, is stored in ControllerBase.ModelState or PageModel.ModelState. To find out if this process was successful, the app checks the ModelState.IsValid flag.
Targets
Model binding tries to find values for the following kinds of targets:
- Parameters of the controller action method that a request is routed to.
- Parameters of the Razor Pages handler method that a request is routed to.
- Public properties of a controller or
PageModel
class, if specified by attributes.
[BindProperty] attribute
Can be applied to a public property of a controller or PageModel
class to cause model binding to target that property:
public class EditModel : PageModel
{
[BindProperty]
public Instructor? Instructor { get; set; }
// ...
}
[BindProperties] attribute
Can be applied to a controller or PageModel
class to tell model binding to target all public properties of the class:
[BindProperties]
public class CreateModel : PageModel
{
public Instructor? Instructor { get; set; }
// ...
}
Model binding for HTTP GET requests
By default, properties are not bound for HTTP GET requests. Typically, all you need for a GET request is a record ID parameter. The record ID is used to look up the item in the database. Therefore, there is no need to bind a property that holds an instance of the model. In scenarios where you do want properties bound to data from GET requests, set the SupportsGet
property to true
:
[BindProperty(Name = "ai_user", SupportsGet = true)]
public string? ApplicationInsightsCookie { get; set; }
Model binding simple and complex types
Model binding uses specific definitions for the types it operates on. A simple type is converted from a single string using TypeConverter or a TryParse
method. A complex type is converted from multiple input values. The framework determines the difference based on the existence of a TypeConverter
or TryParse
. We recommend creating a type converter or using TryParse
for a string
to SomeType
conversion that doesn't require external resources or multiple inputs.
Sources
By default, model binding gets data in the form of key-value pairs from the following sources in an HTTP request:
- Form fields
- The request body (For controllers that have the [ApiController] attribute.)
- Route data
- Query string parameters
- Uploaded files
For each target parameter or property, the sources are scanned in the order indicated in the preceding list. There are a few exceptions:
- Route data and query string values are used only for simple types.
- Uploaded files are bound only to target types that implement
IFormFile
orIEnumerable<IFormFile>
.
If the default source is not correct, use one of the following attributes to specify the source:
[FromQuery]
- Gets values from the query string.[FromRoute]
- Gets values from route data.[FromForm]
- Gets values from posted form fields.[FromBody]
- Gets values from the request body.[FromHeader]
- Gets values from HTTP headers.
These attributes:
Are added to model properties individually and not to the model class, as in the following example:
public class Instructor { public int Id { get; set; } [FromQuery(Name = "Note")] public string? NoteFromQueryString { get; set; } // ... }
Optionally accept a model name value in the constructor. This option is provided in case the property name doesn't match the value in the request. For instance, the value in the request might be a header with a hyphen in its name, as in the following example:
public void OnGet([FromHeader(Name = "Accept-Language")] string language)
[FromBody] attribute
Apply the [FromBody]
attribute to a parameter to populate its properties from the body of an HTTP request. The ASP.NET Core runtime delegates the responsibility of reading the body to an input formatter. Input formatters are explained later in this article.
When [FromBody]
is applied to a complex type parameter, any binding source attributes applied to its properties are ignored. For example, the following Create
action specifies that its pet
parameter is populated from the body:
public ActionResult<Pet> Create([FromBody] Pet pet)
The Pet
class specifies that its Breed
property is populated from a query string parameter:
public class Pet
{
public string Name { get; set; } = null!;
[FromQuery] // Attribute is ignored.
public string Breed { get; set; } = null!;
}
In the preceding example:
- The
[FromQuery]
attribute is ignored. - The
Breed
property is not populated from a query string parameter.
Input formatters read only the body and don't understand binding source attributes. If a suitable value is found in the body, that value is used to populate the Breed
property.
Don't apply [FromBody]
to more than one parameter per action method. Once the request stream is read by an input formatter, it's no longer available to be read again for binding other [FromBody]
parameters.
Additional sources
Source data is provided to the model binding system by value providers. You can write and register custom value providers that get data for model binding from other sources. For example, you might want data from cookies or session state. To get data from a new source:
- Create a class that implements
IValueProvider
. - Create a class that implements
IValueProviderFactory
. - Register the factory class in
Program.cs
.
The sample includes a value provider and factory example that gets values from cookies. Register custom value provider factories in Program.cs
:
builder.Services.AddControllers(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});
The preceding code puts the custom value provider after all built-in value providers. To make it the first in the list, call Insert(0, new CookieValueProviderFactory())
instead of Add
.
No source for a model property
By default, a model state error isn't created if no value is found for a model property. The property is set to null or a default value:
- Nullable simple types are set to
null
. - Non-nullable value types are set to
default(T)
. For example, a parameterint id
is set to 0. - For complex Types, model binding creates an instance by using the default constructor, without setting properties.
- Arrays are set to
Array.Empty<T>()
, except thatbyte[]
arrays are set tonull
.
If model state should be invalidated when nothing is found in form fields for a model property, use the [BindRequired]
attribute.
Note that this [BindRequired]
behavior applies to model binding from posted form data, not to JSON or XML data in a request body. Request body data is handled by input formatters.
Type conversion errors
If a source is found but can't be converted into the target type, model state is flagged as invalid. The target parameter or property is set to null or a default value, as noted in the previous section.
In an API controller that has the [ApiController]
attribute, invalid model state results in an automatic HTTP 400 response.
In a Razor page, redisplay the page with an error message:
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
// ...
return RedirectToPage("./Index");
}
When the page is redisplayed by the preceding code, the invalid input isn't shown in the form field. This is because the model property has been set to null or a default value. The invalid input does appear in an error message. If you want to redisplay the bad data in the form field, consider making the model property a string and doing the data conversion manually.
The same strategy is recommended if you don't want type conversion errors to result in model state errors. In that case, make the model property a string.
Simple types
See Model binding simple and complex types for explanation of simple and complex types.
The simple types that the model binder can convert source strings into include the following:
- Boolean
- Byte, SByte
- Char
- DateOnly
- DateTime
- DateTimeOffset
- Decimal
- Double
- Enum
- Guid
- Int16, Int32, Int64
- Single
- TimeOnly
- TimeSpan
- UInt16, UInt32, UInt64
- Uri
- Version
Bind with IParsable<T>.TryParse
The IParsable<TSelf>.TryParse
API supports binding controller action parameter values:
public static bool TryParse (string? s, IFormatProvider? provider, out TSelf result);
The following DateRange
class implements IParsable<TSelf>
to support binding a date range:
public class DateRange : IParsable<DateRange>
{
public DateOnly? From { get; init; }
public DateOnly? To { get; init; }
public static DateRange Parse(string value, IFormatProvider? provider)
{
if (!TryParse(value, provider, out var result))
{
throw new ArgumentException("Could not parse supplied value.", nameof(value));
}
return result;
}
public static bool TryParse(string? value,
IFormatProvider? provider, out DateRange dateRange)
{
var segments = value?.Split(',', StringSplitOptions.RemoveEmptyEntries
| StringSplitOptions.TrimEntries);
if (segments?.Length == 2
&& DateOnly.TryParse(segments[0], provider, out var fromDate)
&& DateOnly.TryParse(segments[1], provider, out var toDate))
{
dateRange = new DateRange { From = fromDate, To = toDate };
return true;
}
dateRange = new DateRange { From = default, To = default };
return false;
}
}
The preceding code:
- Converts a string representing two dates to a
DateRange
object - The model binder uses the
IParsable<TSelf>.TryParse
method to bind theDateRange
.
The following controller action uses the DateRange
class to bind a date range:
// GET /WeatherForecast/ByRange?range=7/24/2022,07/26/2022
public IActionResult ByRange([FromQuery] DateRange range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
&& DateOnly.FromDateTime(wf.Date) <= range.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d"),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
The following Locale
class implements IParsable<TSelf>
to support binding to CultureInfo
:
public class Locale : CultureInfo, IParsable<Locale>
{
public Locale(string culture) : base(culture)
{
}
public static Locale Parse(string value, IFormatProvider? provider)
{
if (!TryParse(value, provider, out var result))
{
throw new ArgumentException("Could not parse supplied value.", nameof(value));
}
return result;
}
public static bool TryParse([NotNullWhen(true)] string? value,
IFormatProvider? provider, out Locale locale)
{
if (value is null)
{
locale = new Locale(CurrentCulture.Name);
return false;
}
try
{
locale = new Locale(value);
return true;
}
catch (CultureNotFoundException)
{
locale = new Locale(CurrentCulture.Name);
return false;
}
}
}
The following controller action uses the Locale
class to bind a CultureInfo
string:
// GET /en-GB/WeatherForecast
public IActionResult Index([FromRoute] Locale locale)
{
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d", locale),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View(weatherForecasts);
}
The following controller action uses the DateRange
and Locale
classes to bind a date range with CultureInfo
:
// GET /af-ZA/WeatherForecast/RangeByLocale?range=2022-07-24,2022-07-29
public IActionResult RangeByLocale([FromRoute] Locale locale, [FromQuery] string range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
if (!DateRange.TryParse(range, locale, out DateRange rangeResult))
{
ModelState.TryAddModelError(nameof(range),
$"Invalid date range: {range} for locale {locale.DisplayName}");
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
}
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= rangeResult.From
&& DateOnly.FromDateTime(wf.Date) <= rangeResult.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d", locale),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int) (wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
The API sample app on GitHub shows the preceding sample for an API controller.
Bind with TryParse
The TryParse
API supports binding controller action parameter values:
public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);
IParsable<T>.TryParse
is the recommended approach for parameter binding because unlike TryParse
, it doesn't depend on reflection.
The following DateRangeTP
class implements TryParse
:
public class DateRangeTP
{
public DateOnly? From { get; }
public DateOnly? To { get; }
public DateRangeTP(string from, string to)
{
if (string.IsNullOrEmpty(from))
throw new ArgumentNullException(nameof(from));
if (string.IsNullOrEmpty(to))
throw new ArgumentNullException(nameof(to));
From = DateOnly.Parse(from);
To = DateOnly.Parse(to);
}
public static bool TryParse(string? value, out DateRangeTP? result)
{
var range = value?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (range?.Length != 2)
{
result = default;
return false;
}
result = new DateRangeTP(range[0], range[1]);
return true;
}
}
The following controller action uses the DateRangeTP
class to bind a date range:
// GET /WeatherForecast/ByRangeTP?range=7/24/2022,07/26/2022
public IActionResult ByRangeTP([FromQuery] DateRangeTP range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
&& DateOnly.FromDateTime(wf.Date) <= range.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d"),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
Complex types
A complex type must have a public default constructor and public writable properties to bind. When model binding occurs, the class is instantiated using the public default constructor.
For each property of the complex type, model binding looks through the sources for the name pattern prefix.property_name. If nothing is found, it looks for just property_name without the prefix. The decision to use the prefix isn't made per property. For example, with a query containing ?Instructor.Id=100&Name=foo
, bound to method OnGet(Instructor instructor)
, the resulting object of type Instructor
contains:
Id
set to100
.Name
set tonull
. Model binding expectsInstructor.Name
becauseInstructor.Id
was used in the preceding query parameter.
For binding to a parameter, the prefix is the parameter name. For binding to a PageModel
public property, the prefix is the public property name. Some attributes have a Prefix
property that lets you override the default usage of parameter or property name.
For example, suppose the complex type is the following Instructor
class:
public class Instructor
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
Prefix = parameter name
If the model to be bound is a parameter named instructorToUpdate
:
public IActionResult OnPost(int? id, Instructor instructorToUpdate)
Model binding starts by looking through the sources for the key instructorToUpdate.ID
. If that isn't found, it looks for ID
without a prefix.
Prefix = property name
If the model to be bound is a property named Instructor
of the controller or PageModel
class:
[BindProperty]
public Instructor Instructor { get; set; }
Model binding starts by looking through the sources for the key Instructor.ID
. If that isn't found, it looks for ID
without a prefix.
Custom prefix
If the model to be bound is a parameter named instructorToUpdate
and a Bind
attribute specifies Instructor
as the prefix:
public IActionResult OnPost(
int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)
Model binding starts by looking through the sources for the key Instructor.ID
. If that isn't found, it looks for ID
without a prefix.
Attributes for complex type targets
Several built-in attributes are available for controlling model binding of complex types:
Warning
These attributes affect model binding when posted form data is the source of values. They do not affect input formatters, which process posted JSON and XML request bodies. Input formatters are explained later in this article.
[Bind] attribute
Can be applied to a class or a method parameter. Specifies which properties of a model should be included in model binding. [Bind]
does not affect input formatters.
In the following example, only the specified properties of the Instructor
model are bound when any handler or action method is called:
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
In the following example, only the specified properties of the Instructor
model are bound when the OnPost
method is called:
[HttpPost]
public IActionResult OnPost(
[Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
The [Bind]
attribute can be used to protect against overposting in create scenarios. It doesn't work well in edit scenarios because excluded properties are set to null or a default value instead of being left unchanged. For protection against overposting, view models are recommended rather than the [Bind]
attribute. For more information, see Security note about overposting.
[ModelBinder] attribute
ModelBinderAttribute can be applied to types, properties, or parameters. It allows specifying the type of model binder used to bind the specific instance or type. For example:
[HttpPost]
public IActionResult OnPost(
[ModelBinder<MyInstructorModelBinder>] Instructor instructor)
The [ModelBinder]
attribute can also be used to change the name of a property or parameter when it's being model bound:
public class Instructor
{
[ModelBinder(Name = "instructor_id")]
public string Id { get; set; }
// ...
}
[BindRequired] attribute
Causes model binding to add a model state error if binding cannot occur for a model's property. Here's an example:
public class InstructorBindRequired
{
// ...
[BindRequired]
public DateTime HireDate { get; set; }
}
See also the discussion of the [Required]
attribute in Model validation.
[BindNever] attribute
Can be applied to a property or a type. Prevents model binding from setting a model's property. When applied to a type, the model binding system excludes all properties the type defines. Here's an example:
public class InstructorBindNever
{
[BindNever]
public int Id { get; set; }
// ...
}
Collections
For targets that are collections of simple types, model binding looks for matches to parameter_name or property_name. If no match is found, it looks for one of the supported formats without the prefix. For example:
Suppose the parameter to be bound is an array named
selectedCourses
:public IActionResult OnPost(int? id, int[] selectedCourses)
Form or query string data can be in one of the following formats:
selectedCourses=1050&selectedCourses=2000
selectedCourses[0]=1050&selectedCourses[1]=2000
[0]=1050&[1]=2000
selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
[a]=1050&[b]=2000&index=a&index=b
Avoid binding a parameter or a property named
index
orIndex
if it is adjacent to a collection value. Model binding attempts to useindex
as the index for the collection which might result in incorrect binding. For example, consider the following action:public IActionResult Post(string index, List<Product> products)
In the preceding code, the
index
query string parameter binds to theindex
method parameter and also is used to bind the product collection. Renaming theindex
parameter or using a model binding attribute to configure binding avoids this issue:public IActionResult Post(string productIndex, List<Product> products)
The following format is supported only in form data:
selectedCourses[]=1050&selectedCourses[]=2000
For all of the preceding example formats, model binding passes an array of two items to the
selectedCourses
parameter:- selectedCourses[0]=1050
- selectedCourses[1]=2000
Data formats that use subscript numbers (... [0] ... [1] ...) must ensure that they are numbered sequentially starting at zero. If there are any gaps in subscript numbering, all items after the gap are ignored. For example, if the subscripts are 0 and 2 instead of 0 and 1, the second item is ignored.
Dictionaries
For Dictionary
targets, model binding looks for matches to parameter_name or property_name. If no match is found, it looks for one of the supported formats without the prefix. For example:
Suppose the target parameter is a
Dictionary<int, string>
namedselectedCourses
:public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
The posted form or query string data can look like one of the following examples:
selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
[1050]=Chemistry&selectedCourses[2000]=Economics
selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry& selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
[0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
For all of the preceding example formats, model binding passes a dictionary of two items to the
selectedCourses
parameter:- selectedCourses["1050"]="Chemistry"
- selectedCourses["2000"]="Economics"
Constructor binding and record types
Model binding requires that complex types have a parameterless constructor. Both System.Text.Json
and Newtonsoft.Json
based input formatters support deserialization of classes that don't have a parameterless constructor.
Record types are a great way to succinctly represent data over the network. ASP.NET Core supports model binding and validating record types with a single constructor:
public record Person(
[Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);
public class PersonController
{
public IActionResult Index() => View();
[HttpPost]
public IActionResult Index(Person person)
{
// ...
}
}
Person/Index.cshtml
:
@model Person
<label>Name: <input asp-for="Name" /></label>
<br />
<label>Age: <input asp-for="Age" /></label>
When validating record types, the runtime searches for binding and validation metadata specifically on parameters rather than on properties.
The framework allows binding to and validating record types:
public record Person([Required] string Name, [Range(0, 100)] int Age);
For the preceding to work, the type must:
- Be a record type.
- Have exactly one public constructor.
- Contain parameters that have a property with the same name and type. The names must not differ by case.
POCOs without parameterless constructors
POCOs that do not have parameterless constructors can't be bound.
The following code results in an exception saying that the type must have a parameterless constructor:
public class Person(string Name)
public record Person([Required] string Name, [Range(0, 100)] int Age)
{
public Person(string Name) : this (Name, 0);
}
Record types with manually authored constructors
Record types with manually authored constructors that look like primary constructors work
public record Person
{
public Person([Required] string Name, [Range(0, 100)] int Age)
=> (this.Name, this.Age) = (Name, Age);
public string Name { get; set; }
public int Age { get; set; }
}
Record types, validation and binding metadata
For record types, validation and binding metadata on parameters is used. Any metadata on properties is ignored
public record Person (string Name, int Age)
{
[BindProperty(Name = "SomeName")] // This does not get used
[Required] // This does not get used
public string Name { get; init; }
}
Validation and metadata
Validation uses metadata on the parameter but uses the property to read the value. In the ordinary case with primary constructors, the two would be identical. However, there are ways to defeat it:
public record Person([Required] string Name)
{
private readonly string _name;
// The following property is never null.
// However this object could have been constructed as "new Person(null)".
public string Name { get; init => _name = value ?? string.Empty; }
}
TryUpdateModel does not update parameters on a record type
public record Person(string Name)
{
public int Age { get; set; }
}
var person = new Person("initial-name");
TryUpdateModel(person, ...);
In this case, MVC will not attempt to bind Name
again. However, Age
is allowed to be updated
Globalization behavior of model binding route data and query strings
The ASP.NET Core route value provider and query string value provider:
- Treat values as invariant culture.
- Expect that URLs are culture-invariant.
In contrast, values coming from form data undergo a culture-sensitive conversion. This is by design so that URLs are shareable across locales.
To make the ASP.NET Core route value provider and query string value provider undergo a culture-sensitive conversion:
- Inherit from IValueProviderFactory
- Copy the code from QueryStringValueProviderFactory or RouteValueValueProviderFactory
- Replace the culture value passed to the value provider constructor with CultureInfo.CurrentCulture
- Replace the default value provider factory in MVC options with your new one:
public class CultureQueryStringValueProviderFactory : IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
_ = context ?? throw new ArgumentNullException(nameof(context));
var query = context.ActionContext.HttpContext.Request.Query;
if (query?.Count > 0)
{
context.ValueProviders.Add(
new QueryStringValueProvider(
BindingSource.Query,
query,
CultureInfo.CurrentCulture));
}
return Task.CompletedTask;
}
}
builder.Services.AddControllers(options =>
{
var index = options.ValueProviderFactories.IndexOf(
options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>()
.Single());
options.ValueProviderFactories[index] =
new CultureQueryStringValueProviderFactory();
});
Special data types
There are some special data types that model binding can handle.
IFormFile and IFormFileCollection
An uploaded file included in the HTTP request. Also supported is IEnumerable<IFormFile>
for multiple files.
CancellationToken
Actions can optionally bind a CancellationToken
as a parameter. This binds RequestAborted that signals when the connection underlying the HTTP request is aborted. Actions can use this parameter to cancel long running async operations that are executed as part of the controller actions.
FormCollection
Used to retrieve all the values from posted form data.
Input formatters
Data in the request body can be in JSON, XML, or some other format. To parse this data, model binding uses an input formatter that is configured to handle a particular content type. By default, ASP.NET Core includes JSON based input formatters for handling JSON data. You can add other formatters for other content types.
ASP.NET Core selects input formatters based on the Consumes attribute. If no attribute is present, it uses the Content-Type header.
To use the built-in XML input formatters:
In
Program.cs
, call AddXmlSerializerFormatters or AddXmlDataContractSerializerFormatters.builder.Services.AddControllers() .AddXmlSerializerFormatters();
Apply the
Consumes
attribute to controller classes or action methods that should expect XML in the request body.[HttpPost] [Consumes("application/xml")] public ActionResult<Pet> Create(Pet pet)
For more information, see Introducing XML Serialization.
Customize model binding with input formatters
An input formatter takes full responsibility for reading data from the request body. To customize this process, configure the APIs used by the input formatter. This section describes how to customize the System.Text.Json
-based input formatter to understand a custom type named ObjectId
.
Consider the following model, which contains a custom ObjectId
property:
public class InstructorObjectId
{
[Required]
public ObjectId ObjectId { get; set; } = null!;
}
To customize the model binding process when using System.Text.Json
, create a class derived from JsonConverter<T>:
internal class ObjectIdConverter : JsonConverter<ObjectId>
{
public override ObjectId Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> new(JsonSerializer.Deserialize<int>(ref reader, options));
public override void Write(
Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
=> writer.WriteNumberValue(value.Id);
}
To use a custom converter, apply the JsonConverterAttribute attribute to the type. In the following example, the ObjectId
type is configured with ObjectIdConverter
as its custom converter:
[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);
For more information, see How to write custom converters.
Exclude specified types from model binding
The model binding and validation systems' behavior is driven by ModelMetadata. You can customize ModelMetadata
by adding a details provider to MvcOptions.ModelMetadataDetailsProviders. Built-in details providers are available for disabling model binding or validation for specified types.
To disable model binding on all models of a specified type, add an ExcludeBindingMetadataProvider in Program.cs
. For example, to disable model binding on all models of type System.Version
:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
To disable validation on properties of a specified type, add a SuppressChildValidationMetadataProvider in Program.cs
. For example, to disable validation on properties of type System.Guid
:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
Custom model binders
You can extend model binding by writing a custom model binder and using the [ModelBinder]
attribute to select it for a given target. Learn more about custom model binding.
Manual model binding
Model binding can be invoked manually by using the TryUpdateModelAsync method. The method is defined on both ControllerBase
and PageModel
classes. Method overloads let you specify the prefix and value provider to use. The method returns false
if model binding fails. Here's an example:
if (await TryUpdateModelAsync(
newInstructor,
"Instructor",
x => x.Name, x => x.HireDate!))
{
_instructorStore.Add(newInstructor);
return RedirectToPage("./Index");
}
return Page();
TryUpdateModelAsync uses value providers to get data from the form body, query string, and route data. TryUpdateModelAsync
is typically:
- Used with Razor Pages and MVC apps using controllers and views to prevent over-posting.
- Not used with a web API unless consumed from form data, query strings, and route data. Web API endpoints that consume JSON use Input formatters to deserialize the request body into an object.
For more information, see TryUpdateModelAsync.
[FromServices] attribute
This attribute's name follows the pattern of model binding attributes that specify a data source. But it's not about binding data from a value provider. It gets an instance of a type from the dependency injection container. Its purpose is to provide an alternative to constructor injection for when you need a service only if a particular method is called.
If an instance of the type isn't registered in the dependency injection container, the app throws an exception when attempting to bind the parameter. To make the parameter optional, use one of the following approaches:
- Make the parameter nullable.
- Set a default value for the parameter.
For nullable parameters, ensure that the parameter isn't null
before accessing it.
Additional resources
This article explains what model binding is, how it works, and how to customize its behavior.
What is Model binding
Controllers and Razor pages work with data that comes from HTTP requests. For example, route data may provide a record key, and posted form fields may provide values for the properties of the model. Writing code to retrieve each of these values and convert them from strings to .NET types would be tedious and error-prone. Model binding automates this process. The model binding system:
- Retrieves data from various sources such as route data, form fields, and query strings.
- Provides the data to controllers and Razor pages in method parameters and public properties.
- Converts string data to .NET types.
- Updates properties of complex types.
Example
Suppose you have the following action method:
[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)
And the app receives a request with this URL:
https://contoso.com/api/pets/2?DogsOnly=true
Model binding goes through the following steps after the routing system selects the action method:
- Finds the first parameter of
GetById
, an integer namedid
. - Looks through the available sources in the HTTP request and finds
id
= "2" in route data. - Converts the string "2" into integer 2.
- Finds the next parameter of
GetById
, a boolean nameddogsOnly
. - Looks through the sources and finds "DogsOnly=true" in the query string. Name matching is not case-sensitive.
- Converts the string "true" into boolean
true
.
The framework then calls the GetById
method, passing in 2 for the id
parameter, and true
for the dogsOnly
parameter.
In the preceding example, the model binding targets are method parameters that are simple types. Targets may also be the properties of a complex type. After each property is successfully bound, model validation occurs for that property. The record of what data is bound to the model, and any binding or validation errors, is stored in ControllerBase.ModelState or PageModel.ModelState. To find out if this process was successful, the app checks the ModelState.IsValid flag.
Targets
Model binding tries to find values for the following kinds of targets:
- Parameters of the controller action method that a request is routed to.
- Parameters of the Razor Pages handler method that a request is routed to.
- Public properties of a controller or
PageModel
class, if specified by attributes.
[BindProperty] attribute
Can be applied to a public property of a controller or PageModel
class to cause model binding to target that property:
public class EditModel : PageModel
{
[BindProperty]
public Instructor? Instructor { get; set; }
// ...
}
[BindProperties] attribute
Can be applied to a controller or PageModel
class to tell model binding to target all public properties of the class:
[BindProperties]
public class CreateModel : PageModel
{
public Instructor? Instructor { get; set; }
// ...
}
Model binding for HTTP GET requests
By default, properties are not bound for HTTP GET requests. Typically, all you need for a GET request is a record ID parameter. The record ID is used to look up the item in the database. Therefore, there is no need to bind a property that holds an instance of the model. In scenarios where you do want properties bound to data from GET requests, set the SupportsGet
property to true
:
[BindProperty(Name = "ai_user", SupportsGet = true)]
public string? ApplicationInsightsCookie { get; set; }
Model binding simple and complex types
Model binding uses specific definitions for the types it operates on. A simple type is converted from a single string using TypeConverter or a TryParse
method. A complex type is converted from multiple input values. The framework determines the difference based on the existence of a TypeConverter
or TryParse
. We recommend creating a type converter or using TryParse
for a string
to SomeType
conversion that doesn't require external resources or multiple inputs.
Sources
By default, model binding gets data in the form of key-value pairs from the following sources in an HTTP request:
- Form fields
- The request body (For controllers that have the [ApiController] attribute.)
- Route data
- Query string parameters
- Uploaded files
For each target parameter or property, the sources are scanned in the order indicated in the preceding list. There are a few exceptions:
- Route data and query string values are used only for simple types.
- Uploaded files are bound only to target types that implement
IFormFile
orIEnumerable<IFormFile>
.
If the default source is not correct, use one of the following attributes to specify the source:
[FromQuery]
- Gets values from the query string.[FromRoute]
- Gets values from route data.[FromForm]
- Gets values from posted form fields.[FromBody]
- Gets values from the request body.[FromHeader]
- Gets values from HTTP headers.
These attributes:
Are added to model properties individually and not to the model class, as in the following example:
public class Instructor { public int Id { get; set; } [FromQuery(Name = "Note")] public string? NoteFromQueryString { get; set; } // ... }
Optionally accept a model name value in the constructor. This option is provided in case the property name doesn't match the value in the request. For instance, the value in the request might be a header with a hyphen in its name, as in the following example:
public void OnGet([FromHeader(Name = "Accept-Language")] string language)
[FromBody] attribute
Apply the [FromBody]
attribute to a parameter to populate its properties from the body of an HTTP request. The ASP.NET Core runtime delegates the responsibility of reading the body to an input formatter. Input formatters are explained later in this article.
When [FromBody]
is applied to a complex type parameter, any binding source attributes applied to its properties are ignored. For example, the following Create
action specifies that its pet
parameter is populated from the body:
public ActionResult<Pet> Create([FromBody] Pet pet)
The Pet
class specifies that its Breed
property is populated from a query string parameter:
public class Pet
{
public string Name { get; set; } = null!;
[FromQuery] // Attribute is ignored.
public string Breed { get; set; } = null!;
}
In the preceding example:
- The
[FromQuery]
attribute is ignored. - The
Breed
property is not populated from a query string parameter.
Input formatters read only the body and don't understand binding source attributes. If a suitable value is found in the body, that value is used to populate the Breed
property.
Don't apply [FromBody]
to more than one parameter per action method. Once the request stream is read by an input formatter, it's no longer available to be read again for binding other [FromBody]
parameters.
Additional sources
Source data is provided to the model binding system by value providers. You can write and register custom value providers that get data for model binding from other sources. For example, you might want data from cookies or session state. To get data from a new source:
- Create a class that implements
IValueProvider
. - Create a class that implements
IValueProviderFactory
. - Register the factory class in
Program.cs
.
The sample includes a value provider and factory example that gets values from cookies. Register custom value provider factories in Program.cs
:
builder.Services.AddControllers(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});
The preceding code puts the custom value provider after all built-in value providers. To make it the first in the list, call Insert(0, new CookieValueProviderFactory())
instead of Add
.
No source for a model property
By default, a model state error isn't created if no value is found for a model property. The property is set to null or a default value:
- Nullable simple types are set to
null
. - Non-nullable value types are set to
default(T)
. For example, a parameterint id
is set to 0. - For complex Types, model binding creates an instance by using the default constructor, without setting properties.
- Arrays are set to
Array.Empty<T>()
, except thatbyte[]
arrays are set tonull
.
If model state should be invalidated when nothing is found in form fields for a model property, use the [BindRequired]
attribute.
Note that this [BindRequired]
behavior applies to model binding from posted form data, not to JSON or XML data in a request body. Request body data is handled by input formatters.
Type conversion errors
If a source is found but can't be converted into the target type, model state is flagged as invalid. The target parameter or property is set to null or a default value, as noted in the previous section.
In an API controller that has the [ApiController]
attribute, invalid model state results in an automatic HTTP 400 response.
In a Razor page, redisplay the page with an error message:
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
// ...
return RedirectToPage("./Index");
}
When the page is redisplayed by the preceding code, the invalid input isn't shown in the form field. This is because the model property has been set to null or a default value. The invalid input does appear in an error message. If you want to redisplay the bad data in the form field, consider making the model property a string and doing the data conversion manually.
The same strategy is recommended if you don't want type conversion errors to result in model state errors. In that case, make the model property a string.
Simple types
See Model binding simple and complex types for explanation of simple and complex types.
The simple types that the model binder can convert source strings into include the following:
- Boolean
- Byte, SByte
- Char
- DateOnly
- DateTime
- DateTimeOffset
- Decimal
- Double
- Enum
- Guid
- Int16, Int32, Int64
- Single
- TimeOnly
- TimeSpan
- UInt16, UInt32, UInt64
- Uri
- Version
Bind with IParsable<T>.TryParse
The IParsable<TSelf>.TryParse
API supports binding controller action parameter values:
public static bool TryParse (string? s, IFormatProvider? provider, out TSelf result);
The following DateRange
class implements IParsable<TSelf>
to support binding a date range:
public class DateRange : IParsable<DateRange>
{
public DateOnly? From { get; init; }
public DateOnly? To { get; init; }
public static DateRange Parse(string value, IFormatProvider? provider)
{
if (!TryParse(value, provider, out var result))
{
throw new ArgumentException("Could not parse supplied value.", nameof(value));
}
return result;
}
public static bool TryParse(string? value,
IFormatProvider? provider, out DateRange dateRange)
{
var segments = value?.Split(',', StringSplitOptions.RemoveEmptyEntries
| StringSplitOptions.TrimEntries);
if (segments?.Length == 2
&& DateOnly.TryParse(segments[0], provider, out var fromDate)
&& DateOnly.TryParse(segments[1], provider, out var toDate))
{
dateRange = new DateRange { From = fromDate, To = toDate };
return true;
}
dateRange = new DateRange { From = default, To = default };
return false;
}
}
The preceding code:
- Converts a string representing two dates to a
DateRange
object - The model binder uses the
IParsable<TSelf>.TryParse
method to bind theDateRange
.
The following controller action uses the DateRange
class to bind a date range:
// GET /WeatherForecast/ByRange?range=7/24/2022,07/26/2022
public IActionResult ByRange([FromQuery] DateRange range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
&& DateOnly.FromDateTime(wf.Date) <= range.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d"),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
The following Locale
class implements IParsable<TSelf>
to support binding to CultureInfo
:
public class Locale : CultureInfo, IParsable<Locale>
{
public Locale(string culture) : base(culture)
{
}
public static Locale Parse(string value, IFormatProvider? provider)
{
if (!TryParse(value, provider, out var result))
{
throw new ArgumentException("Could not parse supplied value.", nameof(value));
}
return result;
}
public static bool TryParse([NotNullWhen(true)] string? value,
IFormatProvider? provider, out Locale locale)
{
if (value is null)
{
locale = new Locale(CurrentCulture.Name);
return false;
}
try
{
locale = new Locale(value);
return true;
}
catch (CultureNotFoundException)
{
locale = new Locale(CurrentCulture.Name);
return false;
}
}
}
The following controller action uses the Locale
class to bind a CultureInfo
string:
// GET /en-GB/WeatherForecast
public IActionResult Index([FromRoute] Locale locale)
{
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d", locale),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View(weatherForecasts);
}
The following controller action uses the DateRange
and Locale
classes to bind a date range with CultureInfo
:
// GET /af-ZA/WeatherForecast/RangeByLocale?range=2022-07-24,2022-07-29
public IActionResult RangeByLocale([FromRoute] Locale locale, [FromQuery] string range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
if (!DateRange.TryParse(range, locale, out DateRange rangeResult))
{
ModelState.TryAddModelError(nameof(range),
$"Invalid date range: {range} for locale {locale.DisplayName}");
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
}
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= rangeResult.From
&& DateOnly.FromDateTime(wf.Date) <= rangeResult.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d", locale),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int) (wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
The API sample app on GitHub shows the preceding sample for an API controller.
Bind with TryParse
The TryParse
API supports binding controller action parameter values:
public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);
IParsable<T>.TryParse
is the recommended approach for parameter binding because unlike TryParse
, it doesn't depend on reflection.
The following DateRangeTP
class implements TryParse
:
public class DateRangeTP
{
public DateOnly? From { get; }
public DateOnly? To { get; }
public DateRangeTP(string from, string to)
{
if (string.IsNullOrEmpty(from))
throw new ArgumentNullException(nameof(from));
if (string.IsNullOrEmpty(to))
throw new ArgumentNullException(nameof(to));
From = DateOnly.Parse(from);
To = DateOnly.Parse(to);
}
public static bool TryParse(string? value, out DateRangeTP? result)
{
var range = value?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (range?.Length != 2)
{
result = default;
return false;
}
result = new DateRangeTP(range[0], range[1]);
return true;
}
}
The following controller action uses the DateRangeTP
class to bind a date range:
// GET /WeatherForecast/ByRangeTP?range=7/24/2022,07/26/2022
public IActionResult ByRangeTP([FromQuery] DateRangeTP range)
{
if (!ModelState.IsValid)
return View("Error", ModelState.Values.SelectMany(v => v.Errors));
var weatherForecasts = Enumerable
.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
&& DateOnly.FromDateTime(wf.Date) <= range.To)
.Select(wf => new WeatherForecastViewModel
{
Date = wf.Date.ToString("d"),
TemperatureC = wf.TemperatureC,
TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
Summary = wf.Summary
});
return View("Index", weatherForecasts);
}
Complex types
A complex type must have a public default constructor and public writable properties to bind. When model binding occurs, the class is instantiated using the public default constructor.
For each property of the complex type, model binding looks through the sources for the name pattern prefix.property_name. If nothing is found, it looks for just property_name without the prefix. The decision to use the prefix isn't made per property. For example, with a query containing ?Instructor.Id=100&Name=foo
, bound to method OnGet(Instructor instructor)
, the resulting object of type Instructor
contains:
Id
set to100
.Name
set tonull
. Model binding expectsInstructor.Name
becauseInstructor.Id
was used in the preceding query parameter.
For binding to a parameter, the prefix is the parameter name. For binding to a PageModel
public property, the prefix is the public property name. Some attributes have a Prefix
property that lets you override the default usage of parameter or property name.
For example, suppose the complex type is the following Instructor
class:
public class Instructor
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
Prefix = parameter name
If the model to be bound is a parameter named instructorToUpdate
:
public IActionResult OnPost(int? id, Instructor instructorToUpdate)
Model binding starts by looking through the sources for the key instructorToUpdate.ID
. If that isn't found, it looks for ID
without a prefix.
Prefix = property name
If the model to be bound is a property named Instructor
of the controller or PageModel
class:
[BindProperty]
public Instructor Instructor { get; set; }
Model binding starts by looking through the sources for the key Instructor.ID
. If that isn't found, it looks for ID
without a prefix.
Custom prefix
If the model to be bound is a parameter named instructorToUpdate
and a Bind
attribute specifies Instructor
as the prefix:
public IActionResult OnPost(
int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)
Model binding starts by looking through the sources for the key Instructor.ID
. If that isn't found, it looks for ID
without a prefix.
Attributes for complex type targets
Several built-in attributes are available for controlling model binding of complex types:
Warning
These attributes affect model binding when posted form data is the source of values. They do not affect input formatters, which process posted JSON and XML request bodies. Input formatters are explained later in this article.
[Bind] attribute
Can be applied to a class or a method parameter. Specifies which properties of a model should be included in model binding. [Bind]
does not affect input formatters.
In the following example, only the specified properties of the Instructor
model are bound when any handler or action method is called:
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
In the following example, only the specified properties of the Instructor
model are bound when the OnPost
method is called:
[HttpPost]
public IActionResult OnPost(
[Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
The [Bind]
attribute can be used to protect against overposting in create scenarios. It doesn't work well in edit scenarios because excluded properties are set to null or a default value instead of being left unchanged. For protection against overposting, view models are recommended rather than the [Bind]
attribute. For more information, see Security note about overposting.
[ModelBinder] attribute
ModelBinderAttribute can be applied to types, properties, or parameters. It allows specifying the type of model binder used to bind the specific instance or type. For example:
[HttpPost]
public IActionResult OnPost(
[ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)
The [ModelBinder]
attribute can also be used to change the name of a property or parameter when it's being model bound:
public class Instructor
{
[ModelBinder(Name = "instructor_id")]
public string Id { get; set; }
// ...
}
[BindRequired] attribute
Causes model binding to add a model state error if binding cannot occur for a model's property. Here's an example:
public class InstructorBindRequired
{
// ...
[BindRequired]
public DateTime HireDate { get; set; }
}
See also the discussion of the [Required]
attribute in Model validation.
[BindNever] attribute
Can be applied to a property or a type. Prevents model binding from setting a model's property. When applied to a type, the model binding system excludes all properties the type defines. Here's an example:
public class InstructorBindNever
{
[BindNever]
public int Id { get; set; }
// ...
}
Collections
For targets that are collections of simple types, model binding looks for matches to parameter_name or property_name. If no match is found, it looks for one of the supported formats without the prefix. For example:
Suppose the parameter to be bound is an array named
selectedCourses
:public IActionResult OnPost(int? id, int[] selectedCourses)
Form or query string data can be in one of the following formats:
selectedCourses=1050&selectedCourses=2000
selectedCourses[0]=1050&selectedCourses[1]=2000
[0]=1050&[1]=2000
selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
[a]=1050&[b]=2000&index=a&index=b
Avoid binding a parameter or a property named
index
orIndex
if it is adjacent to a collection value. Model binding attempts to useindex
as the index for the collection which might result in incorrect binding. For example, consider the following action:public IActionResult Post(string index, List<Product> products)
In the preceding code, the
index
query string parameter binds to theindex
method parameter and also is used to bind the product collection. Renaming theindex
parameter or using a model binding attribute to configure binding avoids this issue:public IActionResult Post(string productIndex, List<Product> products)
The following format is supported only in form data:
selectedCourses[]=1050&selectedCourses[]=2000
For all of the preceding example formats, model binding passes an array of two items to the
selectedCourses
parameter:- selectedCourses[0]=1050
- selectedCourses[1]=2000
Data formats that use subscript numbers (... [0] ... [1] ...) must ensure that they are numbered sequentially starting at zero. If there are any gaps in subscript numbering, all items after the gap are ignored. For example, if the subscripts are 0 and 2 instead of 0 and 1, the second item is ignored.
Dictionaries
For Dictionary
targets, model binding looks for matches to parameter_name or property_name. If no match is found, it looks for one of the supported formats without the prefix. For example:
Suppose the target parameter is a
Dictionary<int, string>
namedselectedCourses
:public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
The posted form or query string data can look like one of the following examples:
selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
[1050]=Chemistry&selectedCourses[2000]=Economics
selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry& selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
[0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
For all of the preceding example formats, model binding passes a dictionary of two items to the
selectedCourses
parameter:- selectedCourses["1050"]="Chemistry"
- selectedCourses["2000"]="Economics"
Constructor binding and record types
Model binding requires that complex types have a parameterless constructor. Both System.Text.Json
and Newtonsoft.Json
based input formatters support deserialization of classes that don't have a parameterless constructor.
Record types are a great way to succinctly represent data over the network. ASP.NET Core supports model binding and validating record types with a single constructor:
public record Person(
[Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);
public class PersonController
{
public IActionResult Index() => View();
[HttpPost]
public IActionResult Index(Person person)
{
// ...
}
}
Person/Index.cshtml
:
@model Person
<label>Name: <input asp-for="Name" /></label>
<br />
<label>Age: <input asp-for="Age" /></label>
When validating record types, the runtime searches for binding and validation metadata specifically on parameters rather than on properties.
The framework allows binding to and validating record types:
public record Person([Required] string Name, [Range(0, 100)] int Age);
For the preceding to work, the type must:
- Be a record type.
- Have exactly one public constructor.
- Contain parameters that have a property with the same name and type. The names must not differ by case.
POCOs without parameterless constructors
POCOs that do not have parameterless constructors can't be bound.
The following code results in an exception saying that the type must have a parameterless constructor:
public class Person(string Name)
public record Person([Required] string Name, [Range(0, 100)] int Age)
{
public Person(string Name) : this (Name, 0);
}
Record types with manually authored constructors
Record types with manually authored constructors that look like primary constructors work
public record Person
{
public Person([Required] string Name, [Range(0, 100)] int Age)
=> (this.Name, this.Age) = (Name, Age);
public string Name { get; set; }
public int Age { get; set; }
}
Record types, validation and binding metadata
For record types, validation and binding metadata on parameters is used. Any metadata on properties is ignored
public record Person (string Name, int Age)
{
[BindProperty(Name = "SomeName")] // This does not get used
[Required] // This does not get used
public string Name { get; init; }
}
Validation and metadata
Validation uses metadata on the parameter but uses the property to read the value. In the ordinary case with primary constructors, the two would be identical. However, there are ways to defeat it:
public record Person([Required] string Name)
{
private readonly string _name;
// The following property is never null.
// However this object could have been constructed as "new Person(null)".
public string Name { get; init => _name = value ?? string.Empty; }
}
TryUpdateModel does not update parameters on a record type
public record Person(string Name)
{
public int Age { get; set; }
}
var person = new Person("initial-name");
TryUpdateModel(person, ...);
In this case, MVC will not attempt to bind Name
again. However, Age
is allowed to be updated
Globalization behavior of model binding route data and query strings
The ASP.NET Core route value provider and query string value provider:
- Treat values as invariant culture.
- Expect that URLs are culture-invariant.
In contrast, values coming from form data undergo a culture-sensitive conversion. This is by design so that URLs are shareable across locales.
To make the ASP.NET Core route value provider and query string value provider undergo a culture-sensitive conversion:
- Inherit from IValueProviderFactory
- Copy the code from QueryStringValueProviderFactory or RouteValueValueProviderFactory
- Replace the culture value passed to the value provider constructor with CultureInfo.CurrentCulture
- Replace the default value provider factory in MVC options with your new one:
public class CultureQueryStringValueProviderFactory : IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
_ = context ?? throw new ArgumentNullException(nameof(context));
var query = context.ActionContext.HttpContext.Request.Query;
if (query?.Count > 0)
{
context.ValueProviders.Add(
new QueryStringValueProvider(
BindingSource.Query,
query,
CultureInfo.CurrentCulture));
}
return Task.CompletedTask;
}
}
builder.Services.AddControllers(options =>
{
var index = options.ValueProviderFactories.IndexOf(
options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>()
.Single());
options.ValueProviderFactories[index] =
new CultureQueryStringValueProviderFactory();
});
Special data types
There are some special data types that model binding can handle.
IFormFile and IFormFileCollection
An uploaded file included in the HTTP request. Also supported is IEnumerable<IFormFile>
for multiple files.
CancellationToken
Actions can optionally bind a CancellationToken
as a parameter. This binds RequestAborted that signals when the connection underlying the HTTP request is aborted. Actions can use this parameter to cancel long running async operations that are executed as part of the controller actions.
FormCollection
Used to retrieve all the values from posted form data.
Input formatters
Data in the request body can be in JSON, XML, or some other format. To parse this data, model binding uses an input formatter that is configured to handle a particular content type. By default, ASP.NET Core includes JSON based input formatters for handling JSON data. You can add other formatters for other content types.
ASP.NET Core selects input formatters based on the Consumes attribute. If no attribute is present, it uses the Content-Type header.
To use the built-in XML input formatters:
In
Program.cs
, call AddXmlSerializerFormatters or AddXmlDataContractSerializerFormatters.builder.Services.AddControllers() .AddXmlSerializerFormatters();
Apply the
Consumes
attribute to controller classes or action methods that should expect XML in the request body.[HttpPost] [Consumes("application/xml")] public ActionResult<Pet> Create(Pet pet)
For more information, see Introducing XML Serialization.
Customize model binding with input formatters
An input formatter takes full responsibility for reading data from the request body. To customize this process, configure the APIs used by the input formatter. This section describes how to customize the System.Text.Json
-based input formatter to understand a custom type named ObjectId
.
Consider the following model, which contains a custom ObjectId
property:
public class InstructorObjectId
{
[Required]
public ObjectId ObjectId { get; set; } = null!;
}
To customize the model binding process when using System.Text.Json
, create a class derived from JsonConverter<T>:
internal class ObjectIdConverter : JsonConverter<ObjectId>
{
public override ObjectId Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> new(JsonSerializer.Deserialize<int>(ref reader, options));
public override void Write(
Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
=> writer.WriteNumberValue(value.Id);
}
To use a custom converter, apply the JsonConverterAttribute attribute to the type. In the following example, the ObjectId
type is configured with ObjectIdConverter
as its custom converter:
[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);
For more information, see How to write custom converters.
Exclude specified types from model binding
The model binding and validation systems' behavior is driven by ModelMetadata. You can customize ModelMetadata
by adding a details provider to MvcOptions.ModelMetadataDetailsProviders. Built-in details providers are available for disabling model binding or validation for specified types.
To disable model binding on all models of a specified type, add an ExcludeBindingMetadataProvider in Program.cs
. For example, to disable model binding on all models of type System.Version
:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
To disable validation on properties of a specified type, add a SuppressChildValidationMetadataProvider in Program.cs
. For example, to disable validation on properties of type System.Guid
:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
Custom model binders
You can extend model binding by writing a custom model binder and using the [ModelBinder]
attribute to select it for a given target. Learn more about custom model binding.
Manual model binding
Model binding can be invoked manually by using the TryUpdateModelAsync method. The method is defined on both ControllerBase
and PageModel
classes. Method overloads let you specify the prefix and value provider to use. The method returns false
if model binding fails. Here's an example:
if (await TryUpdateModelAsync(
newInstructor,
"Instructor",
x => x.Name, x => x.HireDate!))
{
_instructorStore.Add(newInstructor);
return RedirectToPage("./Index");
}
return Page();
TryUpdateModelAsync uses value providers to get data from the form body, query string, and route data. TryUpdateModelAsync
is typically:
- Used with Razor Pages and MVC apps using controllers and views to prevent over-posting.
- Not used with a web API unless consumed from form data, query strings, and route data. Web API endpoints that consume JSON use Input formatters to deserialize the request body into an object.
For more information, see TryUpdateModelAsync.
[FromServices] attribute
This attribute's name follows the pattern of model binding attributes that specify a data source. But it's not about binding data from a value provider. It gets an instance of a type from the dependency injection container. Its purpose is to provide an alternative to constructor injection for when you need a service only if a particular method is called.
If an instance of the type isn't registered in the dependency injection container, the app throws an exception when attempting to bind the parameter. To make the parameter optional, use one of the following approaches:
- Make the parameter nullable.
- Set a default value for the parameter.
For nullable parameters, ensure that the parameter isn't null
before accessing it.
Additional resources
This article explains what model binding is, how it works, and how to customize its behavior.
What is Model binding
Controllers and Razor pages work with data that comes from HTTP requests. For example, route data may provide a record key, and posted form fields may provide values for the properties of the model. Writing code to retrieve each of these values and convert them from strings to .NET types would be tedious and error-prone. Model binding automates this process. The model binding system:
- Retrieves data from various sources such as route data, form fields, and query strings.
- Provides the data to controllers and Razor pages in method parameters and public properties.
- Converts string data to .NET types.
- Updates properties of complex types.
Example
Suppose you have the following action method:
[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)
And the app receives a request with this URL:
https://contoso.com/api/pets/2?DogsOnly=true
Model binding goes through the following steps after the routing system selects the action method:
- Finds the first parameter of
GetById
, an integer namedid
. - Looks through the available sources in the HTTP request and finds
id
= "2" in route data. - Converts the string "2" into integer 2.
- Finds the next parameter of
GetById
, a boolean nameddogsOnly
. - Looks through the sources and finds "DogsOnly=true" in the query string. Name matching is not case-sensitive.
- Converts the string "true" into boolean
true
.
The framework then calls the GetById
method, passing in 2 for the id
parameter, and true
for the dogsOnly
parameter.
In the preceding example, the model binding targets are method parameters that are simple types. Targets may also be the properties of a complex type. After each property is successfully bound, model validation occurs for that property. The record of what data is bound to the model, and any binding or validation errors, is stored in ControllerBase.ModelState or PageModel.ModelState. To find out if this process was successful, the app checks the ModelState.IsValid flag.
Targets
Model binding tries to find values for the following kinds of targets:
- Parameters of the controller action method that a request is routed to.
- Parameters of the Razor Pages handler method that a request is routed to.
- Public properties of a controller or
PageModel
class, if specified by attributes.
[BindProperty] attribute
Can be applied to a public property of a controller or PageModel
class to cause model binding to target that property:
public class EditModel : PageModel
{
[BindProperty]
public Instructor? Instructor { get; set; }
// ...
}
[BindProperties] attribute
Can be applied to a controller or PageModel
class to tell model binding to target all public properties of the class:
[BindProperties]
public class CreateModel : PageModel
{
public Instructor? Instructor { get; set; }
// ...
}
Model binding for HTTP GET requests
By default, properties are not bound for HTTP GET requests. Typically, all you need for a GET request is a record ID parameter. The record ID is used to look up the item in the database. Therefore, there is no need to bind a property that holds an instance of the model. In scenarios where you do want properties bound to data from GET requests, set the SupportsGet
property to true
:
[BindProperty(Name = "ai_user", SupportsGet = true)]
public string? ApplicationInsightsCookie { get; set; }
Sources
By default, model binding gets data in the form of key-value pairs from the following sources in an HTTP request:
- Form fields
- The request body (For controllers that have the [ApiController] attribute.)
- Route data
- Query string parameters
- Uploaded files
For each target parameter or property, the sources are scanned in the order indicated in the preceding list. There are a few exceptions:
- Route data and query string values are used only for simple types.
- Uploaded files are bound only to target types that implement
IFormFile
orIEnumerable<IFormFile>
.
If the default source is not correct, use one of the following attributes to specify the source:
[FromQuery]
- Gets values from the query string.[FromRoute]
- Gets values from route data.[FromForm]
- Gets values from posted form fields.[FromBody]
- Gets values from the request body.[FromHeader]
- Gets values from HTTP headers.
These attributes:
Are added to model properties individually and not to the model class, as in the following example:
public class Instructor { public int Id { get; set; } [FromQuery(Name = "Note")] public string? NoteFromQueryString { get; set; } // ... }
Optionally accept a model name value in the constructor. This option is provided in case the property name doesn't match the value in the request. For instance, the value in the request might be a header with a hyphen in its name, as in the following example:
public void OnGet([FromHeader(Name = "Accept-Language")] string language)
[FromBody] attribute
Apply the [FromBody]
attribute to a parameter to populate its properties from the body of an HTTP request. The ASP.NET Core runtime delegates the responsibility of reading the body to an input formatter. Input formatters are explained later in this article.
When [FromBody]
is applied to a complex type parameter, any binding source attributes applied to its properties are ignored. For example, the following Create
action specifies that its pet
parameter is populated from the body:
public ActionResult<Pet> Create([FromBody] Pet pet)
The Pet
class specifies that its Breed
property is populated from a query string parameter:
public class Pet
{
public string Name { get; set; } = null!;
[FromQuery] // Attribute is ignored.
public string Breed { get; set; } = null!;
}
In the preceding example:
- The
[FromQuery]
attribute is ignored. - The
Breed
property is not populated from a query string parameter.
Input formatters read only the body and don't understand binding source attributes. If a suitable value is found in the body, that value is used to populate the Breed
property.
Don't apply [FromBody]
to more than one parameter per action method. Once the request stream is read by an input formatter, it's no longer available to be read again for binding other [FromBody]
parameters.
Additional sources
Source data is provided to the model binding system by value providers. You can write and register custom value providers that get data for model binding from other sources. For example, you might want data from cookies or session state. To get data from a new source:
- Create a class that implements
IValueProvider
. - Create a class that implements
IValueProviderFactory
. - Register the factory class in
Program.cs
.
The sample includes a value provider and factory example that gets values from cookies. Register custom value provider factories in Program.cs
:
builder.Services.AddControllers(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});
The preceding code puts the custom value provider after all built-in value providers. To make it the first in the list, call Insert(0, new CookieValueProviderFactory())
instead of Add
.
No source for a model property
By default, a model state error isn't created if no value is found for a model property. The property is set to null or a default value:
- Nullable simple types are set to
null
. - Non-nullable value types are set to
default(T)
. For example, a parameterint id
is set to 0. - For complex Types, model binding creates an instance by using the default constructor, without setting properties.
- Arrays are set to
Array.Empty<T>()
, except thatbyte[]
arrays are set tonull
.
If model state should be invalidated when nothing is found in form fields for a model property, use the [BindRequired]
attribute.
Note that this [BindRequired]
behavior applies to model binding from posted form data, not to JSON or XML data in a request body. Request body data is handled by input formatters.
Type conversion errors
If a source is found but can't be converted into the target type, model state is flagged as invalid. The target parameter or property is set to null or a default value, as noted in the previous section.
In an API controller that has the [ApiController]
attribute, invalid model state results in an automatic HTTP 400 response.
In a Razor page, redisplay the page with an error message:
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
// ...
return RedirectToPage("./Index");
}
When the page is redisplayed by the preceding code, the invalid input isn't shown in the form field. This is because the model property has been set to null or a default value. The invalid input does appear in an error message. If you want to redisplay the bad data in the form field, consider making the model property a string and doing the data conversion manually.
The same strategy is recommended if you don't want type conversion errors to result in model state errors. In that case, make the model property a string.
Simple types
The simple types that the model binder can convert source strings into include the following:
- Boolean
- Byte, SByte
- Char
- DateTime
- DateTimeOffset
- Decimal
- Double
- Enum
- Guid
- Int16, Int32, Int64
- Single
- TimeSpan
- UInt16, UInt32, UInt64
- Uri
- Version
Complex types
A complex type must have a public default constructor and public writable properties to bind. When model binding occurs, the class is instantiated using the public default constructor.
For each property of the complex type, model binding looks through the sources for the name pattern prefix.property_name. If nothing is found, it looks for just property_name without the prefix. The decision to use the prefix isn't made per property. For example, with a query containing ?Instructor.Id=100&Name=foo
, bound to method OnGet(Instructor instructor)
, the resulting object of type Instructor
contains:
Id
set to100
.Name
set tonull
. Model binding expectsInstructor.Name
becauseInstructor.Id
was used in the preceding query parameter.
For binding to a parameter, the prefix is the parameter name. For binding to a PageModel
public property, the prefix is the public property name. Some attributes have a Prefix
property that lets you override the default usage of parameter or property name.
For example, suppose the complex type is the following Instructor
class:
public class Instructor
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
Prefix = parameter name
If the model to be bound is a parameter named instructorToUpdate
:
public IActionResult OnPost(int? id, Instructor instructorToUpdate)
Model binding starts by looking through the sources for the key instructorToUpdate.ID
. If that isn't found, it looks for ID
without a prefix.
Prefix = property name
If the model to be bound is a property named Instructor
of the controller or PageModel
class:
[BindProperty]
public Instructor Instructor { get; set; }
Model binding starts by looking through the sources for the key Instructor.ID
. If that isn't found, it looks for ID
without a prefix.
Custom prefix
If the model to be bound is a parameter named instructorToUpdate
and a Bind
attribute specifies Instructor
as the prefix:
public IActionResult OnPost(
int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)
Model binding starts by looking through the sources for the key Instructor.ID
. If that isn't found, it looks for ID
without a prefix.
Attributes for complex type targets
Several built-in attributes are available for controlling model binding of complex types:
Warning
These attributes affect model binding when posted form data is the source of values. They do not affect input formatters, which process posted JSON and XML request bodies. Input formatters are explained later in this article.
[Bind] attribute
Can be applied to a class or a method parameter. Specifies which properties of a model should be included in model binding. [Bind]
does not affect input formatters.
In the following example, only the specified properties of the Instructor
model are bound when any handler or action method is called:
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
In the following example, only the specified properties of the Instructor
model are bound when the OnPost
method is called:
[HttpPost]
public IActionResult OnPost(
[Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
The [Bind]
attribute can be used to protect against overposting in create scenarios. It doesn't work well in edit scenarios because excluded properties are set to null or a default value instead of being left unchanged. For protection against overposting, view models are recommended rather than the [Bind]
attribute. For more information, see Security note about overposting.
[ModelBinder] attribute
ModelBinderAttribute can be applied to types, properties, or parameters. It allows specifying the type of model binder used to bind the specific instance or type. For example:
[HttpPost]
public IActionResult OnPost(
[ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)
The [ModelBinder]
attribute can also be used to change the name of a property or parameter when it's being model bound:
public class Instructor
{
[ModelBinder(Name = "instructor_id")]
public string Id { get; set; }
// ...
}
[BindRequired] attribute
Causes model binding to add a model state error if binding cannot occur for a model's property. Here's an example:
public class InstructorBindRequired
{
// ...
[BindRequired]
public DateTime HireDate { get; set; }
}
See also the discussion of the [Required]
attribute in Model validation.
[BindNever] attribute
Can be applied to a property or a type. Prevents model binding from setting a model's property. When applied to a type, the model binding system excludes all properties the type defines. Here's an example:
public class InstructorBindNever
{
[BindNever]
public int Id { get; set; }
// ...
}
Collections
For targets that are collections of simple types, model binding looks for matches to parameter_name or property_name. If no match is found, it looks for one of the supported formats without the prefix. For example:
Suppose the parameter to be bound is an array named
selectedCourses
:public IActionResult OnPost(int? id, int[] selectedCourses)
Form or query string data can be in one of the following formats:
selectedCourses=1050&selectedCourses=2000
selectedCourses[0]=1050&selectedCourses[1]=2000
[0]=1050&[1]=2000
selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
[a]=1050&[b]=2000&index=a&index=b
Avoid binding a parameter or a property named
index
orIndex
if it is adjacent to a collection value. Model binding attempts to useindex
as the index for the collection which might result in incorrect binding. For example, consider the following action:public IActionResult Post(string index, List<Product> products)
In the preceding code, the
index
query string parameter binds to theindex
method parameter and also is used to bind the product collection. Renaming theindex
parameter or using a model binding attribute to configure binding avoids this issue:public IActionResult Post(string productIndex, List<Product> products)
The following format is supported only in form data:
selectedCourses[]=1050&selectedCourses[]=2000
For all of the preceding example formats, model binding passes an array of two items to the
selectedCourses
parameter:- selectedCourses[0]=1050
- selectedCourses[1]=2000
Data formats that use subscript numbers (... [0] ... [1] ...) must ensure that they are numbered sequentially starting at zero. If there are any gaps in subscript numbering, all items after the gap are ignored. For example, if the subscripts are 0 and 2 instead of 0 and 1, the second item is ignored.
Dictionaries
For Dictionary
targets, model binding looks for matches to parameter_name or property_name. If no match is found, it looks for one of the supported formats without the prefix. For example:
Suppose the target parameter is a
Dictionary<int, string>
namedselectedCourses
:public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
The posted form or query string data can look like one of the following examples:
selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
[1050]=Chemistry&selectedCourses[2000]=Economics
selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry& selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
[0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
For all of the preceding example formats, model binding passes a dictionary of two items to the
selectedCourses
parameter:- selectedCourses["1050"]="Chemistry"
- selectedCourses["2000"]="Economics"
Constructor binding and record types
Model binding requires that complex types have a parameterless constructor. Both System.Text.Json
and Newtonsoft.Json
based input formatters support deserialization of classes that don't have a parameterless constructor.
Record types are a great way to succinctly represent data over the network. ASP.NET Core supports model binding and validating record types with a single constructor:
public record Person(
[Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);
public class PersonController
{
public IActionResult Index() => View();
[HttpPost]
public IActionResult Index(Person person)
{
// ...
}
}
Person/Index.cshtml
:
@model Person
<label>Name: <input asp-for="Name" /></label>
<br />
<label>Age: <input asp-for="Age" /></label>
When validating record types, the runtime searches for binding and validation metadata specifically on parameters rather than on properties.
The framework allows binding to and validating record types:
public record Person([Required] string Name, [Range(0, 100)] int Age);
For the preceding to work, the type must:
- Be a record type.
- Have exactly one public constructor.
- Contain parameters that have a property with the same name and type. The names must not differ by case.
POCOs without parameterless constructors
POCOs that do not have parameterless constructors can't be bound.
The following code results in an exception saying that the type must have a parameterless constructor:
public class Person(string Name)
public record Person([Required] string Name, [Range(0, 100)] int Age)
{
public Person(string Name) : this (Name, 0);
}
Record types with manually authored constructors
Record types with manually authored constructors that look like primary constructors work
public record Person
{
public Person([Required] string Name, [Range(0, 100)] int Age)
=> (this.Name, this.Age) = (Name, Age);
public string Name { get; set; }
public int Age { get; set; }
}
Record types, validation and binding metadata
For record types, validation and binding metadata on parameters is used. Any metadata on properties is ignored
public record Person (string Name, int Age)
{
[BindProperty(Name = "SomeName")] // This does not get used
[Required] // This does not get used
public string Name { get; init; }
}
Validation and metadata
Validation uses metadata on the parameter but uses the property to read the value. In the ordinary case with primary constructors, the two would be identical. However, there are ways to defeat it:
public record Person([Required] string Name)
{
private readonly string _name;
// The following property is never null.
// However this object could have been constructed as "new Person(null)".
public string Name { get; init => _name = value ?? string.Empty; }
}
TryUpdateModel does not update parameters on a record type
public record Person(string Name)
{
public int Age { get; set; }
}
var person = new Person("initial-name");
TryUpdateModel(person, ...);
In this case, MVC will not attempt to bind Name
again. However, Age
is allowed to be updated
Globalization behavior of model binding route data and query strings
The ASP.NET Core route value provider and query string value provider:
- Treat values as invariant culture.
- Expect that URLs are culture-invariant.
In contrast, values coming from form data undergo a culture-sensitive conversion. This is by design so that URLs are shareable across locales.
To make the ASP.NET Core route value provider and query string value provider undergo a culture-sensitive conversion:
- Inherit from IValueProviderFactory
- Copy the code from QueryStringValueProviderFactory or RouteValueValueProviderFactory
- Replace the culture value passed to the value provider constructor with CultureInfo.CurrentCulture
- Replace the default value provider factory in MVC options with your new one:
public class CultureQueryStringValueProviderFactory : IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
_ = context ?? throw new ArgumentNullException(nameof(context));
var query = context.ActionContext.HttpContext.Request.Query;
if (query?.Count > 0)
{
context.ValueProviders.Add(
new QueryStringValueProvider(
BindingSource.Query,
query,
CultureInfo.CurrentCulture));
}
return Task.CompletedTask;
}
}
builder.Services.AddControllers(options =>
{
var index = options.ValueProviderFactories.IndexOf(
options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>()
.Single());
options.ValueProviderFactories[index] =
new CultureQueryStringValueProviderFactory();
});
Special data types
There are some special data types that model binding can handle.
IFormFile and IFormFileCollection
An uploaded file included in the HTTP request. Also supported is IEnumerable<IFormFile>
for multiple files.
CancellationToken
Actions can optionally bind a CancellationToken
as a parameter. This binds RequestAborted that signals when the connection underlying the HTTP request is aborted. Actions can use this parameter to cancel long running async operations that are executed as part of the controller actions.
FormCollection
Used to retrieve all the values from posted form data.
Input formatters
Data in the request body can be in JSON, XML, or some other format. To parse this data, model binding uses an input formatter that is configured to handle a particular content type. By default, ASP.NET Core includes JSON based input formatters for handling JSON data. You can add other formatters for other content types.
ASP.NET Core selects input formatters based on the Consumes attribute. If no attribute is present, it uses the Content-Type header.
To use the built-in XML input formatters:
In
Program.cs
, call AddXmlSerializerFormatters or AddXmlDataContractSerializerFormatters.builder.Services.AddControllers() .AddXmlSerializerFormatters();
Apply the
Consumes
attribute to controller classes or action methods that should expect XML in the request body.[HttpPost] [Consumes("application/xml")] public ActionResult<Pet> Create(Pet pet)
For more information, see Introducing XML Serialization.
Customize model binding with input formatters
An input formatter takes full responsibility for reading data from the request body. To customize this process, configure the APIs used by the input formatter. This section describes how to customize the System.Text.Json
-based input formatter to understand a custom type named ObjectId
.
Consider the following model, which contains a custom ObjectId
property:
public class InstructorObjectId
{
[Required]
public ObjectId ObjectId { get; set; } = null!;
}
To customize the model binding process when using System.Text.Json
, create a class derived from JsonConverter<T>:
internal class ObjectIdConverter : JsonConverter<ObjectId>
{
public override ObjectId Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> new(JsonSerializer.Deserialize<int>(ref reader, options));
public override void Write(
Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
=> writer.WriteNumberValue(value.Id);
}
To use a custom converter, apply the JsonConverterAttribute attribute to the type. In the following example, the ObjectId
type is configured with ObjectIdConverter
as its custom converter:
[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);
For more information, see How to write custom converters.
Exclude specified types from model binding
The model binding and validation systems' behavior is driven by ModelMetadata. You can customize ModelMetadata
by adding a details provider to MvcOptions.ModelMetadataDetailsProviders. Built-in details providers are available for disabling model binding or validation for specified types.
To disable model binding on all models of a specified type, add an ExcludeBindingMetadataProvider in Program.cs
. For example, to disable model binding on all models of type System.Version
:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
To disable validation on properties of a specified type, add a SuppressChildValidationMetadataProvider in Program.cs
. For example, to disable validation on properties of type System.Guid
:
builder.Services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(Guid)));
});
Custom model binders
You can extend model binding by writing a custom model binder and using the [ModelBinder]
attribute to select it for a given target. Learn more about custom model binding.
Manual model binding
Model binding can be invoked manually by using the TryUpdateModelAsync method. The method is defined on both ControllerBase
and PageModel
classes. Method overloads let you specify the prefix and value provider to use. The method returns false
if model binding fails. Here's an example:
if (await TryUpdateModelAsync(
newInstructor,
"Instructor",
x => x.Name, x => x.HireDate!))
{
_instructorStore.Add(newInstructor);
return RedirectToPage("./Index");
}
return Page();
TryUpdateModelAsync uses value providers to get data from the form body, query string, and route data. TryUpdateModelAsync
is typically:
- Used with Razor Pages and MVC apps using controllers and views to prevent over-posting.
- Not used with a web API unless consumed from form data, query strings, and route data. Web API endpoints that consume JSON use Input formatters to deserialize the request body into an object.
For more information, see TryUpdateModelAsync.
[FromServices] attribute
This attribute's name follows the pattern of model binding attributes that specify a data source. But it's not about binding data from a value provider. It gets an instance of a type from the dependency injection container. Its purpose is to provide an alternative to constructor injection for when you need a service only if a particular method is called.
If an instance of the type isn't registered in the dependency injection container, the app throws an exception when attempting to bind the parameter. To make the parameter optional, use one of the following approaches:
- Make the parameter nullable.
- Set a default value for the parameter.
For nullable parameters, ensure that the parameter isn't null
before accessing it.
Additional resources
This article explains what model binding is, how it works, and how to customize its behavior.
View or download sample code (how to download).
What is Model binding
Controllers and Razor pages work with data that comes from HTTP requests. For example, route data may provide a record key, and posted form fields may provide values for the properties of the model. Writing code to retrieve each of these values and convert them from strings to .NET types would be tedious and error-prone. Model binding automates this process. The model binding system:
- Retrieves data from various sources such as route data, form fields, and query strings.
- Provides the data to controllers and Razor pages in method parameters and public properties.
- Converts string data to .NET types.
- Updates properties of complex types.
Example
Suppose you have the following action method:
[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)
And the app receives a request with this URL:
http://contoso.com/api/pets/2?DogsOnly=true
Model binding goes through the following steps after the routing system selects the action method:
- Finds the first parameter of
GetById
, an integer namedid
. - Looks through the available sources in the HTTP request and finds
id
= "2" in route data. - Converts the string "2" into integer 2.
- Finds the next parameter of
GetById
, a boolean nameddogsOnly
. - Looks through the sources and finds "DogsOnly=true" in the query string. Name matching is not case-sensitive.
- Converts the string "true" into boolean
true
.
The framework then calls the GetById
method, passing in 2 for the id
parameter, and true
for the dogsOnly
parameter.
In the preceding example, the model binding targets are method parameters that are simple types. Targets may also be the properties of a complex type. After each property is successfully bound, model validation occurs for that property. The record of what data is bound to the model, and any binding or validation errors, is stored in ControllerBase.ModelState or PageModel.ModelState. To find out if this process was successful, the app checks the ModelState.IsValid flag.
Targets
Model binding tries to find values for the following kinds of targets:
- Parameters of the controller action method that a request is routed to.
- Parameters of the Razor Pages handler method that a request is routed to.
- Public properties of a controller or
PageModel
class, if specified by attributes.
[BindProperty] attribute
Can be applied to a public property of a controller or PageModel
class to cause model binding to target that property:
public class EditModel : InstructorsPageModel
{
[BindProperty]
public Instructor Instructor { get; set; }
[BindProperties] attribute
Available in ASP.NET Core 2.1 and later. Can be applied to a controller or PageModel
class to tell model binding to target all public properties of the class:
[BindProperties(SupportsGet = true)]
public class CreateModel : InstructorsPageModel
{
public Instructor Instructor { get; set; }
Model binding for HTTP GET requests
By default, properties are not bound for HTTP GET requests. Typically, all you need for a GET request is a record ID parameter. The record ID is used to look up the item in the database. Therefore, there is no need to bind a property that holds an instance of the model. In scenarios where you do want properties bound to data from GET requests, set the SupportsGet
property to true
:
[BindProperty(Name = "ai_user", SupportsGet = true)]
public string ApplicationInsightsCookie { get; set; }
Sources
By default, model binding gets data in the form of key-value pairs from the following sources in an HTTP request:
- Form fields
- The request body (For controllers that have the [ApiController] attribute.)
- Route data
- Query string parameters
- Uploaded files
For each target parameter or property, the sources are scanned in the order indicated in the preceding list. There are a few exceptions:
- Route data and query string values are used only for simple types.
- Uploaded files are bound only to target types that implement
IFormFile
orIEnumerable<IFormFile>
.
If the default source is not correct, use one of the following attributes to specify the source:
[FromQuery]
- Gets values from the query string.[FromRoute]
- Gets values from route data.[FromForm]
- Gets values from posted form fields.[FromBody]
- Gets values from the request body.[FromHeader]
- Gets values from HTTP headers.
These attributes:
Are added to model properties individually (not to the model class), as in the following example:
public class Instructor { public int ID { get; set; } [FromQuery(Name = "Note")] public string NoteFromQueryString { get; set; }
Optionally accept a model name value in the constructor. This option is provided in case the property name doesn't match the value in the request. For instance, the value in the request might be a header with a hyphen in its name, as in the following example:
public void OnGet([FromHeader(Name = "Accept-Language")] string language)
[FromBody] attribute
Apply the [FromBody]
attribute to a parameter to populate its properties from the body of an HTTP request. The ASP.NET Core runtime delegates the responsibility of reading the body to an input formatter. Input formatters are explained later in this article.
When [FromBody]
is applied to a complex type parameter, any binding source attributes applied to its properties are ignored. For example, the following Create
action specifies that its pet
parameter is populated from the body:
public ActionResult<Pet> Create([FromBody] Pet pet)
The Pet
class specifies that its Breed
property is populated from a query string parameter:
public class Pet
{
public string Name { get; set; }
[FromQuery] // Attribute is ignored.
public string Breed { get; set; }
}
In the preceding example:
- The
[FromQuery]
attribute is ignored. - The
Breed
property is not populated from a query string parameter.
Input formatters read only the body and don't understand binding source attributes. If a suitable value is found in the body, that value is used to populate the Breed
property.
Don't apply [FromBody]
to more than one parameter per action method. Once the request stream is read by an input formatter, it's no longer available to be read again for binding other [FromBody]
parameters.
Additional sources
Source data is provided to the model binding system by value providers. You can write and register custom value providers that get data for model binding from other sources. For example, you might want data from cookies or session state. To get data from a new source:
- Create a class that implements
IValueProvider
. - Create a class that implements
IValueProviderFactory
. - Register the factory class in
Startup.ConfigureServices
.
The sample app includes a value provider and factory example that gets values from cookies. Here's the registration code in Startup.ConfigureServices
:
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(System.Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();
The code shown puts the custom value provider after all the built-in value providers. To make it the first in the list, call Insert(0, new CookieValueProviderFactory())
instead of Add
.
No source for a model property
By default, a model state error isn't created if no value is found for a model property. The property is set to null or a default value:
- Nullable simple types are set to
null
. - Non-nullable value types are set to
default(T)
. For example, a parameterint id
is set to 0. - For complex Types, model binding creates an instance by using the default constructor, without setting properties.
- Arrays are set to
Array.Empty<T>()
, except thatbyte[]
arrays are set tonull
.
If model state should be invalidated when nothing is found in form fields for a model property, use the [BindRequired]
attribute.
Note that this [BindRequired]
behavior applies to model binding from posted form data, not to JSON or XML data in a request body. Request body data is handled by input formatters.
Type conversion errors
If a source is found but can't be converted into the target type, model state is flagged as invalid. The target parameter or property is set to null or a default value, as noted in the previous section.
In an API controller that has the [ApiController]
attribute, invalid model state results in an automatic HTTP 400 response.
In a Razor page, redisplay the page with an error message:
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
_instructorsInMemoryStore.Add(Instructor);
return RedirectToPage("./Index");
}
Client-side validation catches most bad data that would otherwise be submitted to a Razor Pages form. This validation makes it hard to trigger the preceding highlighted code. The sample app includes a Submit with Invalid Date button that puts bad data in the Hire Date field and submits the form. This button shows how the code for redisplaying the page works when data conversion errors occur.
When the page is redisplayed by the preceding code, the invalid input is not shown in the form field. This is because the model property has been set to null or a default value. The invalid input does appear in an error message. But if you want to redisplay the bad data in the form field, consider making the model property a string and doing the data conversion manually.
The same strategy is recommended if you don't want type conversion errors to result in model state errors. In that case, make the model property a string.
Simple types
The simple types that the model binder can convert source strings into include the following:
- Boolean
- Byte, SByte
- Char
- DateTime
- DateTimeOffset
- Decimal
- Double
- Enum
- Guid
- Int16, Int32, Int64
- Single
- TimeSpan
- UInt16, UInt32, UInt64
- Uri
- Version
Complex types
A complex type must have a public default constructor and public writable properties to bind. When model binding occurs, the class is instantiated using the public default constructor.
For each property of the complex type, model binding looks through the sources for the name pattern prefix.property_name. If nothing is found, it looks for just property_name without the prefix.
For binding to a parameter, the prefix is the parameter name. For binding to a PageModel
public property, the prefix is the public property name. Some attributes have a Prefix
property that lets you override the default usage of parameter or property name.
For example, suppose the complex type is the following Instructor
class:
public class Instructor
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
Prefix = parameter name
If the model to be bound is a parameter named instructorToUpdate
:
public IActionResult OnPost(int? id, Instructor instructorToUpdate)
Model binding starts by looking through the sources for the key instructorToUpdate.ID
. If that isn't found, it looks for ID
without a prefix.
Prefix = property name
If the model to be bound is a property named Instructor
of the controller or PageModel
class:
[BindProperty]
public Instructor Instructor { get; set; }
Model binding starts by looking through the sources for the key Instructor.ID
. If that isn't found, it looks for ID
without a prefix.
Custom prefix
If the model to be bound is a parameter named instructorToUpdate
and a Bind
attribute specifies Instructor
as the prefix:
public IActionResult OnPost(
int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)
Model binding starts by looking through the sources for the key Instructor.ID
. If that isn't found, it looks for ID
without a prefix.
Attributes for complex type targets
Several built-in attributes are available for controlling model binding of complex types:
[Bind]
[BindRequired]
[BindNever]
Warning
These attributes affect model binding when posted form data is the source of values. They do not affect input formatters, which process posted JSON and XML request bodies. Input formatters are explained later in this article.
[Bind] attribute
Can be applied to a class or a method parameter. Specifies which properties of a model should be included in model binding. [Bind]
does not affect input formatters.
In the following example, only the specified properties of the Instructor
model are bound when any handler or action method is called:
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
In the following example, only the specified properties of the Instructor
model are bound when the OnPost
method is called:
[HttpPost]
public IActionResult OnPost([Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
The [Bind]
attribute can be used to protect against overposting in create scenarios. It doesn't work well in edit scenarios because excluded properties are set to null or a default value instead of being left unchanged. For protection against overposting, view models are recommended rather than the [Bind]
attribute. For more information, see Security note about overposting.
[ModelBinder] attribute
ModelBinderAttribute can be applied to types, properties, or parameters. It allows specifying the type of model binder used to bind the specific instance or type. For example:
[HttpPost]
public IActionResult OnPost([ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)
The [ModelBinder]
attribute can also be used to change the name of a property or parameter when it's being model bound:
public class Instructor
{
[ModelBinder(Name = "instructor_id")]
public string Id { get; set; }
public string Name { get; set; }
}
[BindRequired] attribute
Can only be applied to model properties, not to method parameters. Causes model binding to add a model state error if binding cannot occur for a model's property. Here's an example:
public class InstructorWithCollection
{
public int ID { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
[BindRequired]
public DateTime HireDate { get; set; }
See also the discussion of the [Required]
attribute in Model validation.
[BindNever] attribute
Can only be applied to model properties, not to method parameters. Prevents model binding from setting a model's property. Here's an example:
public class InstructorWithDictionary
{
[BindNever]
public int ID { get; set; }
Collections
For targets that are collections of simple types, model binding looks for matches to parameter_name or property_name. If no match is found, it looks for one of the supported formats without the prefix. For example:
Suppose the parameter to be bound is an array named
selectedCourses
:public IActionResult OnPost(int? id, int[] selectedCourses)
Form or query string data can be in one of the following formats:
selectedCourses=1050&selectedCourses=2000
selectedCourses[0]=1050&selectedCourses[1]=2000
[0]=1050&[1]=2000
selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
[a]=1050&[b]=2000&index=a&index=b
Avoid binding a parameter or a property named
index
orIndex
if it is adjacent to a collection value. Model binding attempts to useindex
as the index for the collection which might result in incorrect binding. For example, consider the following action:public IActionResult Post(string index, List<Product> products)
In the preceding code, the
index
query string parameter binds to theindex
method parameter and also is used to bind the product collection. Renaming theindex
parameter or using a model binding attribute to configure binding avoids this issue:public IActionResult Post(string productIndex, List<Product> products)
The following format is supported only in form data:
selectedCourses[]=1050&selectedCourses[]=2000
For all of the preceding example formats, model binding passes an array of two items to the
selectedCourses
parameter:- selectedCourses[0]=1050
- selectedCourses[1]=2000
Data formats that use subscript numbers (... [0] ... [1] ...) must ensure that they are numbered sequentially starting at zero. If there are any gaps in subscript numbering, all items after the gap are ignored. For example, if the subscripts are 0 and 2 instead of 0 and 1, the second item is ignored.
Dictionaries
For Dictionary
targets, model binding looks for matches to parameter_name or property_name. If no match is found, it looks for one of the supported formats without the prefix. For example:
Suppose the target parameter is a
Dictionary<int, string>
namedselectedCourses
:public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
The posted form or query string data can look like one of the following examples:
selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
[1050]=Chemistry&selectedCourses[2000]=Economics
selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry& selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
[0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
For all of the preceding example formats, model binding passes a dictionary of two items to the
selectedCourses
parameter:- selectedCourses["1050"]="Chemistry"
- selectedCourses["2000"]="Economics"
Constructor binding and record types
Model binding requires that complex types have a parameterless constructor. Both System.Text.Json
and Newtonsoft.Json
based input formatters support deserialization of classes that don't have a parameterless constructor.
C# 9 introduces record types, which are a great way to succinctly represent data over the network. ASP.NET Core adds support for model binding and validating record types with a single constructor:
public record Person([Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);
public class PersonController
{
public IActionResult Index() => View();
[HttpPost]
public IActionResult Index(Person person)
{
...
}
}
Person/Index.cshtml
:
@model Person
<label>Name: <input asp-for="Name" /></label>
...
<label>Age: <input asp-for="Age" /></label>
When validating record types, the runtime searches for binding and validation metadata specifically on parameters rather than on properties.
The framework allows binding to and validating record types:
public record Person([Required] string Name, [Range(0, 100)] int Age);
For the preceding to work, the type must:
- Be a record type.
- Have exactly one public constructor.
- Contain parameters that have a property with the same name and type. The names must not differ by case.
POCOs without parameterless constructors
POCOs that do not have parameterless constructors can't be bound.
The following code results in an exception saying that the type must have a parameterless constructor:
public class Person(string Name)
public record Person([Required] string Name, [Range(0, 100)] int Age)
{
public Person(string Name) : this (Name, 0);
}
Record types with manually authored constructors
Record types with manually authored constructors that look like primary constructors work
public record Person
{
public Person([Required] string Name, [Range(0, 100)] int Age) => (this.Name, this.Age) = (Name, Age);
public string Name { get; set; }
public int Age { get; set; }
}
Record types, validation and binding metadata
For record types, validation and binding metadata on parameters is used. Any metadata on properties is ignored
public record Person (string Name, int Age)
{
[BindProperty(Name = "SomeName")] // This does not get used
[Required] // This does not get used
public string Name { get; init; }
}
Validation and metadata
Validation uses metadata on the parameter but uses the property to read the value. In the ordinary case with primary constructors, the two would be identical. However, there are ways to defeat it:
public record Person([Required] string Name)
{
private readonly string _name;
public Name { get; init => _name = value ?? string.Empty; } // Now this property is never null. However this object could have been constructed as `new Person(null);`
}
TryUpdateModel does not update parameters on a record type
public record Person(string Name)
{
public int Age { get; set; }
}
var person = new Person("initial-name");
TryUpdateModel(person, ...);
In this case, MVC will not attempt to bind Name
again. However, Age
is allowed to be updated
Globalization behavior of model binding route data and query strings
The ASP.NET Core route value provider and query string value provider:
- Treat values as invariant culture.
- Expect that URLs are culture-invariant.
In contrast, values coming from form data undergo a culture-sensitive conversion. This is by design so that URLs are shareable across locales.
To make the ASP.NET Core route value provider and query string value provider undergo a culture-sensitive conversion:
- Inherit from IValueProviderFactory
- Copy the code from QueryStringValueProviderFactory or RouteValueValueProviderFactory
- Replace the culture value passed to the value provider constructor with CultureInfo.CurrentCulture
- Replace the default value provider factory in MVC options with your new one:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options =>
{
var index = options.ValueProviderFactories.IndexOf(
options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>().Single());
options.ValueProviderFactories[index] = new CulturedQueryStringValueProviderFactory();
});
}
public class CulturedQueryStringValueProviderFactory : IValueProviderFactory
{
public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var query = context.ActionContext.HttpContext.Request.Query;
if (query != null && query.Count > 0)
{
var valueProvider = new QueryStringValueProvider(
BindingSource.Query,
query,
CultureInfo.CurrentCulture);
context.ValueProviders.Add(valueProvider);
}
return Task.CompletedTask;
}
}
Special data types
There are some special data types that model binding can handle.
IFormFile and IFormFileCollection
An uploaded file included in the HTTP request. Also supported is IEnumerable<IFormFile>
for multiple files.
CancellationToken
Actions can optionally bind a CancellationToken
as a parameter. This binds RequestAborted that signals when the connection underlying the HTTP request is aborted. Actions can use this parameter to cancel long running async operations that are executed as part of the controller actions.
FormCollection
Used to retrieve all the values from posted form data.
Input formatters
Data in the request body can be in JSON, XML, or some other format. To parse this data, model binding uses an input formatter that is configured to handle a particular content type. By default, ASP.NET Core includes JSON based input formatters for handling JSON data. You can add other formatters for other content types.
ASP.NET Core selects input formatters based on the Consumes attribute. If no attribute is present, it uses the Content-Type header.
To use the built-in XML input formatters:
Install the
Microsoft.AspNetCore.Mvc.Formatters.Xml
NuGet package.In
Startup.ConfigureServices
, call AddXmlSerializerFormatters or AddXmlDataContractSerializerFormatters.services.AddRazorPages() .AddMvcOptions(options => { options.ValueProviderFactories.Add(new CookieValueProviderFactory()); options.ModelMetadataDetailsProviders.Add( new ExcludeBindingMetadataProvider(typeof(System.Version))); options.ModelMetadataDetailsProviders.Add( new SuppressChildValidationMetadataProvider(typeof(System.Guid))); }) .AddXmlSerializerFormatters();
Apply the
Consumes
attribute to controller classes or action methods that should expect XML in the request body.[HttpPost] [Consumes("application/xml")] public ActionResult<Pet> Create(Pet pet)
For more information, see Introducing XML Serialization.
Customize model binding with input formatters
An input formatter takes full responsibility for reading data from the request body. To customize this process, configure the APIs used by the input formatter. This section describes how to customize the System.Text.Json
-based input formatter to understand a custom type named ObjectId
.
Consider the following model, which contains a custom ObjectId
property named Id
:
public class ModelWithObjectId
{
public ObjectId Id { get; set; }
}
To customize the model binding process when using System.Text.Json
, create a class derived from JsonConverter<T>:
internal class ObjectIdConverter : JsonConverter<ObjectId>
{
public override ObjectId Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return new ObjectId(JsonSerializer.Deserialize<int>(ref reader, options));
}
public override void Write(
Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value.Id);
}
}
To use a custom converter, apply the JsonConverterAttribute attribute to the type. In the following example, the ObjectId
type is configured with ObjectIdConverter
as its custom converter:
[JsonConverter(typeof(ObjectIdConverter))]
public struct ObjectId
{
public ObjectId(int id) =>
Id = id;
public int Id { get; }
}
For more information, see How to write custom converters.
Exclude specified types from model binding
The model binding and validation systems' behavior is driven by ModelMetadata. You can customize ModelMetadata
by adding a details provider to MvcOptions.ModelMetadataDetailsProviders. Built-in details providers are available for disabling model binding or validation for specified types.
To disable model binding on all models of a specified type, add an ExcludeBindingMetadataProvider in Startup.ConfigureServices
. For example, to disable model binding on all models of type System.Version
:
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(System.Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();
To disable validation on properties of a specified type, add a SuppressChildValidationMetadataProvider in Startup.ConfigureServices
. For example, to disable validation on properties of type System.Guid
:
services.AddRazorPages()
.AddMvcOptions(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(System.Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();
Custom model binders
You can extend model binding by writing a custom model binder and using the [ModelBinder]
attribute to select it for a given target. Learn more about custom model binding.
Manual model binding
Model binding can be invoked manually by using the TryUpdateModelAsync method. The method is defined on both ControllerBase
and PageModel
classes. Method overloads let you specify the prefix and value provider to use. The method returns false
if model binding fails. Here's an example:
if (await TryUpdateModelAsync<InstructorWithCollection>(
newInstructor,
"Instructor",
i => i.FirstMidName, i => i.LastName, i => i.HireDate))
{
_instructorsInMemoryStore.Add(newInstructor);
return RedirectToPage("./Index");
}
PopulateAssignedCourseData(newInstructor);
return Page();
TryUpdateModelAsync uses value providers to get data from the form body, query string, and route data. TryUpdateModelAsync
is typically:
- Used with Razor Pages and MVC apps using controllers and views to prevent over-posting.
- Not used with a web API unless consumed from form data, query strings, and route data. Web API endpoints that consume JSON use Input formatters to deserialize the request body into an object.
For more information, see TryUpdateModelAsync.
[FromServices] attribute
This attribute's name follows the pattern of model binding attributes that specify a data source. But it's not about binding data from a value provider. It gets an instance of a type from the dependency injection container. Its purpose is to provide an alternative to constructor injection for when you need a service only if a particular method is called.
Additional resources
ASP.NET Core