Utilizing Generics to make a Class structure more mutable…
While the ASP.NET GridView control supports automatic paging I found it faster to use custom paging in several situations. I found myself rewriting the same code over and over just to add the basic sorting capabilities to an ASP.NET GridView object. So today I took just a little bit of time to encapsulate it all into a Class I can use and reuse on any page with a GridView. In fact, it will probably take longer to write this blog entry than it took to encapsulate the functionality.
This set of code is the new class:
1: /// <summary>
2: /// Binds a List<T> data source to an ASP.NET GridView object.
3: /// </summary>
4: /// <typeparam name="T">Typically a DTO or POCO object.</typeparam>
5: internal class SortingGridView<T>
6: {
7: // http://stackoverflow.com/questions/3945645/sorting-gridview-with-entity-framework - almost there...
8: // http://stackoverflow.com/questions/722868/sorting-a-list-using-lambda-linq-to-objects - .AsQueryable()...
9:
10: #region Private Declarations
11: private StateBag _viewState = new StateBag(true);
12: #endregion
13:
14: #region Internal Properties
15: internal int TakeIndex { get { return _viewState["takeIndex"].ToString().ToInt32(); } set { _viewState["takeIndex"] = value.ToString(); } }
16: internal int SkipIndex { get { return _viewState["skipIndex"].ToString().ToInt32(); } set { _viewState["skipIndex"] = value.ToString(); } }
17: internal int TotalIndex { get { return _viewState["totalIndex"].ToString().ToInt32(); } set { _viewState["totalIndex"] = value.ToString(); } }
18: internal string SortingField { get { return _viewState["sortingField"].ToString(); } set { _viewState["sortingField"] = value.ToString(); } }
19: internal string SortingDirection { get { return _viewState["sortingDirection"].ToString(); } set { _viewState["sortingDirection"] = value.ToString(); } }
20: internal GridView SortedGridView { get; set; }
21: internal bool CanMovePrev { get; set; }
22: internal bool CanMoveNext { get; set; }
23: internal string NavigationText { get; set; }
24: internal List<T> SourceList { get; set; }
25: #endregion
26:
27: #region Constructor(s)
28: /// <summary>
29: /// Use this on all Post Backs
30: /// </summary>
31: /// <param name="viewState">ViewState from the page you are embedding this on</param>
32: /// <param name="GridView">The target GridView object</param>
33: /// <param name="SourceDataList">The List<T> Data Source to bind to the grid</param>
34: internal SortingGridView(StateBag viewState, GridView GridView, List<T> SourceDataList)
35: {
36: _viewState = viewState;
37: SortedGridView = GridView;
38: SourceList = SourceDataList;
39: BindGrid();
40: }
41: /// <summary>
42: /// Use this the first time to initialize all the ViewState paramaters.
43: /// </summary>
44: /// <param name="viewState">ViewState from the page you are embedding this on</param>
45: /// <param name="GridView">The target GridView object</param>
46: /// <param name="SourceDataList">The List<T> Data Source to bind to the grid</param>
47: /// <param name="Take">Default value for LINQ's .Take()</param>
48: /// <param name="Skip">Default value for LINQ's .Skip()</param>
49: /// <param name="Total">Default value for a total count of List<T> items</param>
50: /// <param name="Field">Default sorting column</param>
51: /// <param name="Direction">Default sorting order ("" for Ascending or "DESC" for Descending)</param>
52: internal SortingGridView(StateBag viewState, GridView GridView, List<T> SourceDataList, int Take, int Skip, int Total, string Field, string Direction)
53: {
54: _viewState = viewState;
55: TakeIndex = Take;
56: SkipIndex = Skip;
57: TotalIndex = Total;
58: SortingField = Field;
59: SortingDirection = Direction;
60: SortedGridView = GridView;
61: SourceList = SourceDataList;
62: BindGrid();
63: }
64: #endregion
65:
66: #region Internal Methods
67: internal void DoSorting(string SortExpression)
68: {
69: if (SortExpression.Equals(SortingField, StringComparison.CurrentCultureIgnoreCase))
70: {
71: if (SortingDirection.Equals("DESC", StringComparison.CurrentCultureIgnoreCase))
72: SortingDirection = "";
73: else
74: SortingDirection = "DESC";
75: }
76: else
77: {
78: SortingDirection = "";
79: SortingField = SortExpression;
80: }
81: BindGrid();
82: }
83: internal void GetFirst()
84: {
85: SkipIndex = 0;
86: BindGrid();
87: }
88: internal void GetPrevious()
89: {
90: if (SkipIndex > 0) SkipIndex--;
91: BindGrid();
92: }
93: internal void GetNext()
94: {
95: int lastIndex = (int)Math.Floor((decimal)TotalIndex / (decimal)TakeIndex);
96: if (SkipIndex < lastIndex) SkipIndex++;
97: BindGrid();
98: }
99: internal void GetLast()
100: {
101: int lastIndex = (int)Math.Floor((decimal)TotalIndex / (decimal)TakeIndex);
102: SkipIndex = lastIndex;
103: BindGrid();
104: }
105: #endregion
106:
107: #region Private Methods
108: private void BindGrid()
109: {
110: bool canMovePrev = true;
111: bool canMoveNext = true;
112: int start = (SkipIndex * TakeIndex) + 1;
113: int end = (SkipIndex * TakeIndex) + TakeIndex;
114:
115: RivWorks.DTO.FeedClientList feedClientList = RivWorks.Controller.FeedInfo.GetClients();
116:
117: //TotalIndex = feedClientList.ClientList.Count;
118: TotalIndex = SourceList.Count;
119:
120: if (SkipIndex <= 0)
121: {
122: canMovePrev = false;
123: canMoveNext = true;
124: }
125: else if (SkipIndex >= (int)Math.Floor((decimal)TotalIndex / (decimal)TakeIndex))
126: {
127: canMovePrev = true;
128: canMoveNext = false;
129: }
130: else
131: {
132: canMovePrev = true;
133: canMoveNext = true;
134: }
135: this.CanMovePrev = canMovePrev;
136: this.CanMoveNext = canMoveNext;
137:
138: this.NavigationText = string.Format("Records {0}-{1} of {2}.", start, end, TotalIndex);
139:
140: string sortExpression = SortingField + " " + SortingDirection;
141: SortedGridView.DataSource = SourceList.AsQueryable().SortBy(sortExpression.Trim()).Skip(SkipIndex * TakeIndex).Take(TakeIndex).ToList();
142: SortedGridView.DataBind();
143: }
144: #endregion
145: }
Notice on line 5 that I defined this class to accept a Generic Object. It is important to note here that it is the object and not a List<T> generic list! You can see in both Constructors that we can now pass in a List<T> generic object. I could just as easily pass in any other Generic<T> object if I wanted to define them in the Constructor or exposed method.
By defining the class this way I do not need to define the DTO/POCO object to use beforehand. This is what makes reuse in .NET very nice!
The following code example is for a web page’s code behind using the new class:
1: public partial class Default : System.Web.UI.Page
2: {
3: #region Internal Properties
4: internal SortingGridView<DTO.Feed.Client> SortingMainGrid;
5: #endregion
6:
7: #region Event Handlers
8: protected void Page_Load(object sender, EventArgs e)
9: {
10: DTO.FeedClientList fcl = new DTO.FeedClientList();
11:
12: if (!IsPostBack)
13: SortingMainGrid = new SortingGridView<DTO.Feed.Client>(this.ViewState, ClientGrid, fcl.ClientList, 15, 0, 0, "ClientID", "");
14: else
15: SortingMainGrid = new SortingGridView<DTO.Feed.Client>(this.ViewState, ClientGrid, fcl.ClientList);
16:
17: UpdatePager();
18: }
19: protected void ClientGrid_Sorting(object sender, GridViewSortEventArgs e)
20: {
21: SortingMainGrid.DoSorting(e.SortExpression);
22: }
23: protected void getFirst_Click(object sender, EventArgs e)
24: {
25: SortingMainGrid.GetFirst();
26: UpdatePager();
27: }
28: protected void getPrv_Click(object sender, EventArgs e)
29: {
30: SortingMainGrid.GetPrevious();
31: UpdatePager();
32: }
33: protected void getNext_Click(object sender, EventArgs e)
34: {
35: SortingMainGrid.GetNext();
36: UpdatePager();
37: }
38: protected void getLast_Click(object sender, EventArgs e)
39: {
40: SortingMainGrid.GetLast();
41: UpdatePager();
42: }
43: #endregion
44:
45: #region Private Methods
46: private void UpdatePager()
47: {
48: getFirst.Enabled = SortingMainGrid.CanMovePrev;
49: getPrev.Enabled = SortingMainGrid.CanMovePrev;
50: getNext.Enabled = SortingMainGrid.CanMoveNext;
51: getLast.Enabled = SortingMainGrid.CanMoveNext;
52: navIndicator.Text = SortingMainGrid.NavigationText;
53: }
54: #endregion
55: }
Note: Since I did not play around too much in the HttpContext with this, every time I call into the page I am passing in the ViewState, Target GridView and a List<T> data source.
Still things left to do. I am probably going to move this into a User Control with the paging controls so it is entirely encapsulated.
While I know most everyone now knows how to do this I wanted to document what I am doing. :)