Search code examples
c#asp.net-mvcvalidationclient-side-validation

Validate MVC field at client side


I have problem validating one field at client side, Here is my code:

Model:

 public class Registration
    {
        public int Id { get; set; }
        [IsUserExistAttribute(ErrorMessage = "Email already exists!")]
        [EmailAddress(ErrorMessage = "Please enter valid Email")]
        [Required(ErrorMessage = "Email is required")]
        public string Email
        {
            get; set;

        }
        [MustBeTrue(ErrorMessage = "Please Accept the Terms & Conditions")]
        public bool TermsAndConditions { get; set; }


    }

ValidationAttribute:

public class IsUserExistAttribute : ValidationAttribute,IClientValidatable
    {
        public override bool IsValid(object email)
        {
            DemoDbContext demoContext=new DemoDbContext();
            string emailString = Convert.ToString(email);
            if (demoContext.Registrations.Any(a=>a.Email.Contains(emailString)))
            {
                return false;
            }
            return true;
        }

        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            yield return new ModelClientValidationRule
            {
                ErrorMessage = this.ErrorMessage,
                ValidationType = "emailvalidate"
            };
        }
    }

View:

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

    <div class="form-horizontal">
        <h4>Registration</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <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.TermsAndConditions, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                <div class="checkbox">
                    @Html.EditorFor(model => model.TermsAndConditions)
                    @Html.ValidationMessageFor(model => model.TermsAndConditions, "", new { @class = "text-danger" })
                </div>
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

Everthing upto this works fine, But my question is This IsUserExistAttribute doesn't validate it on client side, on server side it validates and give the message like Email already exists!

But, I want it to validate client side too. So, what is the good way to achieve this? Other approach is also welcome :)

I tried something like this seems no success:

 $.validator.addMethod("emailvalidate", function (value, element) {
            var is_valid = false;
            $.ajax({
                // as before...
                async: false,
                success: function (data) {
                    is_valid = data === 'True';
                }
            });
            return is_valid;
        }, "Username not available.");

Solution

  • MVC already comes with a RemoteAttribute for client side validation (refer How to: Implement Remote Validation in ASP.NET MVC) so there is little point in you trying to reinvent the wheel. (As a side note, your currently missing the $.validator.unobtrusive.adapters.add() method necessary to add the rules for client side validation).

    But there are a few issues with your current validation attribute. A validation attribute should not be accessing the database and you have made it impossible to unit test your code. And you will be repeating the code in the attribute again in the controller method that is called by the ajax function, violating the DRY principal and making it harder to maintain your code.

    Instead, create a private method in your controller (or a public method in a separate service) for you database code, for example

    private bool IsEmailUnique(string email)
    {
        return !db.Registrations.Any(a => a.Email.Contains(email)))
    }
    

    Then for client side validation, decorate you property with the RemoteAttribute

    [Remote("IsEmailValid", "ControllerName", ErorMessage = "...")]
    public string Email { get; set; }
    

    and add the controller method

    public JsonResult IsEmailValid(string email)
    {
        bool isValid = IsEmailUnique(email);
        return Json(isValid, JsonRequestBehavior.AllowGet);
    }
    

    and then if you also want to have server side validation when you submit the form, call the same method and if not valid, add a ModelStateError

    public ActionResult Register(Registration model)
    {
        bool isValid = IsEmailUnique(model.Email);
        if (!isValid)
        {
            ModelState.AddModelError("Email", "...");
        }
        if (!ModelState.IsValid)
        {
            return View(model);
        }
        // save and redirect
    }