BlueLemonCode.Com

Yet Another Tech Blog

Implementing validation callout in Asp.net MVC (with data annotations)

Introduction

I always liked validation callout extender that is available in Ajax control toolkit. However in MVC, we largely rely on jQuery plugins to do all the fancy stuff (and of course important tasks too).

MVC 3 uses Unobtrusive Javascript and jQuery validation plugin to provide inbuilt ability to do client side validation based on data annotations used in model class. This works very smooth and saves developers from writing too much of code for doing validations.

However, what if I want to use validation call out like functionality with data annotation feature of MVC 3 taking care of validation logic. In this article I will demonstrate how that can be done.

Background

Of course there are plenty of jQuery validation plugins available which provides validation callout functionality. But that way, we lose advantage of doing validations based on data annotations. Means, I have to get my hands dirty again in managing validation conditions, validation message etc.

I can take advantage of both by somehow combining jQuery plugin in unobtrusive javascript.

I tried to achieve that and I believe I have partially done it. I am still to remove some flaws from the code. But I think this would give someone like me a stating point... (I searched endlessly to see if someone has already done it).

I have used Ajax ControToolkit's validation callout extender sample to style the callout box. I borrowed html tags and style of call out extender from this old post of James Ashley

Article Body

little background about data annotation feature of MVC 3

You can take advantage of Data annotation model binder by adding Data Annotation Validation attribute on model properties. Following namespace are need to be included in you model class

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

We just need to decorate model property with the suitable annotation attribute. Like in the example below, the property UserName is required field.

[Required (ErrorMessage="hey.. enter this")]
public string UserName { get; set; }

By data annotation attribute, we add validation condition for server side validation at model level as well as validation on client side. To read more about Data annotation validation, read this tutorial on asp.net site

How client side validation happens by data annotation attributes

To demonstrate validation call out using data annotation, I have created this demo project which uses following model class to customize user registration

using System.Web.Mvc;
using System.Data.Entity;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace MvcApplication1.Models
{
    [Bind(Exclude="userid")]
    public class Users
    {
        [Key]
        [ScaffoldColumn(false)]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public string Userid { get; set; }

        [Required (ErrorMessage="hey.. enter this")]
        [Display(Name = "User name")]
        public string UserName { get; set; }

        [Required]
        [DataType(DataType.EmailAddress)]
        [Display(Name = "Email address")]
        public string EmailAddress { get; set; }

        [Required]
        [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "Password")]
        public string Password { get; set; }

        [DataType(DataType.Text)]
        [Display(Name = "Full Name")]
        public string FullName { get; set; }

        [DataType(DataType.Text)]
        [Display(Name = "Role")]
        public string Role { get; set; }

        [DataType(DataType.Text)]
        public string Active { get; set; }
    }

}

I have then created a strongly typed view. Run the project and navigate to the new page. Click on the Register button and you can see that client side validation message are shown.

If you look at the view source, you can find that Asp.net MVC by default added reference to following two scripts

<script src="/Scripts/jquery.validate.min.js" type="text/javascript"></script>
<script src="/Scripts/jquery.validate.unobtrusive.min.js" type="text/javascript"></script>

and html tags of user name field is like this which contains all information about validation.. which is then used by unobtrusive script to implement client side validation logic.

<div class="editor-label">
      <label for="UserName">User name</label>
</div>
<div class="editor-field">
      <input data-val="true" data-val-required="hey.. enter this" id="UserName" name="UserName" type="text" value="" />
      <span class="field-validation-valid" data-valmsg-for="UserName" data-valmsg-replace="true"></span>
</div>

It takes advantage of unobtrusive javascript. Lets take a look at un-minified unobtrusive script (jquery.validate.unobtrusive.js) in Visual studio. The OnError function in this file is where validation of field is done.

function onError(error, inputElement) {  // 'this' is the form element
        var container = $(this).find("[data-valmsg-for='" + inputElement[0].name + "']"),
            replace = $.parseJSON(container.attr("data-valmsg-replace")) !== false;

        container.removeClass("field-validation-valid").addClass("field-validation-error");
        error.data("unobtrusiveContainer", container);

        if (replace) {
            container.empty();
            error.removeClass("input-validation-error").appendTo(container);
        }
        else {
            error.hide();
        }
    }

How to replace validation message with validation Callout

This is all good. We added data annotation validation attribute in model class and with the help of default included unobtrusive script, MVC 3 takes care of both server side as well as client side validation. Now, coming back to our original requirement, we want to display validation callout  type message with underlying data validation using annotation.

First lets create another JavaScript file in the scripts folder as jquery.callout.unobtrusive.js. It contains code for

  • Creating HTML format of validation callout type message.
  • Function to display callout popup
  • Function to close validation message popup when validation is cleared
  • Function to close validation popup when close button clicked by user

Below is the complete code of jquery.callout.unobtrusive.js file. You can simple copy this code and create new js file and its reference in your view.

//Define callout validation popup format.
//This html format is take from a post by James Ashley
//The format contains hardcoded reference to warning and close button images which can be replaced by css class
//The format also contains close button image and reference to a javascript function CloseCallout() which is defined below 
var calloutFormat = '<div id="calloutValidation" style="display:none;position:absolute"  > ';
calloutFormat += '<table id="_popupTable" cellpadding="0" cellspacing="0" border="0" class="customCalloutStyle">';
calloutFormat += '<tbody>'; 
calloutFormat += '<tr class="validatorcallout_popup_table_row">';
calloutFormat += '<td class="validatorcallout_callout_cell"> ';
calloutFormat += '<table width="200px" cellpadding="0" cellspacing="0" border="0"';
calloutFormat += 'class="validatorcallout_callout_table"> ';
calloutFormat += '<tbody> ';
calloutFormat += '<tr class="validatorcallout_callout_table_row"> ';
calloutFormat += '<td class="validatorcallout_callout_arrow_cell"> ';
calloutFormat += '<div class="validatorcallout_innerdiv"> ';
calloutFormat += '<div style="width: 14px"> ';
calloutFormat += '</div> ';
calloutFormat += '<div style="width: 13px"> ';
calloutFormat += '</div> ';
calloutFormat += '<div style="width: 12px"> ';
calloutFormat += '</div> ';
calloutFormat += '<div style="width: 11px"> ';
calloutFormat += '</div> ';
calloutFormat += '<div style="width: 10px"> ';
calloutFormat += '</div> ';
calloutFormat += '<div style="width: 9px"> ';
calloutFormat += '</div> ';
calloutFormat += '<div style="width: 8px"> ';
calloutFormat += '</div> ';
calloutFormat += '<div style="width: 7px"> ';
calloutFormat += '</div> ';
calloutFormat += '<div style="width: 6px"> ';
calloutFormat += '</div> ';
calloutFormat += '<div style="width: 5px"> ';
calloutFormat += '</div> ';
calloutFormat += '<div style="width: 4px"> ';
calloutFormat += '</div> ';
calloutFormat += '<div style="width: 3px"> ';
calloutFormat += '</div> ';
calloutFormat += '<div style="width: 2px"> ';
calloutFormat += '</div> ';
calloutFormat += '<div style="width: 1px"> ';
calloutFormat += '</div> ';
calloutFormat += '</div> ';
calloutFormat += '</td> ';
calloutFormat += '<td class="validatorcallout_icon_cell"> ';
calloutFormat += '<img alt="" border="0" src="../Content/WebResource.gif" /> ';
calloutFormat += '</td> ';
calloutFormat += '<td class="validatorcallout_error_message_cell"> ';
calloutFormat += '<div id="ErrorMessage"> ';
calloutFormat += 'This is a required field. ';
calloutFormat += '</div> ';
calloutFormat += '</td> ';
calloutFormat += '<td class="validatorcallout_close_button_cell"> ';
calloutFormat += '<a onClick="CloseCallout(this);"><div class="validatorcallout_innerdiv"> ';
calloutFormat += '<img alt="" border="0" src="../Content/closeImg.gif"/> ';
calloutFormat += '</div> </a>';
calloutFormat += '</td> ';
calloutFormat += '</tr> ';
calloutFormat += '</tbody> ';
calloutFormat += '</table> ';
calloutFormat += '</td> ';
calloutFormat += '</tr> ';
calloutFormat += '</tbody> ';
calloutFormat += '</table> ';
calloutFormat += '</div> ';

//function to Display callout message which takes name of inout type control and the error object.
function DisplayCallout(cntrlName, error) {
    //find left and top position of input control which is having error
    var cntrlPosition = $("#" + cntrlName).offset();
    var cntrlLeft = cntrlPosition.left;
    var cntrlTop = cntrlPosition.top;
    //get controls width so as to adjust validation popup message's position
    var cntrlWidth = $("#" + cntrlName).width();
    //Create copy of validation message div and assign it a id which contains input field's id. Then add div into DOM
    var calloutCopy = $(calloutFormat).attr('id', 'calloutValidation_' + cntrlName).appendTo('body');
    //The validation div format contains a div ErroMessage. Replace it with validation message for current input field
    $(calloutCopy).find("#ErrorMessage").replaceWith(error[0].innerHTML);
    //Make validation div visible
    $(calloutCopy).css('display', 'block');
    //set validation div's position - 10 point right from input field
    $(calloutCopy).css({ 'top': '' + cntrlTop + 'px', 'left': '' + (cntrlLeft + cntrlWidth + 10) + 'px' });
}

//function to remove callout message when validation is cleared.
function RemoveCallout(container) {
    //The container contains input field's id in the selector property. Retrive it using string functions
    var firstQuote = container.selector.indexOf("'", 0);
    var calloutName = container.selector.substring(firstQuote + 1, container.selector.indexOf("'", firstQuote + 1));
    //Remove corrosponding validation div from DOM
    $('body').find("#calloutValidation_" + calloutName).remove().end();
}

//function to remove validation message div when user clicks on close button
function CloseCallout(callout) {
    $(callout).parents().filter("[id*=calloutValidation_]").remove().end()
}

After creating this script file, we need to modify jquery.validate.unobtrusive.js file. Open the file and make changes in the OnError and OnSuccess function as displayed below

function onError(error, inputElement) {  // 'this' is the form element
        var container = $(this).find("[data-valmsg-for='" + inputElement[0].name + "']"),
            replace = $.parseJSON(container.attr("data-valmsg-replace")) !== false;

        container.removeClass("field-validation-valid").addClass("field-validation-error");
        error.data("unobtrusiveContainer", container);

        if (replace) {
            container.empty();
            //call custom function to display validation callout like message
            DisplayCallout(inputElement[0].name,error);            
            //error.removeClass("input-validation-error").appendTo(container);
        }
        else {
            $('calloutValidation_' + inputElement[0].name).remove();
            $('body').find("#calloutValidation_" + inputElement[0].name).remove().end();
            error.hide();
        }
    }


    function onSuccess(error) {  // 'this' is the form element
        var container = error.data("unobtrusiveContainer"),
            replace = $.parseJSON(container.attr("data-valmsg-replace"));

        if (container) {
            //call removecallout function to remove validation message div when validation is cleared
            RemoveCallout(container);
            //container.addClass("field-validation-valid").removeClass("field-validation-error");
            error.removeData("unobtrusiveContainer");
            if (replace) {
                container.empty();
            }
        }
    }

There are couple of more changes we have to do. Since, by default reference is added to minified unobtrusive script file, we need to add reference to  uminified script too. Go to view file (in this case Register.cshtml) and add reference to jquery.validate.unobtrusive.js file and newly created script file jquery.callout.unobtrusive.js

Then create a new css file and include following styles in it. These style properties are referred and used in validation callout div format.

.customCalloutStyle div, .customCalloutStyle td
        {
            border: solid 1px Black;
            background-color: LemonChiffon;
        }
        .customCalloutStyle .validatorcallout_popup_table
        {
            display: none;
            border: none;
            background-color: transparent;
            padding: 0px;
        }
        .customCalloutStyle .validatorcallout_popup_table_row
        {
            vertical-align: top;
            height: 100%;
            background-color: transparent;
            padding: 0px;
        }
        .customCalloutStyle .validatorcallout_callout_cell
        {
            width: 20px;
            height: 100%;
            text-align: right;
            vertical-align: top;
            border: none;
            background-color: transparent;
            padding: 0px;
        }
        .customCalloutStyle .validatorcallout_callout_table
        {
            height: 100%;
            border: none;
            background-color: transparent;
            padding: 0px;
        }
        .customCalloutStyle .validatorcallout_callout_table_row
        {
            background-color: transparent;
            padding: 0px;
        }
        .customCalloutStyle .validatorcallout_callout_arrow_cell
        {
            padding: 8px 0px 0px 0px;
            text-align: right;
            vertical-align: top;
            font-size: 1px;
            border: none;
            background-color: transparent;
        }
        .customCalloutStyle .validatorcallout_callout_arrow_cell .validatorcallout_innerdiv
        {
            font-size: 1px;
            position: relative;
            left: 1px;
            border-bottom: none;
            border-right: none;
            border-left: none;
            width: 15px;
            background-color: transparent;
            padding: 0px;
        }
        .customCalloutStyle .validatorcallout_callout_arrow_cell .validatorcallout_innerdiv div
        {
            height: 1px;
            overflow: hidden;
            border-top: none;
            border-bottom: none;
            border-right: none;
            padding: 0px;
            margin-left: auto;
        }
        .customCalloutStyle .validatorcallout_error_message_cell
        {
            font-family: Verdana;
            font-size: 10px;
            padding: 5px;
            border-right: none;
            border-left: none;
            width: 100%;
        }
        .customCalloutStyle .validatorcallout_icon_cell
        {
            width: 20px;
            padding: 5px;
            border-right: none;
        }
        
        .customCalloutStyle .validatorcallout_close_button_cell
        {
            vertical-align: top;
            padding: 0px;
            text-align: right;
            border-left: none;
        }
        .customCalloutStyle .validatorcallout_close_button_cell .validatorcallout_innerdiv
        {
            border: none;
            text-align: center;
            width: 10px;
            padding: 2px;
            cursor: pointer;
        }

Also, we need to add following two images in the project which are used in validation div

    

That's it, Now, run the project and click on the Register button...whooo..! this is what is displayed

There are still few things which can be corrected/improved. However, this can surely give someone a head start who are looking for callout like validation in MVC project.

Hope this helps someone. Please add comment if you find any issues in the code and I will be glad to make changes/improvements :)

Update: Sample application attached

MVC_ValidationCallout.zip (2.37 mb)