Click here to Skip to main content
15,888,113 members
Articles / Programming Languages / Javascript

Building Client (JavaScript) Custom Validation in ASP.NET MVC 4 using jQuery

Rate me:
Please Sign up or sign in to vote.
4.93/5 (10 votes)
1 Jul 2013CPOL8 min read 98.3K   29   4
Building Client (JavaScript) Custom Validation in ASP.NET MVC 4 using jQuery.

Introduction

I was recently asked by some students of mine how exactly is client custom validation done in ASP.NET MVC (4). I did this once before unobtrusive validation and jQuery in ASP.NET MVC 2.0 but then I lost contact with the implementation details.

In ASP.NET MVC 4 (this started in MVC 3) there is jQuery unobtrusive validation that works hand-in-hand with Data Annotations (a set of validation attributes that can decorate properties or even (view)model classes). I just remembered that you need to create a ValidationAttribute subclass and also implement IClientValidatable on it. Also you must decorate a property of the (View)Model with this attribute.

On the client side you need to write JavaScript code that provides a validation adapter and a validation function.

Let’s suppose we’d want to create an URL shortening service and on the “Add URL” page we’d have three fields:

  • Original URL (textbox)
  • Use custom slug (checkbox)
  • Custom slug (textbox)

The “Original URL” textbox would be mandatory and the input format should be of a fully-qualified URL (for example “http://blog.andrei.rinea.ro” :P )

The custom slug textbox would be mandatory only if the “Use custom slug” checkbox would be checked. This is the tough part since validating this field requires knowledge of another field’s value (the checkbox in this case). And by tough I mean having to write actual code because there is no out-of-the-box validator for this (not on the server-side nor on the client-side).

This small tutorial will assume that you have knowledge of the ASP.NET MVC platform and C#. I will not go into detail on matters such as what is the MVC pattern, what is the controller, view etc.

Implementation – server side

Let’s start by creating a new ASP.NET MVC 4 web project (select “Internet site” template). We’ll create a UrlController controller with an action called Add :

C#
using System.Web.Mvc;

namespace MVC4_jQuery_Unobtrusive_Custom_Validation.Controllers
{
    public class UrlController : Controller
    {
        [HttpGet]
        public ActionResult Add()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Add(AddUrlViewModel userInput)
        {
            if (ModelState.IsValid)
            {
                // store the data
                // ...
                return RedirectToAction("Index", "Home");
            }
            return View();
        }
    }
}

I think the controller code is pretty straight-forward. So now let’s create the view and the viewmodel :

C#
@model MVC4_jQuery_Unobtrusive_Custom_Validation.ViewModels.AddUrlViewModel

<h2>Add a new URL shortening</h2>
@using (Html.BeginForm())
{
    <table>
        <tr>
            <td>Original URL : </td>
            <td>
                @Html.TextBoxFor(m => m.OriginalUrl)
                @Html.ValidationMessageFor(m => m.OriginalUrl)
            </td>
        </tr>

        <tr>
            <td>@Html.LabelFor(m => m.UseCustomSlug, "Use a custom slug")</td>
            <td>@Html.CheckBoxFor(m => m.UseCustomSlug)</td>
        </tr>

        <tr>
            <td>Custom slug : </td>
            <td>
                @Html.TextBoxFor(m => m.CustomSlug)
                @Html.ValidationMessageFor(m => m.CustomSlug)
            </td>
        </tr>
    </table>

    <input type="submit" value="Save"/>
}
C#
using System.ComponentModel.DataAnnotations;

namespace MVC4_jQuery_Unobtrusive_Custom_Validation.ViewModels
{
    public class AddUrlViewModel
    {
        [Required]
        [Url]
        public string OriginalUrl { get; set; }

        public bool UseCustomSlug { get; set; }

        public string CustomSlug { get; set; }
    }
}

We’ll need to create a validation attribute which will be used in decorating the “CustomSlug” property of the viewmodel. We’ll create another folder, called ‘Validation’ and a new class that we’ll call ‘RequiredIfTrueAttribute’:

C#
using System;
using System.ComponentModel.DataAnnotations;

namespace MVC4_jQuery_Unobtrusive_Custom_Validation.Validation
{
    public class RequiredIfTrueAttribute : ValidationAttribute
    {
        public string BooleanPropertyName { get; set; }

        protected override ValidationResult IsValid(object value, 
                  ValidationContext validationContext)
        {
            if (GetValue<bool>(validationContext.ObjectInstance, BooleanPropertyName))
            {
                return new RequiredAttribute().IsValid(value) ?
                    ValidationResult.Success :
                    new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
            }
            return ValidationResult.Success;
        }

        private static T GetValue<T>(object objectInstance, string propertyName)
        {
            if (objectInstance == null) throw new ArgumentNullException("objectInstance");
            if (string.IsNullOrWhiteSpace(propertyName))
                throw new ArgumentNullException("propertyName");

            var propertyInfo = objectInstance.GetType().GetProperty(propertyName);
            return (T)propertyInfo.GetValue(objectInstance);
        }
    }
}

As we can see we made use of reflection in order to get the boolean property’s value. Since we can only specify the property as a string, we are stuck with using reflection. Don’t worry about the performance impact, it’s minimal.

In case the property is set to true we delegate the validation logic to a RequiredAttribute instance (I chose in this case aggregation over inheritance, which is the most flexible approach – inheriting would have meant deriving from the RequiredAttribute and overriding the IsValid method and then using the base.IsValid, etc.)

Let’s run the site and see how things go. Being lazy I haven’t included a link in the header of the page so we’ll just ‘hack’ the address to point to http://localhost[:some-port]/Url/Add.

Image 2

If we try to submit the form as it is :

Image 3

As we can see the custom slug is not set as invalid since we did not opt in for a custom slug by checking the “Use a custom slug” checkbox. If we do check this checkbox and retry submitting the form we’ll get this :

Image 4

Implementation – client side

Well this is fine, that is, the server-side validation. If you ran the sample you might have noticed that a full post is made for the validation errors to be displayed on the page. It works as we intended. Now it’s time to do the same for the client-side. In the case of the ‘Original URL’ field things are handled out-of-the-box. For our new custom validation attribute we’ll need to write some JavaScript using jQuery.

First we’ll enable the client side validation by including the jQuery validation scripts at the view level (“at the top of the file, right beneath the @model directive) :

XML
@section scripts
{
    @Scripts.Render("~/bundles/jqueryval")
}

Although the unobtrusive client-side validation is enabled at the web.config level :

XML
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key="webpages:Version" value="2.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="PreserveLoginUrl" value="true" />

    <!-- these two enables the validation -->
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />

  </appSettings>
  <!-- ... -->
<configuration>

Well, running the app now, yields basically the same results but at least for the ‘Original URL’ field the validation is done at the client-side first (never ever rely only on the client-side validation and not doing the same on the server! Javascript can be turned off or a malicious user could bypass it one way or the other). If we do check the checkbox and hit ‘Save’ then only the server-side validation is being run. Let’s add some client-side validation for this too.

Next we’ll have to implement IClientValidatable on the RequiredIfTrueAttribute class. This means adding a ‘GetClientValidationRules’ method to the class which will provide the metadata for the client-side script to run with :

C#
public IEnumerable<ModelClientValidationRule> 
  GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
    var modelClientValidationRule = new ModelClientValidationRule
    {
        ValidationType = "requirediftrue",
        ErrorMessage = FormatErrorMessage(metadata.DisplayName)
    };
    modelClientValidationRule.ValidationParameters.Add("boolprop", BooleanPropertyName);
    yield return modelClientValidationRule;
}

In case you haven’t stumbled upon the yield keyword before have a look on its MSDN page. Basically it creates an inner class that is an enumerator and returns an instance of it, which will enumerate all the values you specify, in this case one value, that instance of the ModelClientValidationRule.

We should take note of two things at this step :

  1. the validation type property allows only lowered case letters because of a limitation in the HTML 5 specifications, this will be rendered as an attribute on the HTML input element
  2. We should provide the error message value as a result of the FormatErrorMessage method call and not provide the ErrorMessage property’s value because in this (current) way we give the validation rendering mechanism a chance to include the property name as part of the validation message. In our case we haven’t included “{0}” in the error message at the attribute decoration level but we could have done it.

This is not all but let’s run the site now just to inspect the html page source code. Notice data-val-requirediftrue and data-val-requirediftrue-boolprop on line 59 :

Image 5

These two new attributes hold the error message for this rule and the validation parameter. Now we will implement JavaScript code to make use of these attributes. We will add a new JavaScript file in the Scripts folder, file called ‘RequiredIfTrueValidation.js’. We will also included this file in the view :

XML
@section scripts
{
    @Scripts.Render("~/bundles/jqueryval")
    @Scripts.Render("~/Scripts/RequiredIfTrueValidation.js")
}

… and the JavaScript file contents :

JavaScript
jQuery.validator.addMethod('requirediftrue', function (value, element, params) {
    var checkboxId = $(element).attr('data-val-requirediftrue-boolprop');
    var checkboxValue = $('#' + checkboxId).first().is(":checked");
    return !checkboxValue || value;
}, '');

jQuery.validator.unobtrusive.adapters.add('requirediftrue', {}, function (options) {
    options.rules['requirediftrue'] = true;
    options.messages['requirediftrue'] = options.message;
});

JavaScript analysis

Let’s have a deeper look on the JavaScript file. In the first chunk of code there is the validation function and in the second chunk we wire up an ‘adaptor’ for the jQuery unobtrusive validation engine.

Line 8 adds the rule and sets its parameter to the ‘true’ value. This true value is useless in our case (it will be passed as the ‘params’ parameter value on line 1 later) but the line is required in order for the rule to be added to the rules collection.

Line 9 sets the error message for this rule (in case it’s going to be broken).

Line 2 gets the checkbox id from the validated textbox. Notice the attribute name that I was telling you earlier. This validating function (defined on line 1, inline) will be called by the validation engine with the validated textbox value as the ‘value’ parameter value, the textbox instance itself as the ‘element’ parameter value and the value from line 8 as the ‘params’ parameter value. Now, after we get the checkbox id, on line 2, we proceed on line 3 to get the checkbox element and read its checked state. The “first()” function call is necessary because jQuery will always return an array regardless if you use a (unique) id selector – which in fact can only result in one or zero element(s).

Finally on line 4 we return the validation result (true meaning ‘is valid’ and false otherwise). We decide this value either by the fact that the checkbox is not checked (“!checkboxValue”) or, the checkbox is checked and the ‘value’ – that is the text in the textbox – is not empty (empty string is evaluated as ‘false’ and non-empty, non-null string is evaluated as ‘true’ in JavaScript).

Let’s run the site and find out things run as they should. (no screenshot necessary for this, I can’t show how the server is not called ;) – maybe a video but for now I feel lazy). You can place a breakpoint at the action level and see how the server is not called.

Finally let’s add a finishing touch, by toggling the display of the ‘custom slug’ field through the checkbox :

XML
@model MVC4_jQuery_Unobtrusive_Custom_Validation.ViewModels.AddUrlViewModel

@section scripts
{
    @Scripts.Render("~/bundles/jqueryval")
    @Scripts.Render("~/Scripts/RequiredIfTrueValidation.js")

    <script type="text/javascript">
        $('#customSlugRow').toggle($($(
           '#@Html.IdFor(m => m.UseCustomSlug)')).is(':checked'));
        $('#@Html.IdFor(m => m.UseCustomSlug)').click(function () {
            $('#customSlugRow').toggle(this.checked);
        });
    </script>
}

<h2>Add a new URL shortening</h2>
@using (Html.BeginForm())
{
    <table>
        <tr>
            <td>Original URL : </td>
            <td>
                @Html.TextBoxFor(m => m.OriginalUrl)
                @Html.ValidationMessageFor(m => m.OriginalUrl)
            </td>
        </tr>

        <tr>
            <td>@Html.LabelFor(m => m.UseCustomSlug, "Use a custom slug")</td>
            <td>@Html.CheckBoxFor(m => m.UseCustomSlug)</td>
        </tr>

        <tr id="customSlugRow">
            <td>Custom slug : </td>
            <td>
                @Html.TextBoxFor(m => m.CustomSlug)
                @Html.ValidationMessageFor(m => m.CustomSlug)
            </td>
        </tr>
    </table>

    <input type="submit" value="Save" />
}

Let’s start with line 10. This tells jQuery to get the checkbox and attach an event handler for the click event. This handler (line 11) will toggle the visibility of the ‘customSlugRow’ table row (notice I’ve added an id to the tr element on line 33) based on the checked state of the checkbox. In jQuery the ‘this’ reference will point to the element that has the event handler, in this case the checkbox.

Finally on line 9 we set the initial visibility of the row based on the value of the checkbox. Don’t assume that this will always be ‘unchecked’. If the viewmodel comes with the UseCustomSlug set to true (initially not) then the ‘CustomSlug’ should not be hidden.

Summary

In this tutorial we’ve seen how to create custom JavaScript/jQuery unobtrusive client-side validation. We’ve created a validation attribute class inheriting from (System.ComponentModel.DataAnnotations.)ValidationAttribute and we’ve implemented the IClientValidatable interface in order to send validation metadata to the view. We’ve, then, created a validator adapter (a JavaScript function that sets up the rule validation function) and the rule validation function.

Have fun jQuerying but don’t forget to check out the Stackoverflow jQuery-tagged question and the jQuery Learning Center because you’ll surely end up needing that information! m.CustomSlug)

You can download the project code.

This article was originally posted at http://blog.andrei.rinea.ro?p=356

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) IBM, Business Analytics
Romania Romania
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questioncustom client side validation. Pin
dudu43512-Aug-14 3:28
dudu43512-Aug-14 3:28 
AnswerRe: custom client side validation. Pin
Andrei Ion Rînea13-Aug-14 1:39
Andrei Ion Rînea13-Aug-14 1:39 
GeneralRe: custom client side validation. Pin
dudu43513-Aug-14 7:21
dudu43513-Aug-14 7:21 
Questionreturn of GetValue is throwing error Pin
Member 77575439-Jan-14 0:36
Member 77575439-Jan-14 0:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.