【问题标题】:ASP.NET Core 3.0 form ModelState always invalidASP.NET Core 3.0 表单 ModelState 总是无效
【发布时间】:2020-02-10 06:34:12
【问题描述】:

我正在将较旧的 ASP.NET 4.5 网站移植到 ASP.NET Core 3.0,并且在身份 Razor 页面及其工作方式方面遇到了一些问题。

例如,在应用程序的注册页面,如下图,表格是完整填写的,但是当点击按钮时,我在Register.cs文件的PostAsync()方法中打了断点,与Input 模型绑定,所有内容始终为 null,除了我在 HTML 中设置初始选定值的 Enum。

注意: 是的,我在代码文件中看到了输入。那不是问题。截图后我就去掉了。

    [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 &amp; 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 -->

更新:根据评论者的要求,这里也是请求负载。

【问题讨论】:

    标签: c# asp.net-core model-binding asp.net-core-3.0 modelstate


    【解决方案1】:

    经过几个小时的反复试验,简单的答案是 html 标记中的 name 属性必须是模型属性名称。

    我将其发布为答案,以便遇到此问题的任何其他人都可以在这里轻松找到解决方案。

    即这是导致每个属性失败的原因

     <!-- 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 一起使用,则您不能也放入自己的 name= 属性。

    【讨论】:

      【解决方案2】:

      正如您在回答中所指出的,这里的问题是由于您的代码覆盖了asp-for 设置的name 值。而且,通常,您确实希望保留该默认值。

      值得指出的是,严格没有必要遵守该命名约定。如果您遇到需要,可以使用一些变体。

      上下文无关绑定

      首先,绑定到复杂对象时动作参数或控制器属性的名称是可选的(reference)。因此,例如,您可以使用Input.LastName 的默认值(由asp-for 生成),或者您可以简单地使用LastName。这对于可重用的组件很有用(例如, 视图组件或部分视图)公开特定模型的标记,但可能不知道用于不同操作的参数的名称。当然,这也可能为歧义创造空间,因此我认为最好的做法是明确的,除非有令人信服的业务需求来为参数名称提供灵活性。

      自定义前缀[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 Core 默认值不同。

      【讨论】:

        猜你喜欢
        • 2021-11-09
        • 2017-05-30
        • 2021-07-10
        • 1970-01-01
        • 2021-07-26
        • 2020-02-19
        • 1970-01-01
        • 2020-04-18
        • 2021-08-30
        相关资源
        最近更新 更多