DataControls 101 Part 2: Why you should love Datasource controls
I've been scouring the forums recently trying to find problems people encounter when using the data controls. One thing that I found is mostly asked for but is kind of a hidden art:
How do I use the GridView/ListView/DetailsView.... without a Datasource control? (and still get all the fancy features offered).
Now I've always know that you can do this but I decided to explore how much is involved in making this stuff to work. This sample shows a GridView using raw Linq queries in C#. Let’s dive into the code:
Markup
<asp:GridView
DataKeyNames="ProductID"
ID="products" runat="server"
AllowPaging="True"
AllowSorting="True"
CellPadding="4"
ForeColor="#333333"
GridLines="None">
<RowStyle BackColor="#F7F6F3" ForeColor="#333333" />
<Columns>
<asp:CommandField
ShowDeleteButton="True"
ShowEditButton="True"
ShowSelectButton="True" />
</Columns>
<FooterStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
<PagerStyle BackColor="#284775" ForeColor="White" HorizontalAlign="Center" />
<SelectedRowStyle BackColor="#E2DED6" Font-Bold="True" ForeColor="#333333" />
<HeaderStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
<EditRowStyle BackColor="#999999" />
<AlternatingRowStyle BackColor="White" ForeColor="#284775" />
</asp:GridView>
Code behind
protected void Page_Load(object sender, EventArgs e) {
// Setup Events
products.RowEditing += new GridViewEditEventHandler(OnProductsRowEditing);
products.RowUpdating += new GridViewUpdateEventHandler(OnProductsRowUpdating);
products.RowCancelingEdit += new GridViewCancelEditEventHandler(OnProductsRowCancelingEdit);
products.RowDeleting += new GridViewDeleteEventHandler(OnProductsRowDeleting);
products.Sorting += new GridViewSortEventHandler(OnProductsSorting);
products.PageIndexChanging += new GridViewPageEventHandler(OnProductsPageIndexChanging);
if (!IsPostBack) {
DataBindProducts();
}
}
private void DataBindProducts() {
using (NorthwindDataContext db = new NorthwindDataContext()) {
products.DataSource = db.Products;
products.DataBind();
}
}
The code above can be done in the markup but I chose to do it in the code behind. The code in Page_Load is very basic and self explanatory.
Editing
private void OnProductsRowEditing(object sender, GridViewEditEventArgs e) {
// Set EditIndex
products.EditIndex = e.NewEditIndex;
DataBindProducts();
}
The Edit event handler is pretty simple and straight forward. We set the new edit index and Databind the grid.
Paging
products.EditIndex = -1;
products.PageIndex = e.NewPageIndex;
DataBindProducts();
}
Paging code is similar to editing; we just set the new page index and reset the edit index to -1.
Cancel
// Reset the edit index
products.EditIndex = -1;
DataBindProducts();
}
Now the hard stuff...
Sorting
products.EditIndex = -1;
products.PageIndex = 0;
if ((SortExpression == e.SortExpression) && (SortDirection == SortDirection.Ascending)) {
e.SortDirection = SortDirection.Descending;
}
using (NorthwindDataContext db = new NorthwindDataContext()) {
products.DataSource = QueryableExtensions.SortBy(db.Products, e.SortExpression, e.SortDirection);
products.DataBind();
}
SortExpression = e.SortExpression;
SortDirection = e.SortDirection;
}
Woah! What’s going on here? When you examine the event arguments of the sorting event you will realize that the SortExpression changes but the SortDirection is always Ascending... This seems very weird, because the GridView keeps track of the sorting state (SortExpression and SortDirection). The problem is this state is only updated when the GridView is bound to a Datasource control :(. Why did we make that decision I’m not sure, but it is what it is so we must keep track of this ourselves.
public string SortExpression {
get {
return (string)ViewState["SortExpression"] ?? String.Empty;
}
set {
ViewState["SortExpression"] = value;
}
}
public SortDirection SortDirection {
get {
object o = ViewState["SortDirection"];
return o != null ? (SortDirection)o : SortDirection.Ascending;
}
set {
ViewState["SortDirection"] = value;
}
}
We store the current SortExpression and SortDirection in the viewstate (GridView keeps it in control state). Then we do the obvious logic to change the sort direction appropriately.
The rest of the sort method just uses my helper to convert SortExpression and SortDirection to a Linq expression.
Updating/Deleting
private void OnProductsRowUpdating(object sender, GridViewUpdateEventArgs e) {
GridViewRow row = products.Rows[e.RowIndex];
IOrderedDictionary values = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
DictionaryHelper.ExtractRowValues(products, values, row, true /* includeReadOnlyFields */, true /* includePrimaryKey */);
// Update the product
QueryableExtensions.UpdateProduct(values);
products.EditIndex = -1;
DataBindProducts();
}
private void OnProductsRowDeleting(object sender, GridViewDeleteEventArgs e) {
GridViewRow row = products.Rows[e.RowIndex];
IOrderedDictionary values = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
DictionaryHelper.ExtractRowValues(products, values, row, true /* includeReadOnlyFields */, true /* includePrimaryKey */);
// Delete the product
QueryableExtensions.DeleteProduct(values);
products.EditIndex = -1;
DataBindProducts();
}
These 2 methods are pretty similar in nature.First thing you may realize when examining the EventArgs in these methods are that all the dictionaries are empty! (again, another weird design choice) There is some logic in the Databound controls that detects if it is bound to a Datasource control or Datasource. In the case of the GridView, it calls ExtractRowValues to fill the dictionaries for update/delete if it is bound to a Datasource control. Since this function is protected the only other way to get the values from the row is to use find control and manually extract the data yourself, which can be a mess. I used private reflection in my sample to call the function and fill the dictionary (yes it's a little hacky :)).
Other random other pieces of code:
The calls to QueryableDataSourceHelper.SortBy, QueryableDataSourceHelper.UpdateProduct, QueryableDataSourceHelper.DeleteProduct, can be seen as specific implementations of a Datasource control’s ExecuteDelete, ExecuteInsert, and ExecuteSelect (sorting handled here) methods.
What your Datasource does for you:
- CRUD operations for a specific technology (SQL/XML/objects)
- Sorting and paging
- Conversion of values from controls/querystring/session…etc through parameters.
If you look at my previous post and the amount of code I wrote to get it working compared to the above is mind boggling.
DON'T TAKE YOUR DATASOURCE FOR GRANTED.
Here is a link to the sample solution.