The Code
We define sort order as an enum.
When the page loads, we determine the grid sort order and the current sort column. These values are stored in the ViewState.
When we're done processing, and the page is about to render, we save the sort order and sort column back into the ViewState. Then we call PositionGlyph() to put the sort arrow into the grid's header.
PositionGlyph() creates an Image control. If the sort order is ascending, the Image control will have an image of an arrow pointing up. If the sort order is descending, the Image control will have an image of an arrow pointing down. The Image control gets inserted in the column being sorted.
When the user clicks the LinkButton at the top of a column, the gridUsers_Sort() method gets called. We determine which column is being sorted by looking at the LinkButton's CommandArgument. We update the current sort order and sort column and then we bind the grid.
Whenever we bind the GridView, we sort the data first.
Going Further
Most of this code is the same for any grid you want to sort. The Page_Load(), Page_PreRender(), PositionGlyph() and grid_Sort() methods don't change. Rather than cut and paste these methods over and over, they could all go in a base class.