ASP.NET Core 3.0 窗体 ModelState 总是无效
ASP.NET Core 3.0 form ModelState always invalid
我正在将一个较旧的 ASP.NET 4.5 网站移植到 ASP.NET Core 3.0,并且 运行 遇到了身份 Razor 页面及其工作方式的一些问题。
例如,在应用程序的注册页面,如下所示,表单已完全填写,但是当单击按钮时,我在PostAsync()
方法中打了断点Register.cs
文件,与 Input
模型绑定,一切总是 null
,除了枚举,我在 HTML.
中设置了最初选择的值
Note: Yes I see the typing in the code file. That isn't the issue. I got rid of that after I took the screenshot.
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[Required]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[Display(Name = "Gender")]
public Gender Gender { get; set; }
[Required]
[Phone]
[Display(Name = "MobilePhone")]
public string MobilePhone { get; set; }
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
在我的 Startup
文件中:
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
});
有没有想过为什么 ModelState
由于表单值总是为空而总是无效?
这里是 HTML 的形式:
<form asp-route-returnUrl="@Model.ReturnUrl"
class="form-horizontal push-50-t push-50 form-register"
method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<!-- form entry - first name -->
<div class="form-group">
<div class="col-xs-12">
<div class="form-material form-material-success">
<label asp-for="Input.FirstName"></label>
<input asp-for="Input.FirstName" type="text"
id="register-firstname" name="register-firstname"
class="form-control" placeholder="Enter your first name" />
<span asp-validation-for="Input.FirstName" class="text-danger"></span>
</div>
</div>
</div>
<!-- form entry - last name -->
<div class="form-group">
<div class="col-xs-12">
<div class="form-material form-material-success">
<label asp-for="Input.LastName"></label>
<input asp-for="Input.LastName" type="text"
id="register-lastname" name="register-lastname"
class="form-control" placeholder="Enter your last name" />
<span asp-validation-for="Input.LastName" class="text-danger"></span>
</div>
</div>
</div>
<!-- form entry - gender -->
<div class="form-group">
<div class="col-xs-12">
<div class="form-material form-material-success">
<label asp-for="Input.Gender"></label>
<select class="form-control" asp-for="Input.Gender" id="register-gender" name="register-gender"
placeholder="Please enter Male/Female">
<option selected="selected" value="@Gender.Male">Male</option>
<option value="@Gender.Female">Female</option>
</select>
<span asp-validation-for="Input.Gender" class="text-danger"></span>
</div>
</div>
</div>
<!-- form entry - mobile phone -->
<div class="form-group">
<div class="col-xs-12">
<div class="form-material form-material-success">
<label asp-for="Input.MobilePhone"></label>
<input asp-for="Input.MobilePhone" type="tel"
id="register-mobilephone" name="register-mobilephone"
class="form-control" placeholder="Provide your mobile phone number" />
<span asp-validation-for="Input.MobilePhone" class="text-danger"></span>
</div>
</div>
</div>
<!-- form entry - email -->
<div class="form-group">
<div class="col-xs-12">
<div class="form-material form-material-success">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email"
type="email" id="register-email" name="register-email"
class="form-control" placeholder="Your email address" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
</div>
</div>
<!-- form entry - password -->
<div class="form-group">
<div class="col-xs-12">
<div class="form-material form-material-success">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password"
type="password" id="register-password" name="register-password"
class="form-control" placeholder="Choose a strong password" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
</div>
</div>
<!-- form entry - confirm password -->
<div class="form-group">
<div class="col-xs-12">
<div class="form-material form-material-success">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword"
type="password" id="register-password2" name="register-password2"
class="form-control" placeholder="Confirm your password" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
</div>
</div>
<div class="form-group">
<div class="col-xs-7 col-sm-8">
<label class="css-input switch switch-sm switch-success">
<input type="checkbox" id="register-terms" name="register-terms"><span></span> I agree with terms & conditions
</label>
</div>
<div class="col-xs-5 col-sm-4">
<div class="font-s13 text-right push-5-t">
<a href="#" data-toggle="modal" data-target="#modal-terms">View Terms</a>
</div>
</div>
</div>
<div class="form-group">
<div class="col-xs-12 col-sm-6 col-sm-offset-3">
<button id="register-button" class="btn btn-sm btn-block btn-success" type="submit">Create Account</button>
</div>
</div>
</form>
<!-- END Register Form -->
更新:应评论者的要求,这里也是请求负载。
经过几个小时的艰苦试验和错误,简单的答案是 html 标记中的名称属性必须是模型 属性 名称。
我将此作为答案发布,以便其他遇到此问题的人可以轻松地在此处找到解决方案。
即这就是导致每个 属性
失败的原因
<!-- form entry - email -->
<div class="form-group">
<div class="col-xs-12">
<div class="form-material form-material-success">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" type="email" id="register-email" **name="register-email"**
class="form-control" placeholder="Your email address" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
</div>
</div>
输入标签助手中的 asp-for 属性助手是创建 id 和 name 属性的原因,因此如果您将标签助手与 asp-for 一起使用,您也不能输入你自己的名字=属性。
正如您在回答中指出的那样,这里的问题是由于您的代码覆盖了 asp-for
设置的 name
值。而且,一般来说,您确实希望保留该默认值。
然而,值得指出的是,严格来说 并非必须遵守该命名约定。如果您需要,可以使用一些变体。
上下文无关绑定
首先,当绑定到复杂对象时,动作参数或控制器的名称 属性 是可选的 (reference)。因此,例如,您可以使用由 asp-for
生成的默认值 Input.LastName
,或者您可以简单地使用 LastName
。这对于可重用组件很有用(例如,
view components or partial views),它公开了特定模型的标记,但可能不知道用于不同操作的参数的名称。当然,这也可能会产生歧义,所以我认为最好的做法是明确,除非有强制性的业务要求在参数名称上提供灵活性。
带有 [Bind(Prefix="")]
的自定义前缀
其次,您可以通过在操作参数上使用 [Bind(Prefix="MyInput")]
来更改绑定模型前缀的名称——或者,在您的情况下,使用 [BindProperty(Name="MyInput")]
(reference)。到那时,您不妨更改参数或 属性 名称。但是,当您的标记命名约定与您在 C# 中的首选参数名称不匹配时,这可以提供一些灵活性。
自定义绑定 IModelBinder
最后,您还可以创建一个派生自 IModelBinder
的自定义模型绑定器,以开发自定义、约定如何将表单名称映射到操作参数和绑定模型属性 (reference)。如果您需要保持与旧客户端或命名约定的兼容性,或者出于其他原因更喜欢非标准命名约定,这将很有用。自定义模型绑定器通常很复杂,但为了支持基本约定,例如将 snake-case
转换为 PascalCase
甚至 snake-case
为 object.Notation
,它们不需要太复杂。
结论
这有点令人费解。对于大多数用户和案例,使用 asp-for
强制执行的开箱即用约定是在简单性、可预测性和可维护性方面的最佳方法。但请注意它不是 only 选项,因为在许多情况下您可能希望 HTML name
属性有所不同来自预期的 ASP.NET 核心默认值。
我正在将一个较旧的 ASP.NET 4.5 网站移植到 ASP.NET Core 3.0,并且 运行 遇到了身份 Razor 页面及其工作方式的一些问题。
例如,在应用程序的注册页面,如下所示,表单已完全填写,但是当单击按钮时,我在PostAsync()
方法中打了断点Register.cs
文件,与 Input
模型绑定,一切总是 null
,除了枚举,我在 HTML.
Note: Yes I see the typing in the code file. That isn't the issue. I got rid of that after I took the screenshot.
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[Required]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[Display(Name = "Gender")]
public Gender Gender { get; set; }
[Required]
[Phone]
[Display(Name = "MobilePhone")]
public string MobilePhone { get; set; }
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
在我的 Startup
文件中:
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
});
有没有想过为什么 ModelState
由于表单值总是为空而总是无效?
这里是 HTML 的形式:
<form asp-route-returnUrl="@Model.ReturnUrl"
class="form-horizontal push-50-t push-50 form-register"
method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<!-- form entry - first name -->
<div class="form-group">
<div class="col-xs-12">
<div class="form-material form-material-success">
<label asp-for="Input.FirstName"></label>
<input asp-for="Input.FirstName" type="text"
id="register-firstname" name="register-firstname"
class="form-control" placeholder="Enter your first name" />
<span asp-validation-for="Input.FirstName" class="text-danger"></span>
</div>
</div>
</div>
<!-- form entry - last name -->
<div class="form-group">
<div class="col-xs-12">
<div class="form-material form-material-success">
<label asp-for="Input.LastName"></label>
<input asp-for="Input.LastName" type="text"
id="register-lastname" name="register-lastname"
class="form-control" placeholder="Enter your last name" />
<span asp-validation-for="Input.LastName" class="text-danger"></span>
</div>
</div>
</div>
<!-- form entry - gender -->
<div class="form-group">
<div class="col-xs-12">
<div class="form-material form-material-success">
<label asp-for="Input.Gender"></label>
<select class="form-control" asp-for="Input.Gender" id="register-gender" name="register-gender"
placeholder="Please enter Male/Female">
<option selected="selected" value="@Gender.Male">Male</option>
<option value="@Gender.Female">Female</option>
</select>
<span asp-validation-for="Input.Gender" class="text-danger"></span>
</div>
</div>
</div>
<!-- form entry - mobile phone -->
<div class="form-group">
<div class="col-xs-12">
<div class="form-material form-material-success">
<label asp-for="Input.MobilePhone"></label>
<input asp-for="Input.MobilePhone" type="tel"
id="register-mobilephone" name="register-mobilephone"
class="form-control" placeholder="Provide your mobile phone number" />
<span asp-validation-for="Input.MobilePhone" class="text-danger"></span>
</div>
</div>
</div>
<!-- form entry - email -->
<div class="form-group">
<div class="col-xs-12">
<div class="form-material form-material-success">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email"
type="email" id="register-email" name="register-email"
class="form-control" placeholder="Your email address" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
</div>
</div>
<!-- form entry - password -->
<div class="form-group">
<div class="col-xs-12">
<div class="form-material form-material-success">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password"
type="password" id="register-password" name="register-password"
class="form-control" placeholder="Choose a strong password" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
</div>
</div>
<!-- form entry - confirm password -->
<div class="form-group">
<div class="col-xs-12">
<div class="form-material form-material-success">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword"
type="password" id="register-password2" name="register-password2"
class="form-control" placeholder="Confirm your password" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
</div>
</div>
<div class="form-group">
<div class="col-xs-7 col-sm-8">
<label class="css-input switch switch-sm switch-success">
<input type="checkbox" id="register-terms" name="register-terms"><span></span> I agree with terms & conditions
</label>
</div>
<div class="col-xs-5 col-sm-4">
<div class="font-s13 text-right push-5-t">
<a href="#" data-toggle="modal" data-target="#modal-terms">View Terms</a>
</div>
</div>
</div>
<div class="form-group">
<div class="col-xs-12 col-sm-6 col-sm-offset-3">
<button id="register-button" class="btn btn-sm btn-block btn-success" type="submit">Create Account</button>
</div>
</div>
</form>
<!-- END Register Form -->
更新:应评论者的要求,这里也是请求负载。
经过几个小时的艰苦试验和错误,简单的答案是 html 标记中的名称属性必须是模型 属性 名称。
我将此作为答案发布,以便其他遇到此问题的人可以轻松地在此处找到解决方案。
即这就是导致每个 属性
失败的原因 <!-- form entry - email -->
<div class="form-group">
<div class="col-xs-12">
<div class="form-material form-material-success">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" type="email" id="register-email" **name="register-email"**
class="form-control" placeholder="Your email address" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
</div>
</div>
输入标签助手中的 asp-for 属性助手是创建 id 和 name 属性的原因,因此如果您将标签助手与 asp-for 一起使用,您也不能输入你自己的名字=属性。
正如您在回答中指出的那样,这里的问题是由于您的代码覆盖了 asp-for
设置的 name
值。而且,一般来说,您确实希望保留该默认值。
然而,值得指出的是,严格来说 并非必须遵守该命名约定。如果您需要,可以使用一些变体。
上下文无关绑定
首先,当绑定到复杂对象时,动作参数或控制器的名称 属性 是可选的 (reference)。因此,例如,您可以使用由 asp-for
生成的默认值 Input.LastName
,或者您可以简单地使用 LastName
。这对于可重用组件很有用(例如,
view components or partial views),它公开了特定模型的标记,但可能不知道用于不同操作的参数的名称。当然,这也可能会产生歧义,所以我认为最好的做法是明确,除非有强制性的业务要求在参数名称上提供灵活性。
带有 [Bind(Prefix="")]
的自定义前缀
其次,您可以通过在操作参数上使用 [Bind(Prefix="MyInput")]
来更改绑定模型前缀的名称——或者,在您的情况下,使用 [BindProperty(Name="MyInput")]
(reference)。到那时,您不妨更改参数或 属性 名称。但是,当您的标记命名约定与您在 C# 中的首选参数名称不匹配时,这可以提供一些灵活性。
自定义绑定 IModelBinder
最后,您还可以创建一个派生自 IModelBinder
的自定义模型绑定器,以开发自定义、约定如何将表单名称映射到操作参数和绑定模型属性 (reference)。如果您需要保持与旧客户端或命名约定的兼容性,或者出于其他原因更喜欢非标准命名约定,这将很有用。自定义模型绑定器通常很复杂,但为了支持基本约定,例如将 snake-case
转换为 PascalCase
甚至 snake-case
为 object.Notation
,它们不需要太复杂。
结论
这有点令人费解。对于大多数用户和案例,使用 asp-for
强制执行的开箱即用约定是在简单性、可预测性和可维护性方面的最佳方法。但请注意它不是 only 选项,因为在许多情况下您可能希望 HTML name
属性有所不同来自预期的 ASP.NET 核心默认值。