Monday, May 14, 2012

ASP.net MVC generic client and server side validation

In ASP.net Webform has CustomValidator which can call any javascript function for validation and which make our task is easy in terms of validation. During ASP.net MVC development we have validation via “DataAnnotation Attribute” for validation. All default attribute support most of our requirement but sometime need for custom validation.ASP.net MVC support this via Custom Attribute development but when this lead us to develop to many validation attribute for our need.

Now here i am going to show you one generic validation attribute implement which call any javascript function ( client side validation) or static function ( server side validation). Here i am assuming that ASP.net MVC 3 or later used which Unobtrusive validation true for project.

To develop custom validation attribute create class that inherits from ValidationAttribute and implement IClientValidatable interface.

C# Code 


public class JSFunctionValidateAttribute : ValidationAttribute, IClientValidatable
{
/// <summary>
/// Ctor: Javascript functionname is mandatory and serversideStaticFunction is not mandatory.
/// </summary>
/// <param name="javascriptFunctioname"></param>
/// <param name="serverSideStaticFunction"></param>
public JSFunctionValidateAttribute(string javascriptFunctioname,string serverSideStaticFunction = "")
:base()
{
JavaScriptFunctionName = javascriptFunctioname;
ServerSideFunction = serverSideStaticFunction;
}

/// <summary>
/// Javascript function that need to call
/// </summary>
public string JavaScriptFunctionName { get; set; }

/// <summary>
/// Server side validation.
/// </summary>
public string ServerSideFunction { get; set; }

/// <summary>
/// This function is used to call server side validation function.
/// </summary>
/// <param name="value"></param>
/// <param name="validationContext"></param>
/// <returns></returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            if (!string.IsNullOrEmpty(ServerSideFunction))
            {
                string className = ServerSideFunction.Substring(0, ServerSideFunction.LastIndexOf('.'));
                string functionName = ServerSideFunction.Substring(ServerSideFunction.LastIndexOf('.')+1);
                Type staticclassMethod = Type.GetType(className);
                MethodInfo info = staticclassMethod.GetMethod(functionName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                object result = info.Invoke(null ,new object[]{ value, validationContext , ErrorMessage });
                if (result != null)
                {
                    return (ValidationResult)result;
                }
            }           
            return null;
        }

/// <summary>
/// This generate client side unobtrusive validation.
/// </summary>
/// <param name="metadata"></param>
/// <param name="context"></param>
/// <returns></returns>
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule();
rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
rule.ValidationType = "jsfunctionvalidation";
rule.ValidationParameters.Add("jsfunctionname", JavaScriptFunctionName);
yield return rule;
}
}

In above code two important thing.

1. For client side validation

 public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule();
rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
rule.ValidationType = "jsfunctionvalidation";
rule.ValidationParameters.Add("jsfunctionname", JavaScriptFunctionName);
yield return rule;
}

ValidationType is “jsfunctionvalidation” and validation parameter contain “jsfunctionname” which contain name of javascript function to call.


Now register for unobstrusive validation to Jquery validation adaptor.

jQuery.validator.addMethod("jsfunctionvalidation", function (value, element, param) {  
return window[param](value, element, param);
});

jQuery.validator.unobtrusive.adapters.add("jsfunctionvalidation", ["jsfunctionname"], function (options) {
options.rules["jsfunctionvalidation"] = options.params.jsfunctionname;
options.messages["jsfunctionvalidation"] = options.message;
});

2. For Server-side validation

protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            if (!string.IsNullOrEmpty(ServerSideFunction))
            {
                string className = ServerSideFunction.Substring(0, ServerSideFunction.LastIndexOf('.'));
                string functionName = ServerSideFunction.Substring(ServerSideFunction.LastIndexOf('.')+1);
                Type staticclassMethod = Type.GetType(className);
                MethodInfo info = staticclassMethod.GetMethod(functionName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
                object result = info.Invoke(null ,new object[]{ value, validationContext , ErrorMessage });
                if (result != null)
                {
                    return (ValidationResult)result;
                }
            }           
            return null;
        }

This function call static function for validation via reflection and ServerSideFunction must contain fully qualified name. (Look at example later in this post)


Now look at complete example.


Let say we have to implement that required that Name must start which “C”.

Model class

    /// <summary>
/// Person model.
/// </summary>
public class Person
{
public int Id { get; set; }

[Display(Name = "Full name")]
[Required]
[JSFunctionValidate("NameValidation", "Models.Models.ServerSideValidation.ServerSideNameValidation", ErrorMessage = "Name must start with character 'C'")]
public string Name { get; set; }
}
JSFunctionValidate attribute indicate that need to call “NameValidation” javascript function for client side validation ( you will see sample function later in code) and for server side validation function you need to call “ServerSideNameValidation” static function of static class “Models.Models.ServerSideValidation”.

Server side validation class ( Signature of method must be same and class,method must be static)

    /// <summary>
/// Static server side validation function
/// </summary>
public static class ServerSideValidation
{
public static ValidationResult ServerSideNameValidation(object value,ValidationContext context, string errorMessage)
{
if (value != null)
{
if (value.ToString().StartsWith("C"))
{
return null;
}
}
return new ValidationResult(errorMessage);
}
}

Create View ( Razor View)

@model Models.Models.Person
@{
ViewBag.Title = "Create";
}
<h2>Create</h2>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/customvalidations.js")" type="text/javascript"></script>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Person</legend>

<div class="editor-label">
@Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>

<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<script language="javascript" type="text/javascript">
function NameValidation(value, element, param) {
if (value != null) {
if (value[0] == 'C')
return true;
}
return false;
}
</script>

Highlighted JS code is for client side validation.


Test Controller

public class TestController : Controller
{

public ActionResult Index()
{
return View();
}


public ActionResult Create()
{
return View();
}

[HttpPost]
public ActionResult Create(Models.Person person)
{
try
{
// TODO: Add insert logic here
if (ModelState.IsValid)
{
return RedirectToAction("Index");
}
else
{
return View("Create", person);
}
}
catch
{
return View();
}
}
}

Hope this solution give you your solution. Feel free to give your valuable comment.


Sample Code: Download

No comments: