简体   繁体   中英

Can't Fix ASP.NET MVC Validation

I've been trying to fix the validation on my form for a while now, but can't get it to work properly. The validation works fine, unless I enter a valid email address. If I do, then it just skips all other validation for some reason.

Also, would appreciate advice about if I'm doing it right with the split of everything in the controller. Should there be 2 actions (1 for GET to just load the empty form and 1 for POST when the user submits it)? Am I doing something wrong there?

EDIT: Correction: If I enter a valid email address, the form submits fine and ignores the other validation. If I don't have a valid email address, the validation checks everything.

Here's my model class:

public class User
{
    public int ID { get; set; }

    [DisplayName("Name")]
    [Required(ErrorMessage = "Name is required")]
    public string Name { get; set; }

    [DisplayName("Email")]
    [DataType(DataType.EmailAddress)]
    [Required(ErrorMessage = "Email is required")]
    [RegularExpression(
        @"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" +
            @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$",
        ErrorMessage = "Invalid email")]
    public string Email { get; set; }

    [DisplayName("Password")]
    [DataType(DataType.Password)]
    [Required(ErrorMessage = "Password required")]
    [MinLength(6, ErrorMessage = "Min 6 characters")]
    public string Password { get; set; }
}

Here is my controller:

    public ActionResult Register()
    {
        ViewBag.LoggedIn = false;

        return View();
    }

    [HttpPost]
    public ActionResult Register(User user)
    {
        ViewBag.LoggedIn = false;

        user.Password = PasswordHash.CreateHash(user.Password);

        using (var context = new FoundationContext())
        {
            // if user exists
            if (context.Users.FirstOrDefault(n => n.Email == user.Email) != null) return Login(true);

            context.Users.Add(user);
            context.SaveChanges();
        }

        return View("~/Views/Home.Index.cshtml");
    }

And here is my form:

    @using (Html.BeginForm())
    {
        @Html.AntiForgeryToken()

        <div class="form-horizontal">
            @Html.ValidationSummary(true, "", new { @class = "text-danger" })
            <div class="form-group">
                @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" })
                </div>
            </div>

            <div class="form-group">
                @Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" })
                </div>
            </div>

            <div class="form-group">
                @Html.LabelFor(model => model.Password, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.Password, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.Password, "", new { @class = "text-danger" })
                </div>
            </div>

            <div class="form-group">
                <div class="col-md-10 text-center">
                    <input type="submit" value="Register" class="btn btn-primary" />
                </div>
            </div>
        </div>
    }

And of course bottom of the body includes:

@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryval")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)

Use Modelstate.IsValid . It tells you if any model errors have been added to ModelState.

public ActionResult Register(User user)
{
    if (!ModelState.IsValid) 
    {
       return View();
    }

    //your code
}

In order to check if posted model is valid you need to check ModelState.IsValid in the post action (see in the example below).

Also, would appreciate advice about if I'm doing it right with the split of everything in the controller. Should there be 2 actions (1 for GET to just load the empty form and 1 for POST when the user submits it)? Am I doing something wrong there?

The approach is right but the way you do it is not.

Assuming that you have a Register.cshtml view (not a partial view): Since a view has a model, you should always provide it. So your get method should look like this:

public ActionResult Register()
{
    ViewBag.LoggedIn = false;
    var model =  new User();
    return View(model);
}

For the post method there are 2 approaches:

  1. POST-REDIRECT-GET pattern (PRG) - a little bit more complex(but in my opinion it is more correct way of doing this). Post method instead of returning a view should return a redirect result both in case of error and in case of success. For handling invalid model state you can use some approaches that are described here

  2. Your way

     [HttpPost] public ActionResult Register(User user) { ViewBag.LoggedIn = false; if(!this.ModelState.IsValid) { return View(user); //this will render a view with preserved invalid model state so all the values with the corresponding error messages will be shown. } user.Password = PasswordHash.CreateHash(user.Password); using (var context = new FoundationContext()) { // if user exists if (context.Users.FirstOrDefault(n => n.Email == user.Email) != null) return Login(true); context.Users.Add(user); context.SaveChanges(); } return RedirectToAction("Index", "Home"); } 

First of all, your structure seems correct, to split the actions to have a differentiated Get and Post actions is correct and a good way to do it.

I am not able to test, but I think the issue is that you don't handle the validation correctly in your controller; you should check the the ModelState to be valid.

    [HttpPost]
    public ActionResult Register(User user)
    {
        if (ModelState.IsValid)
        {
            ViewBag.LoggedIn = false;

            user.Password = PasswordHash.CreateHash(user.Password);

            using (var context = new FoundationContext())
            {
                // if user exists
                if (context.Users.FirstOrDefault(n => n.Email == user.Email) != null) return Login(true);

                context.Users.Add(user);
                context.SaveChanges();
            }

            return View("~/Views/Home.Index.cshtml");
        }
        return View(user);
    }

This way if there is an error in the Model (based on the Rules you put with DataAnnotations).

Also I am not sure that your DataAnnotations Attributes are the correct one: Some differ from those that are used in this article:

https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/validation

Hope this helps.

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