ASP.NET GridView ASCII and Numeric Sorting






4.95/5 (95 votes)
ASCII and numeric sorting using an ASP.NET GridView

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:
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).
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:
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:
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:
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:
///
/// 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:
/// 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:
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