Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

ASP.NET GridView ASCII and Numeric Sorting

4.95/5 (95 votes)
28 Feb 2012CPOL3 min read 54.9K   1.2K  
ASCII and numeric sorting using an ASP.NET GridView
Image 1

Introduction

How many of you have a list that contains complex values and want to sort it based on ASCII code and the actual value? I would say this article will certainly solve your problem.

I will try to explain the solution based on a simple scenario.

Background

One of users requirement was to be able to sort and view their plot records, which contains the plot number, area code, area and perimeter, and other attributes. The plot number contains a series of characters that may contain letters, numbers, and one or more non-alphanumeric characters like forward slash (/), backward slash(\), dash(-), or an underscore ( _ ).

So the sorting requirement was, a number is preceded by a letter or non-alphanumeric character, and the number should be sorted by the actual value of the number and the rest of the characters. For example:

  • P-1054/A
  • P-100/B
  • P-807/A
  • P-1083/A
  • P-20/B

After sorted would be:

  • P-20/B
  • P-100/B
  • P-807/A
  • P-1054/A
  • P-1083/A

Problem

To solve this requirement, let’s investigate the default Sort method and property of the List<T> generic collection class and the DataView class, respectively. Both give us a sorted list, but don’t give the required result. If we sort the previous example, the output will be:

  • P-100/B
  • P-1054/A
  • P-1083/
  • P-20/B
  • P-807/A

Solution

Among one of the solutions is traversing each character and compare its value based on its number, letter, or special character value. So if the object to be compared contains a number, then compare as a number value. I will explain how to do this in the next section.

Note: For this article, I will consider only the List<T> class.

Using the Code

One of the built-in comparison interfaces that .NET provides is the IComparer<T> interface, which is a generic type. Implementing this interface in a Comparer class, which I call CharacterComparer<T>, gives us a generic Compare method. The class considers which attribute of the Type T is going to be used for comparing the values. It is also possible to make the comparison case-sensitive. I also use Reflection to get the attribute value of the Type T so that the actual character comparison can be done easily. A portion of the sorting class code is shown below:

C#
namespace SortLibray
{
    //
    //  Full code available in the source code
    //

    /// <summary>
    /// Character Comparer class
    /// </summary>
    /// <typeparam name="T">A Type T which
    ///         it's property is being compared</typeparam>
    public class CharacterComparer<T> : IComparer<T>
    {
        /// <summary>
        /// Compares the left and the right property values of a Type T
        /// </summary>
        /// <param name="left">Left value of Type T</param>
        /// <param name="right">Right value of Type T</param>
        /// <returns>An indicator whether the left
        ///         or the right is greater or not</returns>
        /// <remarks></remarks>
        public int Compare(T left, T right)
        {
            //
            //  Full code available in the source code
            //

            // Traverse each character of the left and right values of Type T
            char charLeft = leftValue[indicatorLeft];
            char charRight = rightValue[indicatorRight];

            // Allocate char array, based on the left and right values length of Type T
            char[] spaceLeft = new char[lengthLeft];
            char[] spaceRight = new char[lengthRight];
            int lockerLeft = 0,lockerRight = 0;

            do // Iterate each characters of the left
               // value of the Type T , until you get a Digit 
            {
                spaceLeft[lockerLeft] = charLeft;
                lockerLeft = lockerLeft + 1;
                indicatorLeft = indicatorLeft + 1;

                if (indicatorLeft < lengthLeft)
                    charLeft = leftValue[indicatorLeft];
                else
                    break;

            } while (char.IsDigit(charLeft) == char.IsDigit(spaceLeft[0]));
        }

       //
       //  Full code available in the source code
       //
     }
}

The core of the comparison logic is to traverse each character of the left and right attribute values of Type T objects. We can also extend this to comply with each attribute of a specific class, say Plot (I chose this class because I already mentioned it in the background of this article).

C#
namespace SortDemo
{
    /// <summary>
    /// Plot class
    /// </summary>
    public class Plot
    {
        //
        // Public Properties
        //
 
        /// <summary>
        /// Plot class
        /// </summary>
        public Plot()
        {
        }

        /// <summary>
        /// Plot class
        /// </summary>
        /// <param name="plotNumber">PlotNumber value</param>
        /// <param name="areaCode">AreaCode value</param>
        /// <param name="area">Area value</param>
        /// <param name="perimeter">Perimeter value</param>
        public Plot(string plotNumber, int? areaCode, 
                    float? area, float? perimeter)
        {
            this.PlotNumber = plotNumber;
            this.AreaCode = areaCode;
            this.Area = area;
            this.Perimeter = perimeter;
        }
    }
}

The extended PlotNumberComparer class will now look like this:

C#
namespace SortDemo
{
    /// <summary>
    /// PlotNumberComparer class
    /// </summary>
    public class PlotNumberComparer : CharacterComparer<Plot>
    {
        /// <summary>
        /// PlotNumberComparter class
        /// </summary>
        public PlotNumberComparer()
            :base("PlotNumber")
        {
            //
            // TODO: Add constructor logic here
            //
        }
        
        /// <summary>
        /// PlotNumberComparter class
        /// <param name="caseSensitive">Case Sensitivity indicator value</param>
        /// </summary>
        public PlotNumberComparer(bool caseSensitive)
            : base("PlotNumber",caseSensitive)
        {
            //
            // TODO: Add constructor logic here
            //
        }
    }
}

Let us see how we can demonstrate this comparer capability. I chose ASP.NET for the demo with a GridView control. Before going into that, let's make a collection class for the Plot class called Plots and an extension class for the List<T> generic collection class called SortExtension that will comply with the GridView control.

Here is the Plots class:

C#
namespace SortDemo
{
    /// <summary>
    /// Plots class
    /// </summary>    
    public class Plots : List<Plot>
    {
        /// <summary>
        /// Plots class
        /// </summary>
        public Plots()
        {
            this.Add(new Plot("P-1054/A", 3001, null, 105.081f));
            this.Add(new Plot("P-100/B", 01, 734.156f, null));        
            this.Add(new Plot("P-807/A", 3001, 764.277f, 111.299f));
            this.Add(new Plot("P-20/B", 01, 734.156f, 108.945f));
            this.Add(new Plot("P-1083/A", 3001, 198.52f, 68.108f));
        }
    }
}

Here is the SortExtension class:

C#
namespace SortDemo
{
    /// <summary>
    /// SortExtension class
    /// </summary>
    public static class SortExtension
    {
        /// <summary>
        /// List sort extension method
        /// </summary>
        /// <typeparam name="T">Type T to be sorted</typeparam>
        /// <param name="genericList">Generic list to be sorted</param>
        /// <param name="sortDirection">SortDirection value</param>
        /// <param name="comparer">Comparer value</param>
        /// <param name="caseSensitive">CaseSensitive indicator value</param>
        /// <returns>Sorted list of type T</returns>
        public static IList<T> SortedList<T>(this List<T> genericList,
            string sortExpression, SortDirection sortDirection,
            CharacterComparer<T> comparer = null, bool caseSensitive = false)
        {
            if (genericList == null ||
                string.IsNullOrEmpty(sortExpression) ||
                string.IsNullOrWhiteSpace(sortExpression))
                return null;
            else
            {
                if (comparer == null)
                    if (caseSensitive)
                        comparer = new CharacterComparer<T>(sortExpression, caseSensitive);
                    else
                        comparer = new CharacterComparer<T>(sortExpression);
                else
                    if (caseSensitive)
                        if (!comparer.CaseSensitive)
                            comparer.CaseSensitive = caseSensitive;

                genericList.Sort(comparer);
                if (sortDirection == SortDirection.Descending)
                    genericList.Reverse();
            }
            return genericList;
        }

        /// <summary>
        /// List sort extension method
        /// </summary>
        /// <typeparam name="T">Type T to be sorted</typeparam>
        /// <param name="genericList">Generic list to be sorted</param>
        /// <param name="sortDirection">SortDirection value</param>
        /// <param name="comparer">Comparer value</param>
        /// <param name="caseSensitive">CaseSensitive indicator value</param>
        /// <returns>Sorted list of type T</returns>
        public static IList<T> SortedList<T>
    (this List<T> genericList, SortDirection sortDirection,
            CharacterComparer<T> comparer, bool caseSensitive = false)
        {
            if (caseSensitive)
                if (!comparer.CaseSensitive)
                    comparer.CaseSensitive = caseSensitive;

            genericList.Sort(comparer);
            if (sortDirection == SortDirection.Descending)
                genericList.Reverse();
            return genericList;
        }

        /// <summary>
        /// List sort extension method
        /// </summary>
        /// <typeparam name="T">Type T to be sorted</typeparam>
        /// <param name="genericList">Generic list to be sorted</param>
        /// <param name="sortExpression">SortExpression value</param>
        /// <param name="sortDirection">SortDirection value</param>
        /// <param name="caseSensitive">CaseSensitive indicator value</param>
        /// <returns>Sorted list of type T</returns>
        public static IList<T> SortedList<T>
    (this List<T> genericList, string sortExpression, SortDirection sortDirection,
            bool caseSensitive = false)
        {
            return SortedList(genericList, sortExpression, 
            sortDirection, null, caseSensitive);
        }               
    }
}

Now most of the things have been done well so far. Let's put them in the presentation layer. In our presentation code class called GridViewSortDemo.aspx.cs, let's put a couple of methods and properties that will ease our life to present the demo.

Here are some page properties:

C#
///
/// Get or set GridView SortExpression in a viewstate
/// 
public string GridViewSortExpression
{
    get
    {
        if (ViewState[Constants.SORT_EXPRESSION] != null)
            return ViewState[Constants.SORT_EXPRESSION].ToString();
        return Constants.PLOT_NUMBER; // return PlotNumber as a default expression
    }
    set
    {
        ViewState[Constants.SORT_EXPRESSION] = value;
    }
}

///
/// Get or set GridView SortDirection in a viewstate
/// 
public SortDirection GridViewSortDirection
{
    get
    {
        if (ViewState[Constants.SORT_DIRECTION] != null)
            return (SortDirection)ViewState[Constants.SORT_DIRECTION];
        return SortDirection.Ascending; // return Ascending order
    }
    set
    {
        ViewState[Constants.SORT_DIRECTION] = value;
    }
}

Here are some private methods:

C#
/// summary
/// Bind GridView to a DataSource
/// summary
private void bindGridView()
{
    try
    {
        Plots plots = new Plots();
        if (cbCharactorComparer.Checked) // Use the Character Comparer
        {
            if (GridViewSortExpression.ToLower().Equals(Constants.PLOT_NUMBER.ToLower()))
               plots.SortedList(GridViewSortDirection, 
        new PlotNumberComparer(), cbCaseSensitivity.Checked);
            else
               plots.SortedList(GridViewSortExpression, 
        GridViewSortDirection, cbCaseSensitivity.Checked);
            gvPlots.DataSource = plots;
            gvPlots.DataBind();
        }
        else // Use built in Linq Sort mechanism 
        {
            List<Plot> sortResult = plots;
            switch (GridViewSortExpression)
            {
                case Constants.PLOT_NUMBER:
                    sortResult = plots.OrderBy(p => p.PlotNumber).ToList();
                    break;
                case Constants.AREA_CODE:
                    sortResult = plots.OrderBy(p => p.AreaCode).ToList();
                    break;
                case Constants.AREA:
                    sortResult = plots.OrderBy(p => p.Area).ToList();
                    break;
                case Constants.PERIMETER:
                    sortResult = plots.OrderBy(p => p.Perimeter).ToList();
                    break;
                default:
                    sortResult = plots.OrderBy(p => p.PlotNumber).ToList();
                    break;
            }
            if (GridViewSortDirection == SortDirection.Descending)
                sortResult.Reverse();
            gvPlots.DataSource = sortResult;
            gvPlots.DataBind();
        }
    }
    catch(Exception exception)
    {
        Response.Write(exception.Message);
    }
 }

Finally, let’s call the method bindGridView() to the Page_Load event:

C#
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
        bindGridView();
}

Now everything is presented well and ready to be tested. Just download the fully functioning application and run it. Enjoy. :)

History

  • Jan. 17, 2011: First version
  • Feb. 27, 2012: Updated version
  • Feb. 28, 2012: Updated version

License

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