简体   繁体   中英

ASP.NET Core 3.0 form ModelState always invalid

I am in the process of porting an older ASP.NET 4.5 website to ASP.NET Core 3.0, and am running into some issues with the identity Razor pages and how they work.

For example, on the registration page of the application, as shown below, the form is entirely filled out, but when the button is clicked and I hit the breakpoint in the PostAsync() method of the Register.cs file, with the Input model bound, everything is always null , except for the Enum which I set the initially selected value in the 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; }
    }

And in my Startup file:

 services.AddMvc()
            .SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
            .ConfigureApiBehaviorOptions(options =>
            {
                options.SuppressConsumesConstraintForFormFileParameters = true;
                options.SuppressInferBindingSourcesForParameters = true;
                options.SuppressModelStateInvalidFilter = true;
                options.SuppressMapClientErrors = true;

            });

Any thoughts on why the ModelState is always invalid due the form values always being null?

Here is the HTML of the form:

<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 -->

Update: As Requested by a commenter, here is the request payload as well.

在此处输入图像描述

After a few hours of laborious trail and error, the simple answer is the name attribute in the html markup HAS TO BE the model property name.

I am posting this as the answer so that any others who experience this issue can easily find the solution here.

ie this is what was causing the failure on each property

 <!-- 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>

The asp-for attribute helper within the input tag helper is what creates the id and name attributes so if you are using tag helpers with asp-for, you CANNOT also have put in your own name= attribute.

As you note in your answer, the problem here was due to your code overriding the name value set by asp-for . And, generally, you'll indeed want to keep that default.

It's worth pointing out, however, that it isn't strictly necessary to adhere to that naming convention. There are a few variations available, should you encounter the need.

Context agnostic binding

First, the name of the action parameter or controller property when binding to complex objects is optional ( reference ). So, for example, you could use the default of Input.LastName —as generated by asp-for —or you could simply use LastName . This can be useful for reusable components (eg, view components or partial views) which expose the markup for a particular model, but may not know the name of the parameter used on different actions. Of course, that potentially creates room for ambiguity, as well, so I consider it a best practice to be explicit unless there's a compelling business requirement to provide flexibility on the parameter name.

Custom prefix with [Bind(Prefix="")]

Second, you can change the name of the binding model prefix by using [Bind(Prefix="MyInput")] on an action parameter—or, in your case, [BindProperty(Name="MyInput")] ( reference ). At that point, you might as well change the parameter or property name. But this can provide some flexibility when your markup naming conventions don't match your preferred parameter names in C#.

Custom binding with an IModelBinder

Finally, you can also create a custom model binder which derives from IModelBinder to develop custom, conventions for how to map form names to action parameters and binding model properties ( reference ). This is useful if you need to maintain compatibility with legacy clients or naming conventions, or have other reasons for preferring a non-standard naming convention. Custom model binders are often complex, but to support basic conventions like converting snake-case to PascalCase or even snake-case to object.Notation , they needn't be too involved.

Conclusion

This is a bit down the rabbit hole. For most users and cases, using the out-of-the-box conventions enforced by asp-for is the best approach in terms of simplicity, predictability, and maintainability. But it's useful to be aware that it isn't the only option, as there are a number of scenarios where you might want your HTML name attributes to vary from the expected ASP.NET Core defaults.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM