Friday, 30 March 2012

Client Side Custom Annotation Validation in ASP.NET MVC 3

On this post, we will see how to implement client side custom data annotation validation in ASP.NET MVC 3. I wrote about server side custom validation on my previous post.

There are a few steps that need to be done to implement client side custom validation:

1. Make our custom validation class (see my previous post for the codes example) inherits from IClientValidatable and implement its GetClientValidationRules method.
public class SumIntegers : ValidationAttribute, IClientValidatable
{
    . . .

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, 
          ControllerContext context)
    {
        ModelClientValidationRule rule = new ModelClientValidationRule();

        //specify a name for the custom validation
        rule.ValidationType = "sumintegers";

        //pass an error message to be used
        rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());

        //pass parameter(s) that need to be used when validating
        rule.ValidationParameters.Add("sum", _sum);

        yield return rule;
    }
}
To do this we need to add a reference to System.Web.Mvc namespace in the class file.

Below is the html generated from the ModelClientValidationRule properties set above.
<input type="text" value="" name="CSVInput" id="CSVInput" 
data-val-sumintegers-sum="20" 
data-val-sumintegers="custom error message for CSVInput" 
data-val="true" class="text-box single-line valid">
As we can see, the properties are put into data-val attributes. The attribute data-val-[validation_name] contains the error message, where [validation_name] is the ValidationType property's value we set above. Each passed parameter is put into data-val-[validation_name]-[parameter_name] attribute.


2. Write a jQuery validation adapter.
The adapter is used to retrieve the data-val attributes with their values and translate them into a format that jQuery validation can understand. So this adapter is helping us to easily implement our unobtrusive client side validation.

The adapter has several methods that we can use:
- addBool - creates an adapter for a validation rule that is 'on' or 'off', it requires no additional parameters
- addSingleVal- creates an adapter for a validation rule that needs to retrieve a single parameter value
- addMinMax - creates an adapter that maps to a set of validation rules, one that checks for a minimum value and the other checks for a maximum value
- add - used to create a custom adapter if we cannot use one of the methods above. We can use this if the adapter requires additional parameters or extra setup code.

In our case, addSingleVal is the best one to use.
// first parameter is the adapter name which should match with the value of ValidationType 
//    property of ModelClientValidationRule set on the server side
// second parameter is the parameter name added to ValidationParameters property of 
//    ModelClientValidationRule on the server side
$.validator.unobtrusive.adapters.addSingleVal("sumintegers", "sum");


3. Write the jQuery validator.
We do this through a method called addMethod that belongs to jQuery validator object.
// first parameter is the validator name which should match with the adapter name 
//    (which is also the same as the value of ValidationType)
// second parameter is the validation function to be invoked
$.validator.addMethod("sumintegers", 

  // the validation function's first parameter is the input value, second is the input element 
  //    and the third one is the validation parameter or an array of validation parameters passed
  function (inputValue, inputElement, sum) {

    var returnValue = true;
    if (inputValue) {
        var total = 0;

        try {
            $.each(inputValue.split(','), function () {
                total += parseInt(this);
            });
        }
        catch (err) {
            returnValue = false;
        }

        if (total != sum) {
            returnValue = false;
        }
    }
    return returnValue;

});

Say we put the scripts from step two and three in a single file called CustomScripts.js. Below is all the scripts that we have written:
/// <reference path="jquery-1.4.4.js" />
/// <reference path="jquery.validate.js" />
/// <reference path="jquery.validate.unobtrusive.js" />

if ($.validator && $.validator.unobtrusive) {

    $.validator.unobtrusive.adapters.addSingleVal("sumintegers", "sum");

    $.validator.addMethod("sumintegers", function (inputValue, inputElement, sum) {
        var returnValue = true;
        if (inputValue) {
            var total = 0;

            try {
                $.each(inputValue.split(','), function () {
                    total += parseInt(this);
                });
            }
            catch (err) {
                returnValue = false;
            }

            if (total != sum) {
                returnValue = false;
            }
        }
        return returnValue;
    });

}
The first three lines are references put to have IntelliSense works in our codes. Make sure the paths are correct.


4. Finally, include jquery.validate, jquery.validate.unobtrusive and our custom scripts files on the page to render.
<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/CustomScripts.js")"></script>


Tips: at the first time this validation will fire after the input lost focus but after that it will fire after each key press. This is the default behaviour of other built-in validators. If you are not happy with this and would like the validation to always fire only when the input lost focus then you need to add one of these scripts:
// if only for the specific input field
$(document).ready(function () {
    $("input#CSVInput").keyup(function () {
        return false;
    });
}

// or for all input fields
//    never put this inside document ready function
$.validator.setDefaults({
    onkeyup: false
})

Reference:
Professional ASP.NET MVC 3 - Jon Galloway, Phil Haack, Brad Wilson, K. Scott Allen

Friday, 16 March 2012

Server Side Custom Annotation Validation in ASP.NET MVC 3

On this post we will see an example of server side custom data annotation validation in ASP.NET MVC 3. I will write the client side one on my next post.

Let's say we would like to create a custom validation using data annotation to check the sum of a list of comma separated integers. If the sum is equal to the sum parameter supplied then the value is valid otherwise an error message will be displayed.

To begin, we need to create a class that derived from ValidationAttribute. This class is used by other built in validation annotations. We also need to add reference to System.ComponentModel.DataAnnotations namespace in our class file.

When implementing ValidationAttribute class, we need to override its IsValid method (line 12 and 13 of the codes below). This method has two parameters; the first one is the value to be validated and the second one provides more information about the field (context) where the value comes from. This method is where we put our validation logic.

Because we want to pass the sum amount to be checked against as a parameter, in other words we would like to use the validation attribute as something like [SumIntegers(20)], then we need to create a constructor accepting the parameter (line 6-10).

On line 27, we return a hard coded error message when an exception has occurred. We could also have a custom error message when using the validation attribute. We do this through ErrorMessage property of the ValidationAttribute. Say we would like to use our validation attribute as something like [SumIntegers(20, ErrorMessage="custom error message for {0}")]; to do this we call FormatErrorMessage method, pass the context display name to the method, then return the message as ValidationResult type (line 21-22).

We also provide a default error message (line 7), passing it to the base constructor. If we do not specify a default error message and somehow we forget to provide one on the validation attribute then the error message returned will be 'The field [fieldname] is invalid.'

Notice also that on the first line there are some attributes for the validation class. This is the default one used if we do not specify any. So in this case we may not need to specify it. To know more about System.AttributeUsage you can see http://msdn.microsoft.com/en-us/library/tw5zxet9%28v=VS.100%29.aspx

[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple = false, Inherited = true)]
public class SumIntegers : ValidationAttribute
{
    private readonly int _sum;

    public SumIntegers(int sum)
        :base("The sum of integers of {0} are not equal to the one specified.")
    {
        _sum = sum;
    }

    protected override ValidationResult IsValid(
        object value, ValidationContext validationContext)
    {
        if (value != null)
        {
            try
            {
                if (value.ToString().Split(',').Select(n => int.Parse(n.Trim())).Sum() != _sum)
                {
                    var errorMessage = FormatErrorMessage(validationContext.DisplayName);
                    return new ValidationResult(errorMessage);
                }
            }
            catch (Exception ex)
            {
                return new ValidationResult("Input is not valid");
            }
        }

        return ValidationResult.Success;
    }
}

Finally, the usages:
[SumIntegers(50)]
public string CSVInput { get; set; }

[SumIntegers(20, ErrorMessage="custom error message for {0}")]
public string Numbers { get; set; }

Monday, 5 March 2012

Grouping Data with LINQ

To group data in LINQ, we can use group ... by ... clause in query syntax or GroupBy() in method syntax. We will go through some examples and explanations along this post.


SIMPLE GROUPING
Let's start with simple grouping, below is an example:
// query syntax
var groupedData = from c in context.Customers
                  group c by c.Country;
// method syntax
var groupedData = context.Customers.GroupBy(c => c.Country);
Grouping in LINQ will result in an object of type IEnumerable<IGrouping<TKey,TSource>> which in this case is IEnumerable<IGrouping<String,Customer>>. IGrouping is a special class that has two properties; a key property and an IEnumerable<TSource> property that holds all the items corresponding to the key.

If we try to debug the 'groupedData' object, we will get something like this (you may need to click the image below to make it displayed bigger):
As we can see there's a 'Key' property and another property that contains some items corresponding to the 'Key' value.

To print out these items on screen:
foreach(var groupedItems in groupedData)
{
    Console.WriteLine(string.Format("Key: {0}", groupedItems.Key));
    foreach (var item in groupedItems)
    {
        Console.WriteLine(string.Format("{0} - {1}", item.CompanyName, item.Country));
    }
    Console.WriteLine("----------------------------------");
}


GROUPING WITH MORE THAN ONE KEY
If we want to have a grouping using two keys, we could use group x by new { x.Key1, x.Key2 } in query syntax or GroupBy( x => new { x.Key1, x.Key2 } ) in method syntax. Below is an example:
// query syntax
var groupedData2 = from c in context.Customers
                   group c by new { c.Country, c.City };
// method syntax
var groupedData2 = context.Customers.GroupBy(c => new {c.Country, c.City});

foreach (var groupedItems in groupedData2)
{
    //note that the Keys' names now become part of Key properties; ie. Key.Country and Key.City
    Console.WriteLine(string.Format("Key: {0} - {1}", groupedItems.Key.Country, groupedItems.Key.City));
    foreach (var item in groupedItems)
    {
        Console.WriteLine(string.Format("{0} - {1} - {2}", item.CompanyName, item.City, item.Country));
    }
    Console.WriteLine("----------------------------------");
}


PROJECTION
Here is an example of projecting the result into anonymous type objects:
// query syntax
var groupedData3 = from c in context.Customers
                   group c by c.Country into grp
                   select new
                   {
                       Key = grp.Key,
                       Items = grp.Select(g => new { g.CompanyName, g.Country })
                   };
// method syntax
var groupedData3 = context.Customers.GroupBy(c => c.Country).
                   Select(grp => new {
                                       Key = grp.Key, 
                                       Items = grp.Select(g => new {g.CompanyName, g.Country})
                                     }
                   );

foreach (var groupedItems in groupedData3)
{
    Console.WriteLine(string.Format("Key: {0}", groupedItems.Key));
    foreach (var item in groupedItems.Items)
    {
        Console.WriteLine(string.Format("{0} - {1}", item.CompanyName, item.Country));
    }
    Console.WriteLine("----------------------------------");
}

Below is another example that projects the result into strong typed objects.
The classes (made simple for demonstration purpose):
public class CompanyViewModel
{
    public string Name { get; set; }
    public string Country { get; set; }
}

public class GroupedCompanies
{
    public string CountryKey { get; set; }
    public IEnumerable<CompanyViewModel> Companies { get; set; }
}
Then the query:
var groupedData4 = from c in context.Customers
                   group c by c.Country into grp
                   select new GroupedCompanies
                   {
                       CountryKey = grp.Key,
                       Companies = grp.Select(g => new CompanyViewModel { Name = g.CompanyName, Country = g.Country })
                   };
foreach (GroupedCompanies groupedItems in groupedData4)
{
    Console.WriteLine(string.Format("Key: {0}", groupedItems.CountryKey));
    foreach (CompanyViewModel item in groupedItems.Companies)
    {
        Console.WriteLine(string.Format("{0} - {1}", item.Name, item.Country));
    }
    Console.WriteLine("----------------------------------");
}


GROUPING WITH MORE THAN ONE KEY + PROJECTION
Finally this example shows a combination of grouping with two keys and projection:
// query syntax
var groupedData5 = from c in context.Customers
                   group c by new { c.Country, c.City } into grp
                   select new
                   {
                       Key = grp.Key,
                       Items = grp.Select(g => new { g.CompanyName, g.City, g.Country })
                   };
// method syntax
var groupedData5 = context.Customers.GroupBy( c => new {c.Country, c.City} ).
                   Select( grp => new {
                                       Key = grp.Key, 
                                       Items = grp.Select(g => new {g.CompanyName, g.City, g.Country})
                                      }
                   );