- ASP.NET Core 中的模型绑定Model Binding in ASP.NET Core
- 什么是模型绑定What is Model binding
- 示例Example
- 目标Targets
- 源Sources
- 不存在模型属性的源No source for a model property
- 类型转换错误Type conversion errors
- 简单类型Simple types
- 复杂类型Complex types
- 集合Collections
- 字典Dictionaries
- 模型绑定路由数据和查询字符串的全球化行为Globalization behavior of model binding route data and query strings
- 特殊数据类型Special data types
- 输入格式化程序Input formatters
- 从模型绑定中排除指定类型Exclude specified types from model binding
- 自定义模型绑定器Custom model binders
- 手动模型绑定Manual model binding
- [FromServices] 属性[FromServices] attribute
- 其他资源Additional resources
- 什么是模型绑定What is Model binding
- 示例Example
- 目标Targets
- 源Sources
- 不存在模型属性的源No source for a model property
- 类型转换错误Type conversion errors
- 简单类型Simple types
- 复杂类型Complex types
- 集合Collections
- 字典Dictionaries
- 模型绑定路由数据和查询字符串的全球化行为Globalization behavior of model binding route data and query strings
- 特殊数据类型Special data types
- 输入格式化程序Input formatters
- 从模型绑定中排除指定类型Exclude specified types from model binding
- 自定义模型绑定器Custom model binders
- 手动模型绑定Manual model binding
- [FromServices] 属性[FromServices] attribute
- 其他资源Additional resources
ASP.NET Core 中的模型绑定Model Binding in ASP.NET Core
本文内容
本文解释了模型绑定的定义、模型绑定的工作原理,以及如何自定义模型绑定的行为。
什么是模型绑定What is Model binding
控制器和 Razor Pages 处理来自 HTTP 请求的数据。例如,路由数据可以提供一个记录键,而发布的表单域可以为模型的属性提供一个值。编写代码以检索这些值,并将其从字符串转换为 .NET 类型不仅繁琐,而且还容易出错。模型绑定会自动化该过程。模型绑定系统:
- 从各种源(如路由数据、表单域和查询字符串)中检索数据。
- 将数据提供给方法参数和公共属性中的控制器和 Razor Pages。
- 将字符串数据转换为 .NET 类型。
- 更新复杂类型的属性。
示例Example
假设有以下操作方法:
[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)
并且应用收到一个带有以下 URL 的请求:
http://contoso.com/api/pets/2?DogsOnly=true
在路由系统选择该操作方法之后,模型绑定执行以下步骤:
- 查找
GetByID
的第一个参数,该参数是一个名为id
的整数。 - 查找 HTTP 请求中的可用源,并在路由数据中查找
id
=“2”。 - 将字符串“2”转换为整数 2。
- 查找
GetByID
的下一个参数,该参数是一个名为dogsOnly
的布尔值。 - 查找源,并在查询字符串中查找“DogsOnly=true”。名称匹配不区分大小写。
- 将字符串“true”转换为布尔值
true
。
然后,该框架会调用 GetById
方法,为 id
参数传入 2,并为 true
参数传入 dogsOnly
。
在前面的示例中,模型绑定目标是简单类型的方法参数。目标也可以是复杂类型的属性。成功绑定每个属性后,将对属性进行模型验证。有关绑定到模型的数据以及任意绑定或验证错误的记录都存储在 ControllerBase.ModelState 或 PageModel.ModelState 中。为查明该过程是否已成功,应用会检查 ModelState.IsValid 标志。
目标Targets
模型绑定尝试查找以下类型目标的值:
- 将请求路由到的控制器操作方法的参数。
- 将请求路由到的 Razor Pages 处理程序方法的参数。
- 控制器或
PageModel
类的公共属性(若由特性指定)。
[BindProperty] 属性[BindProperty] attribute
可应用于控制器或 PageModel
类的公共属性,从而使模型绑定以该属性为目标:
public class EditModel : InstructorsPageModel
{
[BindProperty]
public Instructor Instructor { get; set; }
[BindProperties] 属性[BindProperties] attribute
可在 ASP.NET Core 2.1 及更高版本中获得。可应用于控制器或 PageModel
类,以使模型绑定以该类的所有公共属性为目标:
[BindProperties(SupportsGet = true)]
public class CreateModel : InstructorsPageModel
{
public Instructor Instructor { get; set; }
HTTP GET 请求的模型绑定Model binding for HTTP GET requests
默认情况下,不绑定 HTTP GET 请求的属性。通常,GET 请求只需一个记录 ID 参数。记录 ID 用于查找数据库中的项。因此,无需绑定包含模型实例的属性。在需要将属性绑定到 GET 请求中的数据的情况下,请将 SupportsGet
属性设置为 true
:
[BindProperty(Name = "ai_user", SupportsGet = true)]
public string ApplicationInsightsCookie { get; set; }
源Sources
默认情况下,模型绑定以键值对的形式从 HTTP 请求中的以下源中获取数据:
- 表单域
- 请求正文(对于具有 [ApiController] 属性的控制器。)
- 路由数据
- 查询字符串参数
- 上传的文件
对于每个目标参数或属性,按照之前列表中指示的顺序扫描源。有几个例外情况:
- 路由数据和查询字符串值仅用于简单类型。
- 上传的文件仅绑定到实现
IFormFile
或IEnumerable<IFormFile>
的目标类型。
如果默认源不正确,请使用下列属性之一来指定源:
[FromQuery]
- 从查询字符串中获取值。[FromRoute]
- 从路由数据中获取值。[FromForm]
- 从发布的表单域中获取值。[FromBody]
- 从请求正文中获取值。[FromHeader]
- 从 HTTP 标头中获取值。
这些属性:
- 分别添加到模型属性(而不是模型类),如以下示例所示:
public class Instructor
{
public int ID { get; set; }
[FromQuery(Name = "Note")]
public string NoteFromQueryString { get; set; }
- 选择性地在构造函数中接受模型名称值。提供此选项的目的是应对属性名称与请求中的值不匹配的情况。例如,请求中的值可能是名称中带有连字符的标头,如以下示例所示:
public void OnGet([FromHeader(Name = "Accept-Language")] string language)
[FromBody] 属性[FromBody] attribute
将 [FromBody]
特性应用于一个参数,以便从一个 HTTP 请求的正文填充其属性。ASP.NET Core 运行时将读取正文的责任委托给输入格式化程序。输入格式化程序的解释位于本文后面部分。
将 [FromBody]
应用于复杂类型参数时,应用于其属性的任何绑定源属性都将被忽略。例如,以下 Create
操作指定从正文填充其 pet
参数:
public ActionResult<Pet> Create([FromBody] Pet pet)
Pet
类指定从查询字符串参数填充其 Breed
属性:
public class Pet
{
public string Name { get; set; }
[FromQuery] // Attribute is ignored.
public string Breed { get; set; }
}
在上面的示例中:
[FromQuery]
特性被忽略。Breed
属性未从查询字符串参数进行填充。
输入格式化程序只读取正文,不了解绑定源特性。如果在正文中找到合适的值,则使用该值填充 Breed
属性。
不要将 [FromBody]
应用于每个操作方法的多个参数。输入格式化程序读取请求流后,无法再次读取该流以绑定其他 [FromBody]
参数。
其他源Additional sources
源数据由“值提供程序”提供给模型绑定系统。你可以编写并注册自定义值提供程序,这些提供程序从其他源中获取用于模型绑定的数据。例如,你可能需要来自 Cookie 或会话状态的数据。要从新的源中获取数据,请执行以下操作:
- 创建用于实现
IValueProvider
的类。 - 创建用于实现
IValueProviderFactory
的类。 - 在
Startup.ConfigureServices
中注册工厂类。
示例应用包括从 Cookie 中获取值的 值提供程序和工厂示例。以下是 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();
所示代码将自定义值提供程序置于所有内置值提供程序之后。要将其置于列表中的首位,请调用 Insert(0, new CookieValueProviderFactory())
而不是 Add
。
不存在模型属性的源No source for a model property
默认情况下,如果找不到模型属性的值,则不会创建模型状态错误。该属性设置为 NULL 或默认值:
- 可以为 Null 的简单类型设置为
null
。 - 不可以为 Null 的值类型设置为
default(T)
。例如,参数int id
设置为 0。 - 对于复杂类型,模型绑定使用默认构造函数来创建实例,而不设置属性。
- 数组设置为
Array.Empty<T>()
,但byte[]
数组设置为null
。
如果在模型属性的表单域中找不到任何内容时,模型状态应无效,请使用 [BindRequired]
属性。
请注意,此 [BindRequired]
行为适用于发布的表单数据中的模型绑定,而不适用于请求正文中的 JSON 或 XML 数据。请求正文数据由输入格式化程序进行处理。
类型转换错误Type conversion errors
如果找到源,但无法将其转换为目标类型,则模型状态将被标记为无效。目标参数或属性设置为 NULL 或默认值,如上一部分所述。
在具有 [ApiController]
属性的 API 控制器中,无效的模型状态会导致自动 HTTP 400 响应。
在 Razor Pages 中,重新显示带有错误消息的页面:
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
_instructorsInMemoryStore.Add(Instructor);
return RedirectToPage("./Index");
}
客户端验证会捕获原本会提交到 Razor Pages 表单中的大多数错误数据。此验证使得先前突出显示的代码难以被触发。示例应用包含一个“提交无效日期”按钮,该按钮将错误数据置于“雇用日期”字段中并提交表单。此按钮显示在发生数据转换错误时用于重新显示页的代码将如何工作。
在使用先前的代码重新显示页时,表单域中不会显示无效的输入。这是因为模型属性已设置为 NULL 或默认值。无效输入会出现在错误消息中。但是,如果要在表单域中重新显示错误数据,可以考虑将模型属性设置为字符串并手动执行数据转换。
如果不希望发生类型转换错误导致模型状态错误的情况,建议使用相同的策略。在这种情况下,将模型属性设置为字符串。
简单类型Simple types
模型绑定器可以将源字符串转换为以下简单类型:
- 布尔值
- 字节、SByte
- Char
- DateTime
- DateTimeOffset
- 小数
- 双精度
- Enum
- Guid
- Int16、Int32、Int64
- Single
- TimeSpan
- UInt16、UInt32、UInt64
- Uri
- 版本
复杂类型Complex types
复杂类型必须具有要绑定的公共默认构造函数和公共可写属性。进行模型绑定时,将使用公共默认构造函数来实例化类。
对于复杂类型的每个属性,模型绑定会查找名称模式 prefix.property_name 的源。如果未找到,它将仅查找不含前缀的 properties_name。
对于绑定到参数,前缀是参数名称。对于绑定到 PageModel
公共属性,前缀是公共属性名称。某些属性具有 Prefix
属性,让你可以替代参数或属性名称的默认用法。
例如,假设复杂类型是以下 Instructor
类:
public class Instructor
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
前缀 = 参数名称Prefix = parameter name
如果要绑定的模型是一个名为 instructorToUpdate
的参数:
public IActionResult OnPost(int? id, Instructor instructorToUpdate)
模型绑定从查找键 instructorToUpdate.ID
的源开始操作。如果未找到,它将查找不含前缀的 ID
。
前缀 = 属性名称Prefix = property name
如果要绑定的模型是控制器或 Instructor
类的一个名为 PageModel
的属性:
[BindProperty]
public Instructor Instructor { get; set; }
模型绑定从查找键 Instructor.ID
的源开始操作。如果未找到,它将查找不含前缀的 ID
。
自定义前缀Custom prefix
如果要绑定的模型是名为 instructorToUpdate
的参数,并且 Bind
属性指定 Instructor
作为前缀:
public IActionResult OnPost(
int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)
模型绑定从查找键 Instructor.ID
的源开始操作。如果未找到,它将查找不含前缀的 ID
。
复杂类型目标的属性Attributes for complex type targets
多个内置属性可用于控制复杂类型的模型绑定:
[BindRequired]
[BindNever]
[Bind]
备注
如果发布的表单数据是值的源,则这些属性会影响模型绑定。它们不会影响处理发布的 JSON 和 XML 请求正文的输入格式化程序。输入格式化程序的解释位于本文后面部分。
另请参阅[Required]
模型验证中针对 属性的讨论。
[BindRequired] 属性[BindRequired] attribute
只能应用于模型属性,不能应用于方法参数。如果无法对模型属性进行绑定,则会导致模型绑定添加模型状态错误。下面是一个示例:
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; }
[BindNever] 属性[BindNever] attribute
只能应用于模型属性,不能应用于方法参数。防止模型绑定设置模型的属性。下面是一个示例:
public class InstructorWithDictionary
{
[BindNever]
public int ID { get; set; }
[Bind] 属性[Bind] attribute
可应用于类或方法参数。指定模型绑定中应包含的模型属性。
在下面的示例中,当调用任意处理程序或操作方法时,只绑定 Instructor
模型的指定属性:
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
在下面的示例中,当调用 Instructor
方法时,只绑定 OnPost
模型的指定属性:
[HttpPost]
public IActionResult OnPost([Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
[Bind]
属性可用于防止“创建”方案中的过多发布情况。由于排除的属性设置为 NULL 或默认值,而不是保持不变,因此它在编辑方案中无法很好地工作。为防止过多发布,建议使用视图模型,而不是使用 [Bind]
属性。有关详细信息,请参阅有关过多发布的安全性说明。
集合Collections
对于是简单类型集合的目标,模型绑定将查找 parameter_name 或 property_name 的匹配项。如果找不到匹配项,它将查找某种不含前缀的受支持的格式。例如:
- 假设要绑定的参数是名为
selectedCourses
的数组:
public IActionResult OnPost(int? id, int[] selectedCourses)
- 表单或查询字符串数据可以采用以下某种格式:
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
- 只有表单数据支持以下格式:
selectedCourses[]=1050&selectedCourses[]=2000
对于前面所有的示例格式,模型绑定将两个项的数组传递给
selectedCourses
参数:- selectedCourses[0]=1050
- selectedCourses[1]=2000
使用下标数字的数据格式 (… [0] … [1] …) 必须确保从零开始按顺序进行编号。如果下标编号中存在任何间隔,则间隔后的所有项都将被忽略。例如,如果下标是 0 和 2,而不是 0 和 1,则第二个项会被忽略。
字典Dictionaries
对于 Dictionary
目标,模型绑定会查找 parameter_name 或 property_name 的匹配项。如果找不到匹配项,它将查找某种不含前缀的受支持的格式。例如:
- 假设目标参数是名为
Dictionary<int, string>
的selectedCourses
:
public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
- 发布的表单或查询字符串数据可以类似于以下某一示例:
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
对于前面所有的示例格式,模型绑定将两个项的字典传递给
selectedCourses
参数:- selectedCourses["1050"]="Chemistry"
- selectedCourses["2000"]="Economics"
模型绑定路由数据和查询字符串的全球化行为Globalization behavior of model binding route data and query strings
ASP.NET Core 路由值提供程序和查询字符串值提供程序:
- 将值视为固定区域性。
- URL 的区域性应固定。
相反,来自窗体数据的值要进行区分区域性的转换。这是设计使然,目的是让 URL 可在各个区域设置中共享。
使 ASP.NET Core 路由值提供程序和查询字符串值提供程序进行区分区域性的转换:
- 继承自 IValueProviderFactory
- 从 QueryStringValueProviderFactory 或 RouteValueValueProviderFactory 复制代码
- 使用 CultureInfo.CurrentCulture 替换传递给值提供程序构造函数的区域性值
- 将 MVC 选项中的默认值提供程序工厂替换为新的工厂:
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
模型绑定可以处理某些特殊的数据类型。
IFormFile 和 IFormFileCollectionIFormFile and IFormFileCollection
HTTP 请求中包含的上传文件。还支持多个文件的 IEnumerable<IFormFile>
。
CancellationTokenCancellationToken
用于取消异步控制器中的活动。
FormCollectionFormCollection
用于从发布的表单数据中检索所有的值。
输入格式化程序Input formatters
请求正文中的数据可以是 JSON、XML 或其他某种格式。要分析此数据,模型绑定会使用配置为处理特定内容类型的输入格式化程序。默认情况下,ASP.NET Core 包括用于处理 JSON 数据的基于 JSON 的输入格式化程序。可以为其他内容类型添加其他格式化程序。
ASP.NET Core 基于 Consumes 属性来选择输入格式化程序。如果没有属性,它将使用 Content-Type 标头。
要使用内置 XML 输入格式化程序,请执行以下操作:
安装
Microsoft.AspNetCore.Mvc.Formatters.Xml
NuGet 包。在
Startup.ConfigureServices
中,调用 AddXmlSerializerFormatters 或 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();
- 将
Consumes
属性应用于应在请求正文中使用 XML 的控制器类或操作方法。
[HttpPost]
[Consumes("application/xml")]
public ActionResult<Pet> Create(Pet pet)
有关更多信息,请参阅 XML 序列化简介。
使用输入格式化程序自定义模型绑定Customize model binding with input formatters
由输入格式化程序完全负责从请求正文读取数据。若要自定义此过程,请配置输入格式化程序使用的 API。此部分介绍如何自定义基于 System.Text.Json
的输入格式化程序,以了解自定义类型 ObjectId
。
以包含自定义 ObjectId
属性 Id
的模型为例:
public class ModelWithObjectId
{
public ObjectId Id { get; set; }
}
使用 System.Text.Json
时,若要自定义模型绑定过程,请创建派生自 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);
}
}
将 JsonConverterAttribute 属性应用到此类型,以使用自定义转换器。在下面的示例中,为 ObjectId
类型配置了 ObjectIdConverter
来作为其自定义转换器:
[JsonConverter(typeof(ObjectIdConverter))]
public struct ObjectId
{
public ObjectId(int id) =>
Id = id;
public int Id { get; }
}
有关详细信息,请参阅如何编写自定义转换器。
从模型绑定中排除指定类型Exclude specified types from model binding
模型绑定和验证系统的行为由 ModelMetadata 驱动。可通过向 ModelMetadata
MvcOptions.ModelMetadataDetailsProviders 添加详细信息提供程序来自定义 。内置详细信息提供程序可用于禁用指定类型的模型绑定或验证。
要禁用指定类型的所有模型的模型绑定,请在 ExcludeBindingMetadataProvider 中添加 Startup.ConfigureServices
。例如,禁用对 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();
要禁用指定类型的属性的验证,请在 SuppressChildValidationMetadataProvider 中添加 Startup.ConfigureServices
。例如,禁用对 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
通过编写自定义模型绑定器,并使用 [ModelBinder]
属性为给定目标选择该模型绑定器,可扩展模型绑定。详细了解自定义模型绑定。
手动模型绑定Manual model binding
可以使用 TryUpdateModelAsync 方法手动调用模型绑定。ControllerBase
和 PageModel
类上均定义了此方法。方法重载允许指定要使用的前缀和值提供程序。如果模型绑定失败,该方法返回 false
。下面是一个示例:
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 使用值提供程序从窗体正文、查询字符串和路由数据获取数据。TryUpdateModelAsync
通常有以下特点:
- 用于 Razor Pages 和 MVC 应用,同时使用控制器和视图防止过度发布。
- 不用于 Web API(除非窗体数据、查询字符串和路由数据使用它)。使用 JSON 的 Web API 终结点使用输入格式化程序将请求正文反序列化为对象。
有关详细信息,请参阅 TryUpdateModelAsync。
[FromServices] 属性[FromServices] attribute
此属性的名称遵循指定数据源的模型绑定属性的模式。但这与绑定来自值提供程序的数据无关。它从依赖关系注入容器中获取类型的实例。其目的在于,在仅当调用特定方法时需要服务的情况下,提供构造函数注入的替代方法。
其他资源Additional resources
本文解释了模型绑定的定义、模型绑定的工作原理,以及如何自定义模型绑定的行为。
什么是模型绑定What is Model binding
控制器和 Razor Pages 处理来自 HTTP 请求的数据。例如,路由数据可以提供一个记录键,而发布的表单域可以为模型的属性提供一个值。编写代码以检索这些值,并将其从字符串转换为 .NET 类型不仅繁琐,而且还容易出错。模型绑定会自动化该过程。模型绑定系统:
- 从各种源(如路由数据、表单域和查询字符串)中检索数据。
- 将数据提供给方法参数和公共属性中的控制器和 Razor Pages。
- 将字符串数据转换为 .NET 类型。
- 更新复杂类型的属性。
示例Example
假设有以下操作方法:
[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)
并且应用收到一个带有以下 URL 的请求:
http://contoso.com/api/pets/2?DogsOnly=true
在路由系统选择该操作方法之后,模型绑定执行以下步骤:
- 查找
GetByID
的第一个参数,该参数是一个名为id
的整数。 - 查找 HTTP 请求中的可用源,并在路由数据中查找
id
=“2”。 - 将字符串“2”转换为整数 2。
- 查找
GetByID
的下一个参数,该参数是一个名为dogsOnly
的布尔值。 - 查找源,并在查询字符串中查找“DogsOnly=true”。名称匹配不区分大小写。
- 将字符串“true”转换为布尔值
true
。
然后,该框架会调用 GetById
方法,为 id
参数传入 2,并为 true
参数传入 dogsOnly
。
在前面的示例中,模型绑定目标是简单类型的方法参数。目标也可以是复杂类型的属性。成功绑定每个属性后,将对属性进行模型验证。有关绑定到模型的数据以及任意绑定或验证错误的记录都存储在 ControllerBase.ModelState 或 PageModel.ModelState 中。为查明该过程是否已成功,应用会检查 ModelState.IsValid 标志。
目标Targets
模型绑定尝试查找以下类型目标的值:
- 将请求路由到的控制器操作方法的参数。
- 将请求路由到的 Razor Pages 处理程序方法的参数。
- 控制器或
PageModel
类的公共属性(若由特性指定)。
[BindProperty] 属性[BindProperty] attribute
可应用于控制器或 PageModel
类的公共属性,从而使模型绑定以该属性为目标:
public class EditModel : InstructorsPageModel
{
[BindProperty]
public Instructor Instructor { get; set; }
[BindProperties] 属性[BindProperties] attribute
可在 ASP.NET Core 2.1 及更高版本中获得。可应用于控制器或 PageModel
类,以使模型绑定以该类的所有公共属性为目标:
[BindProperties(SupportsGet = true)]
public class CreateModel : InstructorsPageModel
{
public Instructor Instructor { get; set; }
HTTP GET 请求的模型绑定Model binding for HTTP GET requests
默认情况下,不绑定 HTTP GET 请求的属性。通常,GET 请求只需一个记录 ID 参数。记录 ID 用于查找数据库中的项。因此,无需绑定包含模型实例的属性。在需要将属性绑定到 GET 请求中的数据的情况下,请将 SupportsGet
属性设置为 true
:
[BindProperty(Name = "ai_user", SupportsGet = true)]
public string ApplicationInsightsCookie { get; set; }
源Sources
默认情况下,模型绑定以键值对的形式从 HTTP 请求中的以下源中获取数据:
- 表单域
- 请求正文(对于具有 [ApiController] 属性的控制器。)
- 路由数据
- 查询字符串参数
- 上传的文件
对于每个目标参数或属性,按照之前列表中指示的顺序扫描源。有几个例外情况:
- 路由数据和查询字符串值仅用于简单类型。
- 上传的文件仅绑定到实现
IFormFile
或IEnumerable<IFormFile>
的目标类型。
如果默认源不正确,请使用下列属性之一来指定源:
[FromQuery]
- 从查询字符串中获取值。[FromRoute]
- 从路由数据中获取值。[FromForm]
- 从发布的表单域中获取值。[FromBody]
- 从请求正文中获取值。[FromHeader]
- 从 HTTP 标头中获取值。
这些属性:
- 分别添加到模型属性(而不是模型类),如以下示例所示:
public class Instructor
{
public int ID { get; set; }
[FromQuery(Name = "Note")]
public string NoteFromQueryString { get; set; }
- 选择性地在构造函数中接受模型名称值。提供此选项的目的是应对属性名称与请求中的值不匹配的情况。例如,请求中的值可能是名称中带有连字符的标头,如以下示例所示:
public void OnGet([FromHeader(Name = "Accept-Language")] string language)
[FromBody] 属性[FromBody] attribute
将 [FromBody]
特性应用于一个参数,以便从一个 HTTP 请求的正文填充其属性。ASP.NET Core 运行时将读取正文的责任委托给输入格式化程序。输入格式化程序的解释位于本文后面部分。
将 [FromBody]
应用于复杂类型参数时,应用于其属性的任何绑定源属性都将被忽略。例如,以下 Create
操作指定从正文填充其 pet
参数:
public ActionResult<Pet> Create([FromBody] Pet pet)
Pet
类指定从查询字符串参数填充其 Breed
属性:
public class Pet
{
public string Name { get; set; }
[FromQuery] // Attribute is ignored.
public string Breed { get; set; }
}
在上面的示例中:
[FromQuery]
特性被忽略。Breed
属性未从查询字符串参数进行填充。
输入格式化程序只读取正文,不了解绑定源特性。如果在正文中找到合适的值,则使用该值填充 Breed
属性。
不要将 [FromBody]
应用于每个操作方法的多个参数。输入格式化程序读取请求流后,无法再次读取该流以绑定其他 [FromBody]
参数。
其他源Additional sources
源数据由“值提供程序”提供给模型绑定系统。你可以编写并注册自定义值提供程序,这些提供程序从其他源中获取用于模型绑定的数据。例如,你可能需要来自 Cookie 或会话状态的数据。要从新的源中获取数据,请执行以下操作:
- 创建用于实现
IValueProvider
的类。 - 创建用于实现
IValueProviderFactory
的类。 - 在
Startup.ConfigureServices
中注册工厂类。
示例应用包括从 Cookie 中获取值的 值提供程序和工厂示例。以下是 Startup.ConfigureServices
中的注册代码:
services.AddMvc(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(System.Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
所示代码将自定义值提供程序置于所有内置值提供程序之后。要将其置于列表中的首位,请调用 Insert(0, new CookieValueProviderFactory())
而不是 Add
。
不存在模型属性的源No source for a model property
默认情况下,如果找不到模型属性的值,则不会创建模型状态错误。该属性设置为 NULL 或默认值:
- 可以为 Null 的简单类型设置为
null
。 - 不可以为 Null 的值类型设置为
default(T)
。例如,参数int id
设置为 0。 - 对于复杂类型,模型绑定使用默认构造函数来创建实例,而不设置属性。
- 数组设置为
Array.Empty<T>()
,但byte[]
数组设置为null
。
如果在模型属性的表单域中找不到任何内容时,模型状态应无效,请使用 [BindRequired]
属性。
请注意,此 [BindRequired]
行为适用于发布的表单数据中的模型绑定,而不适用于请求正文中的 JSON 或 XML 数据。请求正文数据由输入格式化程序进行处理。
类型转换错误Type conversion errors
如果找到源,但无法将其转换为目标类型,则模型状态将被标记为无效。目标参数或属性设置为 NULL 或默认值,如上一部分所述。
在具有 [ApiController]
属性的 API 控制器中,无效的模型状态会导致自动 HTTP 400 响应。
在 Razor Pages 中,重新显示带有错误消息的页面:
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
_instructorsInMemoryStore.Add(Instructor);
return RedirectToPage("./Index");
}
客户端验证会捕获原本会提交到 Razor Pages 表单中的大多数错误数据。此验证使得先前突出显示的代码难以被触发。示例应用包含一个“提交无效日期”按钮,该按钮将错误数据置于“雇用日期”字段中并提交表单。此按钮显示在发生数据转换错误时用于重新显示页的代码将如何工作。
在使用先前的代码重新显示页时,表单域中不会显示无效的输入。这是因为模型属性已设置为 NULL 或默认值。无效输入会出现在错误消息中。但是,如果要在表单域中重新显示错误数据,可以考虑将模型属性设置为字符串并手动执行数据转换。
如果不希望发生类型转换错误导致模型状态错误的情况,建议使用相同的策略。在这种情况下,将模型属性设置为字符串。
简单类型Simple types
模型绑定器可以将源字符串转换为以下简单类型:
- 布尔值
- 字节、SByte
- Char
- DateTime
- DateTimeOffset
- 小数
- 双精度
- Enum
- Guid
- Int16、Int32、Int64
- Single
- TimeSpan
- UInt16、UInt32、UInt64
- Uri
- 版本
复杂类型Complex types
复杂类型必须具有要绑定的公共默认构造函数和公共可写属性。进行模型绑定时,将使用公共默认构造函数来实例化类。
对于复杂类型的每个属性,模型绑定会查找名称模式 prefix.property_name 的源。如果未找到,它将仅查找不含前缀的 properties_name。
对于绑定到参数,前缀是参数名称。对于绑定到 PageModel
公共属性,前缀是公共属性名称。某些属性具有 Prefix
属性,让你可以替代参数或属性名称的默认用法。
例如,假设复杂类型是以下 Instructor
类:
public class Instructor
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
前缀 = 参数名称Prefix = parameter name
如果要绑定的模型是一个名为 instructorToUpdate
的参数:
public IActionResult OnPost(int? id, Instructor instructorToUpdate)
模型绑定从查找键 instructorToUpdate.ID
的源开始操作。如果未找到,它将查找不含前缀的 ID
。
前缀 = 属性名称Prefix = property name
如果要绑定的模型是控制器或 Instructor
类的一个名为 PageModel
的属性:
[BindProperty]
public Instructor Instructor { get; set; }
模型绑定从查找键 Instructor.ID
的源开始操作。如果未找到,它将查找不含前缀的 ID
。
自定义前缀Custom prefix
如果要绑定的模型是名为 instructorToUpdate
的参数,并且 Bind
属性指定 Instructor
作为前缀:
public IActionResult OnPost(
int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)
模型绑定从查找键 Instructor.ID
的源开始操作。如果未找到,它将查找不含前缀的 ID
。
复杂类型目标的属性Attributes for complex type targets
多个内置属性可用于控制复杂类型的模型绑定:
[BindRequired]
[BindNever]
[Bind]
备注
如果发布的表单数据是值的源,则这些属性会影响模型绑定。它们不会影响处理发布的 JSON 和 XML 请求正文的输入格式化程序。输入格式化程序的解释位于本文后面部分。
另请参阅[Required]
模型验证中针对 属性的讨论。
[BindRequired] 属性[BindRequired] attribute
只能应用于模型属性,不能应用于方法参数。如果无法对模型属性进行绑定,则会导致模型绑定添加模型状态错误。下面是一个示例:
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; }
[BindNever] 属性[BindNever] attribute
只能应用于模型属性,不能应用于方法参数。防止模型绑定设置模型的属性。下面是一个示例:
public class InstructorWithDictionary
{
[BindNever]
public int ID { get; set; }
[Bind] 属性[Bind] attribute
可应用于类或方法参数。指定模型绑定中应包含的模型属性。
在下面的示例中,当调用任意处理程序或操作方法时,只绑定 Instructor
模型的指定属性:
[Bind("LastName,FirstMidName,HireDate")]
public class Instructor
在下面的示例中,当调用 Instructor
方法时,只绑定 OnPost
模型的指定属性:
[HttpPost]
public IActionResult OnPost([Bind("LastName,FirstMidName,HireDate")] Instructor instructor)
[Bind]
属性可用于防止“创建”方案中的过多发布情况。由于排除的属性设置为 NULL 或默认值,而不是保持不变,因此它在编辑方案中无法很好地工作。为防止过多发布,建议使用视图模型,而不是使用 [Bind]
属性。有关详细信息,请参阅有关过多发布的安全性说明。
集合Collections
对于是简单类型集合的目标,模型绑定将查找 parameter_name 或 property_name 的匹配项。如果找不到匹配项,它将查找某种不含前缀的受支持的格式。例如:
- 假设要绑定的参数是名为
selectedCourses
的数组:
public IActionResult OnPost(int? id, int[] selectedCourses)
- 表单或查询字符串数据可以采用以下某种格式:
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
- 只有表单数据支持以下格式:
selectedCourses[]=1050&selectedCourses[]=2000
对于前面所有的示例格式,模型绑定将两个项的数组传递给
selectedCourses
参数:- selectedCourses[0]=1050
- selectedCourses[1]=2000
使用下标数字的数据格式 (… [0] … [1] …) 必须确保从零开始按顺序进行编号。如果下标编号中存在任何间隔,则间隔后的所有项都将被忽略。例如,如果下标是 0 和 2,而不是 0 和 1,则第二个项会被忽略。
字典Dictionaries
对于 Dictionary
目标,模型绑定会查找 parameter_name 或 property_name 的匹配项。如果找不到匹配项,它将查找某种不含前缀的受支持的格式。例如:
- 假设目标参数是名为
Dictionary<int, string>
的selectedCourses
:
public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
- 发布的表单或查询字符串数据可以类似于以下某一示例:
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
对于前面所有的示例格式,模型绑定将两个项的字典传递给
selectedCourses
参数:- selectedCourses["1050"]="Chemistry"
- selectedCourses["2000"]="Economics"
模型绑定路由数据和查询字符串的全球化行为Globalization behavior of model binding route data and query strings
ASP.NET Core 路由值提供程序和查询字符串值提供程序:
- 将值视为固定区域性。
- URL 的区域性应固定。
相反,来自窗体数据的值要进行区分区域性的转换。这是设计使然,目的是让 URL 可在各个区域设置中共享。
使 ASP.NET Core 路由值提供程序和查询字符串值提供程序进行区分区域性的转换:
- 继承自 IValueProviderFactory
- 从 QueryStringValueProviderFactory 或 RouteValueValueProviderFactory 复制代码
- 使用 CultureInfo.CurrentCulture 替换传递给值提供程序构造函数的区域性值
- 将 MVC 选项中的默认值提供程序工厂替换为新的工厂:
services.AddMvc(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
模型绑定可以处理某些特殊的数据类型。
IFormFile 和 IFormFileCollectionIFormFile and IFormFileCollection
HTTP 请求中包含的上传文件。还支持多个文件的 IEnumerable<IFormFile>
。
CancellationTokenCancellationToken
用于取消异步控制器中的活动。
FormCollectionFormCollection
用于从发布的表单数据中检索所有的值。
输入格式化程序Input formatters
请求正文中的数据可以是 JSON、XML 或其他某种格式。要分析此数据,模型绑定会使用配置为处理特定内容类型的输入格式化程序。默认情况下,ASP.NET Core 包括用于处理 JSON 数据的基于 JSON 的输入格式化程序。可以为其他内容类型添加其他格式化程序。
ASP.NET Core 基于 Consumes 属性来选择输入格式化程序。如果没有属性,它将使用 Content-Type 标头。
要使用内置 XML 输入格式化程序,请执行以下操作:
安装
Microsoft.AspNetCore.Mvc.Formatters.Xml
NuGet 包。在
Startup.ConfigureServices
中,调用 AddXmlSerializerFormatters 或 AddXmlDataContractSerializerFormatters。
services.AddMvc(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(System.Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
- 将
Consumes
属性应用于应在请求正文中使用 XML 的控制器类或操作方法。
[HttpPost]
[Consumes("application/xml")]
public ActionResult<Pet> Create(Pet pet)
有关更多信息,请参阅 XML 序列化简介。
从模型绑定中排除指定类型Exclude specified types from model binding
模型绑定和验证系统的行为由 ModelMetadata 驱动。可通过向 ModelMetadata
MvcOptions.ModelMetadataDetailsProviders 添加详细信息提供程序来自定义 。内置详细信息提供程序可用于禁用指定类型的模型绑定或验证。
要禁用指定类型的所有模型的模型绑定,请在 ExcludeBindingMetadataProvider 中添加 Startup.ConfigureServices
。例如,禁用对 System.Version
类型的所有模型的模型绑定:
services.AddMvc(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(System.Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
要禁用指定类型的属性的验证,请在 SuppressChildValidationMetadataProvider 中添加 Startup.ConfigureServices
。例如,禁用对 System.Guid
类型的属性的验证:
services.AddMvc(options =>
{
options.ValueProviderFactories.Add(new CookieValueProviderFactory());
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider(typeof(System.Version)));
options.ModelMetadataDetailsProviders.Add(
new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
自定义模型绑定器Custom model binders
通过编写自定义模型绑定器,并使用 [ModelBinder]
属性为给定目标选择该模型绑定器,可扩展模型绑定。详细了解自定义模型绑定。
手动模型绑定Manual model binding
可以使用 TryUpdateModelAsync 方法手动调用模型绑定。ControllerBase
和 PageModel
类上均定义了此方法。方法重载允许指定要使用的前缀和值提供程序。如果模型绑定失败,该方法返回 false
。下面是一个示例:
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();
[FromServices] 属性[FromServices] attribute
此属性的名称遵循指定数据源的模型绑定属性的模式。但这与绑定来自值提供程序的数据无关。它从依赖关系注入容器中获取类型的实例。其目的在于,在仅当调用特定方法时需要服务的情况下,提供构造函数注入的替代方法。