I don't know why everyone forgets about using hidden fields! They are so much "cheaper" than ViewState (which I have turned off since 2005). If you don't want to use Session or ViewState, then here is my solution:
Put these two hidden fields on your aspx page, and put the default sort you want for your data (I'm using LastName for example):
<asp:HiddenField ID="hfSortExpression" runat="server" Value="LastName" />
<asp:HiddenField ID="hfSortDirection" runat="server" Value="Ascending" />
Then put this helper code in your Base page (you have a base page don't you? If not, put in your .cs code behind).
/// <summary>
/// Since native ASP.Net GridViews do not provide accurate SortDirections,
/// we must save a hidden field with previous sort Direction and Expression.
/// Put these two hidden fields on page and call this method in grid sorting event
/// </summary>
/// <param name="hfSortExpression">The hidden field on page that has the PREVIOUS column that is sorted on</param>
/// <param name="hfSortDirection">The hidden field on page that has the PREVIOUS sort direction</param>
protected SortDirection GetSortDirection(GridViewSortEventArgs e, HiddenField hfSortExpression, HiddenField hfSortDirection)
{
//assume Ascending always by default!!
SortDirection sortDirection = SortDirection.Ascending;
//see what previous column (if any) was sorted on
string previousSortExpression = hfSortExpression.Value;
//see what previous sort direction was used
SortDirection previousSortDirection = !string.IsNullOrEmpty(hfSortDirection.Value) ? ((SortDirection)Enum.Parse(typeof(SortDirection), hfSortDirection.Value)) : SortDirection.Ascending;
//check if we are now sorting on same column
if (e.SortExpression == previousSortExpression)
{
//check if previous direction was ascending
if (previousSortDirection == SortDirection.Ascending)
{
//since column name matches but direction doesn't,
sortDirection = SortDirection.Descending;
}
}
// save them back so you know for next time
hfSortExpression.Value = e.SortExpression;
hfSortDirection.Value = sortDirection.ToString();
return sortDirection;
}
Next, you need to handle the sorting in your grid sorting event handler. Call the method above from the sorting event handler, before calling your main method that gets your data
protected void gridContacts_Sorting(object sender, GridViewSortEventArgs e)
{
//get the sort direction (since GridView sortDirection is not implemented!)
SortDirection sortDirection = GetSortDirection(e, hfSortExpression, hfSortDirection);
//get data, sort and rebind (obviously, this is my own method... you must replace with your own)
GetCases(_accountId, e.SortExpression, sortDirection);
}
Since so many examples out there use DataTables or DataViews or other non LINQ friendly collections, I thought I'd include an example a call to a middle tier method that returns a generic list, and use LINQ to do the sorting in order to round out the example and make it more "real world":
private void GetCases(AccountID accountId, string sortExpression, SortDirection sortDirection)
{
//get some data from a middle tier method (database etc._)(
List<PendingCase> pendingCases = MyMiddleTier.GetCasesPending(accountId.Value);
//show a count to the users on page (this is just nice to have)
lblCountPendingCases.Text = pendingCases.Count.ToString();
//do the actual sorting of your generic list of custom objects
pendingCases = Sort(sortExpression, sortDirection, pendingCases);
//bind your grid
grid.DataSource = pendingCases;
grid.DataBind();
}
Lastly, here is the down and dirty sorting using LINQ on a generic list of custom objects. I'm sure there is something fancier out there that will do the trick, but this illustrates the concept:
private static List Sort(string sortExpression, SortDirection sortDirection, List pendingCases)
{
switch (sortExpression)
{
case "FirstName":
pendingCases = sortDirection == SortDirection.Ascending ? pendingCases.OrderBy(c => c.FirstName).ToList() : pendingCases.OrderByDescending(c => c.FirstName).ToList();
break;
case "LastName":
pendingCases = sortDirection == SortDirection.Ascending ? pendingCases.OrderBy(c => c.LastName).ToList() : pendingCases.OrderByDescending(c => c.LastName).ToList();
break;
case "Title":
pendingCases = sortDirection == SortDirection.Ascending ? pendingCases.OrderBy(c => c.Title).ToList() : pendingCases.OrderByDescending(c => c.Title).ToList();
break;
case "AccountName":
pendingCases = sortDirection == SortDirection.Ascending ? pendingCases.OrderBy(c => c.AccountName).ToList() : pendingCases.OrderByDescending(c => c.AccountName).ToList();
break;
case "CreatedByEmail":
pendingCases = sortDirection == SortDirection.Ascending ? pendingCases.OrderBy(c => c.CreatedByEmail).ToList() : pendingCases.OrderByDescending(c => c.CreatedByEmail).ToList();
break;
default:
break;
}
return pendingCases;
}
Last but not least (did I say that already?) you may want to put something like this in your Page_Load handler, so that the grid binds by default upon page load... Note that _accountId is a querystring parameter, converted to a custom type of AccountID of my own in this case...
if (!Page.IsPostBack)
{
//sort by LastName ascending by default
GetCases(_accountId,hfSortExpression.Value,SortDirection.Ascending);
}