top button
Flag Notify
    Connect to us
      Site Registration

Site Registration

Using IComparable To Perform Custom Sorting

0 votes
182 views

Introduction

Many web applications use DataSet to bind data with controls such as DataGrid. DataSet allows you to easily sort the data using objects such as DataView. However, in pure OO design instead of sending DataSet to the presentation layer you may want to send an object array. In this case how will you allow users to sort the data? That is what this article explains. Keep reading.

IComparable interface

Sorting is nothing but ordering the list of items in a perticular way. In order to sort any list the underlying system must be able to compare various elements of the list so that they can be rearranged based on the result of comparison. The System namespace contains an interface called IComparable that can be used to provide such custom comparison mechanism for your classes. The IComparable interface consists of a single method called CompareTo that accepts the object to compare with current instance. The method should return 0 if both the instances are equal, less than 0 if current instance is less than supplied instance and greater than 0 if current instance is greater than the supplied one.

[C#]
int CompareTo(object obj);

[Visual Basic]
Function CompareTo(ByVal obj As Object) As Integer

How does this solves our problem?

DataGrid web control can be bound with variety of data sources such as DataSet, DataTable and ArrayList. If we want to bind the grid with an a list of objects ArrayList can be good choice for this binding. ArrayList class has a method called Sort() that checks whether each element has implemented IComparable interface. If it does implements then the CompareTo() method implemented in the class will be used to sort the various elements. That means in order to implement custom sorting we need to:

  • Create a class (say Employee) that implements IComparable
  • Create an ArrayList
  • Add instances of Employee class to the ArrayList
  • Bind the ArrayList to the DataGrid
  • Handle the SortCommand event of the DataGrid

Create a Employee class that implements IComparable

public class Employee:IComparable
{
private string strName;
private int intAge;
private static string strSortOrder;

public Employee(string name,int age)
{
strName=name;
intAge=age;
}

public string Name
{
get
{
return strName;
}
set
{
strName=value;
}
}

public int Age
{
get
{
return intAge;
}
set
{
intAge=value;
}
}

public static string SortOrder
{
get
{
return strSortOrder;
}
set
{
strSortOrder=value;
}
}

public int CompareTo(object y)
{
string name1,name2;
int age1,age2;

switch(strSortOrder)            
{
case "ASCNAME":
	name1=this.Name;
	name2=((Employee)y).Name;
	return name1.CompareTo(name2);
case "DESCNAME":
	name1=((Employee)y).Name;
	name2=this.Name;
	return name1.CompareTo(name2);
case "ASCAGE":
	age1=this.Age;
	age2=((Employee)y).Age;
	if(age1<age2)
		return -1;
	else if(age1==age2)
		return 0;
	else
		return 1;
case "DESCAGE":
	age1=((Employee)y).Age;
	age2=this.Age;
	if(age1<age2)
		return -1;
	else if(age1==age2)
		return 0;
	else
		return 1;
default:
	name1=this.Name;
	name2=((Employee)y).Name;
	return name1.CompareTo(name2);

}
}
}

Here, we will set the SortOrder property (note that it is a static property) to indicate how the sorting should happen. Use use it internally in CompareTo method to decide the object property to sort on. Since this property is independent of any instance of the class we made it shared.

WebForm with DataGrid

Now, you need to create a web form with a DataGrid on it. Set AllowSorting prperty of teh DataGrid to true and add two bound coulmns. The Name boundcolumn should have sort expression set to "ASCNAME" and Age boundcolumn should have set it to "ASCAGE". Note that this is the SortOrder that we used internal to teh Employee class. Next, you need to create a function called BindGrid that will create instances of Employee class and bind the grid.

private void BindGrid(string sortfield)
{
ArrayList=new ArrayList();
Employee emp1 = new Employee("John", 30);
Employee emp2 = new Employee("Henry", 50);
Employee emp3 = new Employee("Bill", 20);
arr.Add(emp1);
arr.Add(emp2);
arr.Add(emp3);
if(sortfield != "")
{
    Employee.SortOrder = sortfield;
    arr.Sort();
}
DataGrid1.DataSource = arr;
DataGrid1.DataBind();
}

You will call this function in the Page_Load of the form and in the SortCommand event handler of teh DataGrid.

private void Page_Load(object sender,
EventArgs e)
{
    if(!Page.IsPostBack)
    {
       BindGrid("");
    }
}
private void DataGrid1_SortCommand
(object sender, 
DataGridSortCommandEventArgs e) 
{
        BindGrid(e.SortExpression);

}
posted Dec 7, 2016 by Shivaranjini

  Promote This Article
Facebook Share Button Twitter Share Button LinkedIn Share Button


Related Articles

Data annotation based validations validate one property at a time. For example, if you decorate CustomerID property of a class with [Required] data annotation attribute, it validates only the CustomerID property. Although this behavior is what is necessary in most of the cases, at times your outcome of a validation may depend on multiple properties. One can deal with such situations using IValidatableObject interface but the downside is that such a validation may not fit seamlessly with data annotation attributes.

In this article you will learn how to create a custom data validation attribute that performs validation based on multiple properties of the class. Specifically you will learn the following two approaches:

  • Create a custom data validation attribute to decorate a single property.
  • Create a custom data validation attribute to decorate the model class.

Which of these two approaches to use depends on your needs. The former approach is good if your class has, say, 10 properties and your validation requires two or three of them. The later approach is good if your validation depends on the object as a whole.

Custom data validation attribute for a property

Before you start developing such a data validation attributes, have a look at the following screen shot:

image

The above figure shows a Customer entry page that accepts CustomerID, CompanyName, ContactName and Country from the user. It performs validation on the CompanyName such that CompanyName ends with a specific phrase depending on a specific Country. For example, if the Country is USA, CompanyName should end with LLC or for an Indian company it should end with Ltd. and so on. If this multi-field validation fails an error message as shown in the figure is thrown back to the user.

To develop this data validation attribute create a new ASP.NET MVC application. Add ADO.NET Entity Data Model for Customers table of the Northwind database and also add HomeController.

Next, add a class named CompanyNameAttribute as shown below:

 public class CompanyNameAttribute:ValidationAttribute
{
  protected override ValidationResult IsValid(object value, 
                ValidationContext validationContext)
  {
    ...
  }
}

As you can see, CompanyNameAttribute class inherits from System.ComponentModel.DataAnnotations.ValidationAttribute base class. It then overrides IsValid() method. The IsValid() method takes two parameters - value and validationContext. The value parameter supplies the value of the property to which this attribute is attached (CompanyName in our case). The validationContext parameter can be used to grab the object instance under consideration. We can then inspect its other properties (as you will see later). The IsValid() method returns ValidationResult object indicating the validation error (if any).

Now add the following code inside the IsValid() method:

bool flag = false;
object instance = validationContext.ObjectInstance;
Type type = instance.GetType();
PropertyInfo property = type.GetProperty("Country");
object propertyValue = property.GetValue(instance);

The above code declares a Boolean variable - flag - that is set to true if the validation succeeds, else it is set to false. Then ObjectInstance property of validationContext is used to grab the underlying instance of Customer class. Remember that our custom attribute will be applied to the CompanyName property of the Customer class. The GetType() method is used to get t he type information of the object instance. Make sure to import System.Reflection for this code to compile correctly. Then GetProperty() method of Type object is called specifying Country as the property name. This way a PropertyInfo object for Country property will be returned. Finally, GetValue() method is used to retrieve the actual value of the Country property.

So far we haven't performed any validation. Next, add the following code:

if (propertyValue == null)
{
    flag = false;
}
else
{
    switch (propertyValue.ToString())
    {
        case "USA":
            if (value.ToString().EndsWith("LLC"))
            {
                flag = true;
            }
            break;
        case "India":
            if (value.ToString().EndsWith("Ltd"))
            {
                flag = true;
            }
            break;
        default:
            flag = true;
            break;
    }
}

The above code is quite straightforward and simply performs our custom validation mentioned earlier. Depending on the switch conditions the flag variable is set. If Country property is not set altogether then flag is set to false.

Finally, add the concluding piece of code as shown below:

if(!flag)
{
    ValidationResult result = new ValidationResult
                    ("Invalid Company Name for the specified country!");
    return result;
}
else
{
    return null;
}

The above code block checks the flag variable. If it is false, a ValidationResult is created with a custom error message and is returned from the IsValid() method. If flag is true, null is returned.

This completes the custom CompanyNameAttribute. Now, it's time to use this attribute. Add a metadata class to the project as shown below:

public class CustomerMetadata
{
    [CompanyName]
    [StringLength(20)]
    public string CompanyName { get; set; }
}

The above code creates a CustomerMetadata class and defines CompanyName property in it. The CompanyName property is decorated with two data validation attributes - our [CompanyName] attribute and inbuilt [StringLength] attribute.

Then add Customer partial class that links the CustomerMetdata class with the Customer model class.

[MetadataType(typeof(CustomerMetadata))]
public partial class Customer
{

}

As you can see, [MetadataType] attribute associates the CustomerMetadata class with the Customer model class.

Now, add Index view to the project and place the following markup in it:

@model MultiFieldCustomDataAnnotationDemo.Models.Customer

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    @using(Html.BeginForm("Index","Home",FormMethod.Post))
    {
        <h1>Add New Customer</h1>
        <table border="1" cellpadding="10">
            <tr>
                <td>Customer ID :</td>
                <td>@Html.TextBoxFor(m => m.CustomerID)</td>
            </tr>
            <tr>
                <td>Company Name :</td>
                <td>
                    @Html.TextBoxFor(m => m.CompanyName)
                    @Html.ValidationMessageFor(m => m.CompanyName)
                </td>
            </tr>
            <tr>
                <td>Contact Name :</td>
                <td>@Html.TextBoxFor(m => m.ContactName)</td>
            </tr>
            <tr>
                <td>Country :</td>
                <td>@Html.TextBoxFor(m => m.Country)</td>
            </tr>
            <tr>
                <td colspan="2">
                    <input type="submit" value="Submit" />
                </td>
            </tr>
        </table>
    }

    @Html.ValidationSummary()
</body>
</html>

The view markup is straightforward and uses ValidationMessageFor() and ValidationSummary() helpers to display validation errors.

Run the application and test our validation by entering some invalid CompanyName and Country combinations.

Custom data validation attribute for the whole model class

In the preceding approach you developed [CompanyName] attribute and decorated the CompanyName model property. In this approach you will create [ValidateCustomer] attribute and decorate the Customer class with it. In this case a sample run looks like this:

image

Did you notice the difference? In the precious case ValidationMessageFor() as well as ValidationSummary() emitted the error message. In this case only ValidationSummary() displays the error because we are validating the Customer object as whole rather than its individual properties.

In this case the custom data validation attribute looks like this:

public class ValidateCustomerAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        bool flag = false;
        Customer obj = (Customer)value;
        switch (obj.Country)
        {
            case "USA":
                if (obj.CompanyName.EndsWith("LLC"))
                {
                    flag = true;
                }
                break;
            case "India":
                if (obj.CompanyName.EndsWith("Ltd"))
                {
                    flag = true;
                }
                break;
            default:
                flag = true;
                break;
        }

        return flag;
    }
}

The ValidateCustomerAttribute class inherits from ValidationAttribute class as before. This time, however, it overrides another version of IsValid() method. This version of IsValid() accepts object value and returns a Boolean value indicating success or failure of the validation. Inside, we perform the same validation as earlier and return flag variable. Notice that since [ValidateCustomer] will be applied to Customer class, the whole Customer object under consideration is passed to the IsValid() method as value parameter. Thus all the properties of Customer class can be accessed inside IsValid() method.

To use [ValidateCustomer] attribute add it on the metadata class as follows:

[ValidateCustomer]
public class CustomerMetadata
{
    ...
}

As you can see [ValidateCustomer] is applied on top of the CustomerMetadata class (and hence Customer model class) rather than CompanyName (or any other) property. Just like any other data validation attribute you can customize the error message by setting the ErrorMessage property of the base class.

[ValidateCustomer(ErrorMessage = 
"Invalid Customer data. Please check all the fields.")]
public class CustomerMetadata
{
  ....
}

That's it! You can now run the application and try entering some invalid values. If all is well you should see our custom error message displayed in the ValidationSummary().

READ MORE
...