Search code examples
c#asp.net-mvcunobtrusive-validation

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)

Solution

  • 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");
      }