Develop Custom Data-bound Controls in ASP.NET 2.0: Part II
Automate Delete, Update, Select, Insert, and Sorting Operations to Allow Page Developers to Use Your Custom Controls without Writinga Single Line of Code!
October 30, 2009
asp:Feature
LANGUAGES:C# | VB.NET
ASP.NETVERSIONS: 2.0 (Beta 2)
Develop Custom Data-bound Controls in ASP.NET 2.0: Part II
Automate Delete, Update, Select, Insert, and SortingOperations to Allow Page Developers to Use Your Custom Controls without Writinga Single Line of Code!
By Dr. Shahram Khosravi
This two-part article provides the implementation of acustom composite data-bound control named MasterDetailsForm that will use astep by step approach to show how to develop custom data-bound controls inASP.NET 2.0 (Beta 2) with minimal efforts. The two articles show how controldevelopers can automate all tasks of their custom controls, such as Delete,Update, Insert, and Sort, as well as display updates to allow page developersto use their custom controls. Please review PartI before continuing.
MasterDetailsForm
In PartI we implemented the important methods and properties of theBaseDataBoundControl, DataBoundControl, and CompositeDataBoundControl baseclasses to help developers get a better understanding of the ASP.NET 2.0implementation of these methods and properties. Because this implementation ofthe methods and properties of these base classes is fully functional, we havethe option of using either this article s or ASP.NET s implementation of thesemethods and properties. This section of the article will show how we can use orextend the ASP.NET 2.0 (or this article s) implementation of these methods andproperties to write our own custom data-bound controls. This section willimplement a custom data-bound control named MasterDetailsForm that extends thefunctionality of the CompositeDataBoundControl base class.
Control developers also have the option of extending thefunctionality of the ASP.NET classes that derive from theCompositeDataBoundControl class; e.g., the GridView and DetailsView classes.
The MasterDetailsForm control renders two tables. The toptable presents all the relevant database records. The MasterDetailsForm exposesa property named MasterFields that allows page developers to specify whichdatabase fields should be displayed in the top table. Each row of the top tablecomes with a link button that allows users to select the row. When a row isselected, the details of the corresponding database record are automaticallydisplayed in the bottom table. The bottom table also allows users to insert anew record and edit, update, and delete the selected record. Therefore, the topand bottom tables create a master/details form (hence the nameMasterDetailsForm). The top and bottom tables will be referred to as master anddetails tables, respectively.
The MasterDetailsForm data-bound control derives from theCompositeDataBoundControl base class where it implements theCreateChildControls method (see Figure 1).
protected override int CreateChildControls(
IEnumerable dataSource,bool useDataSource)
{
this.useDataSource =useDataSource;
if (dataSource != null)
{
CreateMasterDetailsTables();
renderHeader = true;
currentRowIndex = 0;
IEnumerator iter =dataSource.GetEnumerator();
while(iter.MoveNext())
{
this.currentDataItem = iter.Current;
masterCurrentDataRow= new TableRow();
if(currentRowIndex % 2 == 1)
masterCurrentDataRow.SkinID =
MasterAlternatingRowSkinID;
else
masterCurrentDataRow.SkinID =
MasterRowSkinID;
masterTable.Rows.Add(masterCurrentDataRow);
SetParameters();
for (int i = 0; i< columnCount; i++)
{
currentFieldIndex = i;
AddDetailsRow();
AddMasterCell();
}
AddMasterSelectButton();
renderHeader =false;
currentRowIndex++;
}
AddDetailsCommandBar();
AddErrorMessageLabel();
if (useDataSource)
ViewState["ColumnCount"] = columnCount;
}
return currentRowIndex;
}
Figure 1: TheCreateChildControls method enumerates the data and creates the controlhierarchy.
The method enumerates the records in the dataSourceargument to create the control hierarchy from the data source if useDataSourceis true (i.e., the records are real database records) and from the savedviewstate otherwise (i.e., the records are dummy records for enumerationpurposes). The for loop enumerates all the relevant fields or columns of eachenumerated record and calls the AddDetailsRow and AddMasterCell methods torender each enumerated field in the master and details tables, respectively.The master table displays each enumerated field as a cell; the details tabledisplays it as a row.
Recall the details table renders the details of the recordthat the user selects from the master table. Therefore, the details tabledisplays a single record at a time, contrary to the master table that displaysmultiple records. The details table can be in one of the following three states:ReadOnly, Edit, or Insert. The current implementation of the AddDetailsRowmethod allows users to update all fields except the primary key field. However,it can easily be extended to allow page developers to decide which fieldsshould be editable.
The CreateChildControls method uses the TypeDescriptorclass to access the fields of an enumerated record in generic fashion. TheGetProperties method of the TypeDescriptor class uses reflection to access allthe available information about each field. The method uses an instance of thePropertyDescriptor class to represent each field and returns an instance of thePropertyDescriptorCollection class that contains all the PropertyDescriptorinstances:
PropertyDescriptorCollection tempDataItemFields =
TypeDescriptor.GetProperties(currentDataItem);
The current implementation of the AddDetailsRow andAddMasterCell methods display fields whose values can be converted to stringvalues. The following code excludes the fields whose values cannot be convertedto string values:
ArrayList list = new ArrayList();
foreach(PropertyDescriptor pd in tempDataItemFields)
{
if(pd.Converter.CanConvertTo(Type.GetType(
"System.String")))
list.Add(pd);
}
currentDataItemFields =
newPropertyDescriptor[list.Count];
list.CopyTo(currentDataItemFields);
The PropertyDescriptor class exposes a property namedConverter that returns an instance of type TypeConverter. The instance exposesa method named CanConvertTo that can be used to determine whether a field canbe converted to a string value.
The AddDetailsRow method renders an instance of theTextBox control for each enumerated field when the details table is in theInsert or Edit state. The method then stores the name of the enumerated fieldin the Attributes collection of the TextBox instance for future references:
TextBox tbx = new TextBox();
PropertyDescriptor pd =
currentDataItemFields[currentFieldIndex];
tbx.Attributes["FieldName"] = pd.Name;
The above code uses the value of the Name property of therespective PropertyDescriptor object to access the name of the field in genericfashion. The AddDetailsRow method also displays the actual value of theenumerated field in the TextBox instance when the details table is in the Editstate:
tbx.Text = pd.Converter.ConvertTo(
pd.GetValue(currentDataItem),
Type.GetType("System.String")).ToString();
The above code uses the GetValue method of the respectivePropertyDescriptor object to access the value of the enumerated field, thenuses the ConvertTo method of the Converter property of the PropertyDescriptorobject to convert the value to a string. Recall the current implementation ofthe MasterDetailsForm control only displays fields whose values can beconverted to string values. Also notice the method sets the value of theDataKeyFieldValue property to the primary key field value of the selectedrecord. This property will be used in data operations such as Delete, Update,Insert, and Sort (discussed later).
After displaying all the fields of the enumerated record,the CreateChildControls method calls the AddMasterSelectButton method todisplay a select link button for the record and registers the CommandCallbackmethod as the callback for the Command event of the button. TheAddMasterSelectButton method sets the CommandArgument of the link button to theindex of the enumerated record:
LinkButton lbtn = new LinkButton();
Lbtn.CommandName = "Select";
lbtn.CommandArgument =currentRowIndex.ToString();
The CommandCallback method calls the SelectCallbackmethod:
protected void SelectCallback(int index)
{
SelectedIndex = index;
MasterDetailsFormMode =MasterDetailsFormMode.ReadOnly;
RequiresDataBinding =true;
}
The SelectCallback method switches the details table backto the ReadOnly mode and sets the RequiresDataBinding property to true.
There are numerous places where a custom control mustextract fresh data from the data store and refresh its display. TheSelectCallback method is one of these cases. When the user selects a recordfrom the master table, the MasterDetailsForm must extract the details of theselected record from the data store and display them in the details table. TheBaseDataBoundControl, DataBoundControl, and CompositeDataBoundControl baseclasses have made life easy for custom-control developers. Thanks to these baseclasses, every time a custom control needs to refresh its display with freshdata, it can simply set the RequiresDataBinding property to true. A single lineof code, like setting the property value, does the trick. The previous sectionsof this article described how this magic works. As a reminder, let s briefly reviewwhat happens when a custom control sets the RequiresDataBinding property totrue. The setter method of the RequiresDataBinding calls the EnsureDataBoundmethod of the BaseDataBoundControl class. The EnsureDataBound method checks thevalue of the RequiresDataBinding property. Because the custom control has setthe value to true, the EnsureDataBound method automatically calls the followingoverload of the DataBind method of the BaseDataBoundControl class:
public override void DataBind;
The DataBind method automatically calls the PerformSelectmethod of the DataBoundControl class. The PerformSelect method calls theGetData method of the DataBoundControl class to access the default tabularDataSourceView object. The PerformSelect method registers thePerformDataBinding method of the DataBoundControl class as the callback for theSelect operation. The PerformSelect method then calls the Select method of theDataSourceView object. The Select method extracts the fresh data from theunderlying data store. The Select method then automatically calls thePerformDataBinding method of the DataBoundControl and passes the data as itsonly argument. The PerformDataBinding method calls the following overload ofthe DataBind method of the CompositeDataBoundControl class:
protected override void DataBind(bool raiseOnDataBinding)
The DataBind method calls the following overload of theCreateChildControls method of the custom control (e.g., MasterDetailsForm)class:
protected override int CreateChildControls(
IEnumerable dataSource,bool useDataSource)
The CreateChildControls method enumerates the data andcreates the control hierarchy. The CreateChildControls method of theMasterDetailsForm calls the AddDetailsCommandBar to add the standard New, Edit,Update, Cancel, Delete, and Insert command buttons to the details table. Thetype of command buttons depends on the mode of the details table:
The three New, Edit, and Delete standard commandbuttons are used when the details table is in the ReadOnly mode. When the userclicks the New button, the details table switches to Insert mode, allowing theuser to add a new record. When the user clicks the Edit button, the detailstable switches to Edit mode, allowing the user to edit the existing record.
The two Update and Cancel command buttons areused when the details table is in Edit mode.
The two Insert and Cancel command buttons areused when the details table is in Insert mode.
When the user clicks any of the above command buttons, thebutton raises the Command event and calls the CommandCallback method. As theCommandCallback method shows, switching the details table from one mode toanother takes two steps:
1) Setthe MasterDetailsFormMode property to the new mode. The possible values areReadOnly, Edit, and Insert.
2) Setthe RequiresDataBinding property to true.
As previously discussed, setting the RequiresDataBindingproperty automatically refreshes the display of the details table.
The following sections will show how we can use theASP.NET 2.0 (or this article s) implementation of the methods and properties ofthe BaseDataBoundControl, DataBoundControl, and CompositeDataBoundControl toautomate the Delete, Update, Insert, and Sort data operations where pagedevelopers will be able to use the MasterDetailsForm control without writing asingle line of code!
The Delete, Update, and Insert operations change theunderlying data store. Therefore, the MasterDetailsForm control must extractfresh data from the underlying data store and refresh its display after theseoperations. However, thanks to the DataBoundControl class, that is no longernecessary. As discussed in the previous sections, the DataBoundControl classtakes the following two actions:
1) Exposesa new method named OnDataSourceViewChanged that sets the RequiresDataBindingproperty to true.
2) ItsGetData method registers the OnDataSourceViewChanged as the callback for theDataSourceViewChanged event of the default view object.
Therefore, the view object automatically calls theOnDataSourceViewChanged method right after deleting, updating, or inserting arecord. Because the OnDataSourceViewChanged method sets the RequiresDataBindingproperty to true, everything else automatically falls through.
However, it is important that the details table switchesback to its ReadOnly mode after deleting, updating, or inserting a record. Thatis why the MasterDetailsForm class overrides the OnDataSourceViewChangedmethod:
protected override void OnDataSourceViewChanged(
object sender, EventArgse)
{
MasterDetailsFormMode =MasterDetailsFormMode.ReadOnly;
base.OnDataSourceViewChanged(sender, e);
}
Delete Data Operation
The CommandCallback calls the Delete method to handle theDelete event:
private void Delete()
{
DataSourceView dv =GetData();
if (dv.CanDelete)
{
Hashtable keys = newHashtable();
keys.Add(DataKeyField,DataKeyFieldValue);
Hashtable oldValues =new Hashtable();
oldValues.Add(DataKeyField, DataKeyFieldValue);
dv.Delete(keys,oldValues,
newDataSourceViewOperationCallback(DeleteCallback));
}
}
As the Delete method shows, control developers must takethe following steps to automate the delete data operation:
Call the GetData method of the DataBoundControlclass to access the default tabular view object.
Check the value of the CanDelete property of theview object to make sure it supports the Delete data operation.
Create an instance of the Hashtable class andpopulate it with the primary key field values. The MasterDetailsForm controlexposes two properties, named DataKeyField and DataKeyFieldValue, whose valuesare the name of the primary key field and its value, respectively.
Create another instance of the Hashtable classand populate it with the old field values.
Call the Delete method of the view object.
Pass the above two instances of the Hashtableclass as the first two arguments of the Delete method.
Because the Delete operation is asynchronous,register a callback.
The first two arguments of the Delete methodtake objects of type IDictionary. Therefore, we can use objects of any classthat implements IDictionary, such as Hashtable, SortedList, etc.
Update Data Operation
The CommandCallback method calls the Update method tohandle the Update event, as shown in Figure 2.
private void Update()
{
DataSourceView dv =GetData();
if (dv.CanUpdate)
{
Hashtable keys = newHashtable();
keys.Add(DataKeyField,DataKeyFieldValue);
Hashtable values = newHashtable();
Hashtable oldValues =new Hashtable();
foreach (TableRow rowin detailsTable.Rows)
{
foreach (TableCellcell in row.Cells)
{
if(cell.Controls.Count > 0)
{
TextBox tbx= cell.Controls[0] as TextBox;
if (tbx !=null)
{
values.Add(tbx.Attributes["FieldName"],
tbx.Text);
oldValues.Add(tbx.Attributes["FieldName"],
tbx.Text);
}
}
}
}
dv.Update(keys, values,oldValues,
newDataSourceViewOperationCallback(
UpdateInsertCallback));
}
}
Figure 2: TheUpdate method handles the Update event.
As the Update method shows, control developers must takethe following steps to automate the Update data operation:
1) Callthe GetData method of the DataBoundControl class to access the default tabularview object.
2) Checkthe value of the CanUpdate property of the view object to make sure it supportsthe Update data operation.
3) Createan instance of the Hashtable class and populate it with the primary key fieldvalues.
4) Createtwo more instances of the Hashtable class.
5) Enumerateall the cells that contain textboxes and extract the names of the respectivefields and their values. Recall that the AddDetailsRow method added the name ofthe respective field to the Attributes property of the corresponding TextBoxinstance.
6) Populatethe above two Hashtable instances with the above field values.
7) Callthe Delete method of the view object.
8) Passthe above three Hashtable instances as the first three arguments of the Updatemethod.
9) Becausethe Update operation is asynchronous, register a callback.
In step 5, custom controls that use server controls otherthan textboxes must extract the names and values of the fields from whateverserver control is being used. The Insert operation is very similar to theUpdate operation. The only difference is that the Insert method only takes asingle instance of the Hashtable class because inserting a record does notinvolve primary key field values and old values.
Sort Data Operation
The MasterDetailsForm control exposes a property namedAllowSorting that allows page developers to turn sorting on/off. When the valueof this property is true, the AddMasterHeaderCell method renders the headertext as a link button, allowing users to sort the records. The method sets theCommandArgument of the link button to the name of the field and registers theCommandCallback as the callback for the Command event of the link button:
LinkButton hbtn = new LinkButton();
hbtn.CommandName = "Sort";
hbtn.Command += new CommandEventHandler(CommandCallback);
hcell.Controls.Add(hbtn);
if (useDataSource)
{
hbtn.CommandArgument =
currentDataItemFields[currentFieldIndex].Name;
hbtn.Text =
currentDataItemFields[currentFieldIndex].Name;
}
When the user clicks the header text of a column, theCommandCallback method calls the Sort method (see Figure 3).
private void Sort(string sortExpression)
{
if (SortExpression ==sortExpression)
{
if (SortDirection =="Asc")
SortDirection ="Desc";
else
SortDirection ="Asc";
}
else
{
SortExpression =sortExpression;
SortDirection ="Asc";
}
RequiresDataBinding =true;
}
Figure 3: The Sortmethod handles the Sort event.
The Sort method sets the values of the SortExpression andSortDirection properties and sets the RequiresDataBinding property to true.
Recall that the GetData method of the DataBoundControlclass calls the Select method of the underlying default view object. The firstargument of the Select method calls the CreateDataSourceSelectArguments methodto access the DataSourceSelectArguments object. The MasterDetailsForm controloverrides the CreateDataSourceSelectArguments method:
protected override DataSourceSelectArguments
CreateDataSourceSelectArguments()
{
DataSourceView dv =GetData();
DataSourceSelectArgumentsargs =
newDataSourceSelectArguments();
if (dv.CanSort)
args.SortExpression =
SortExpression +" " + SortDirection;
return args;
}
The method first calls the GetData method of theDataBoundControl class to access the default tabular view object. It thencreates an instance of the DataSourceSelectArguments class and checks the valueof the CanSort property of the view object to make sure it supports sorting.The method then sets the SortExpression property of theDataSourceSelectArguments object to the combination of the values of theSortExpression and SortDirection properties of the MasterDetailsForm control.
Control State
Many ASP.NET 1.x server controls provide features thatrely on viewstate to function properly across postbacks. For instance, thepagination feature of a DataGrid relies on viewstate to recover the currentpage index when the user clicks the Next button and posts the page back to theserver. Without recovering the current page index, the control would not knowwhat the next page is. The problem with viewstate is that it is public, whichmeans page developers may decide to turn it off. Obviously, those features ofserver controls that rely on viewstate will not function properly when theviewstate is turned off.
ASP.NET 2.0 provides server controls with their ownprivate storage named control state. Technically, both control and view statesare stored in the same hidden field named __VIEWSTATE. However, the controlstate is private to the control and page developers cannot turn it off.Therefore, custom controls must use their control states as storage for thoseproperties that are essential to their main features.
The MasterDetailsForm control uses its control state tostore those properties that are essential to its functionality and features.The control overrides the SaveControlState method to save its essentialproperties. The following shows a portion of the method:
protected override object SaveControlState()
{
object[] state = newobject[20];
state[0] =base.SaveControlState();
state[1] =_sortExpression;
state[2] =_masterFieldIndeces;
. . .
return state;
}
The MasterDetailsForm control also overrides theLoadControlState method to recover its essential properties. The followingshows a portion of the method:
protected override void LoadControlState(object savedState)
{
if (savedState != null)
{
object[] state =savedState as object[];
if (state != null&& state.Length == 20)
{
base.LoadControlState(state[0]);
if (state[1] !=null)
_sortExpression = (string)state[1];
if (state[2] !=null)
_masterFieldIndeces = (ArrayList)state[2];
. . .
}
}
else
base.LoadControlState(savedState);
}
The MasterDetailsForm control overrides the OnInit methodto notify the containing page that it needs to use its control state:
protected override void OnInit(EventArgs e)
{
Page.RegisterRequiresControlState(this);
base.OnInit(e);
}
Appearance Properties
Composite data-bound controls normally expose styleproperties that apply to their child controls. For instance, the GridViewcontrol exposes a property named SelectedItemStyle that applies to the selectedrow of the control. To get a better understanding of the problems associatedwith style properties, let s consider the following similar client-sidesituation.
There are two ways to apply appearance parameters to HTMLtags. One way is to do it inline, where the appearance attributes are directlyapplied to each tag. This mixes content; i.e., HTML tags with presentation (appearance)properties. Cascading Style Sheets were introduced to separate content frompresentation. Notice Cascading Style Sheets are client-side technology.
The style properties of composite data-bound controls alsomix content with presentation, but on the server side. ASP.NET 2.0 comes withwhat is known as themes to separatecontent from presentation on the server side. Therefore, the ASP.NET 2.0 themesare the preferred way to apply appearance properties to the child controls ofcomposite data-bound controls.
The MasterDetailsForm control exposes SkinID andEnableTheming properties that apply to its child controls. For instance, theMasterDetailsForm control sets the SkinID property of the master table to thevalue of the MasterTableSkinID property.
The MasterDetailsForm control exposes seven SkinIDproperties:
MasterTableSkinID: The SkinID property of themaster table.
MasterHeaderRowSkinID: The SkinID property ofthe header row of the master table.
MasterRowSkinID: The SkinID property of a row inthe master table.
MasterAlternatingRowSkinID: The SkinID propertyof an alternating row in the master table.
DetailsTableSkinID: The SkinID property of thedetails table.
DetailsRowSkinID: The SkinID property of a rowin the details table.
DetailsAlternatingRowSkinID: The SkinID propertyof an alternating row in the details table.
Codeless Master/Details Form
Figure 4 shows that page developers can declaratively usethe MasterDetailsForm custom data-bound control without writing a single lineof code!
<%@ Page Language="C#" Theme="SandAndSky"%>
<%@ Register TagPrefix="custom" Namespace=
"CustomControls"%>
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
"BookID"AllowSorting="true" ID="MyControl"Runat="Server" DataSourceID="MySource" MasterAlternatingRowSkinID="AlternatingRow" MasterTableSkinID="Table" DetailsAlternatingRowSkinID="AlternatingRow" DetailsTableSkinID="Table" MasterHeaderRowSkinID="HeaderRow" MasterFields="BookID,Title" /> "Server"SortParameterName="sortParameterName" TypeName="Books" SelectMethod="SelectBooks" DeleteMethod="DeleteBook" UpdateMethod="UpdateBook"InsertMethod="InsertBook"> Figure 4: Pagedevelopers can declaratively use the MasterDetailsForm control without writinga single line of code. The example page in Figure 4 uses the ObjectDataSourcecontrol to access the underlying data source. The Books class is a custom classthat exposes the SelectBooks, DeleteBook, UpdateBook, and InsertBook methods.Page developers can use any type of data source control that implements IDataSource,such as ObjectDataSource, SqlDataSource, and AccessDataSource. Conclusion This two-part article demonstrates how control developerscan use or extend the methods and properties of the ASP.NET 2.0 data-boundcontrol base classes to write custom controls that automatically handle thedata operations, such as Delete, Insert, Update, and Sort. This allows page developersto use the custom controls without writing a single line of code. The articlealso delved into the ASP.NET 2.0 implementation of the base classes to providecontrol developers with an inside view of the ASP.NET 2.0 data-bound controlmodel. The sample code accompanyingthis article is available for download. Dr. Shahram Khosraviis a Senior Software Engineer with Schlumberger Information Solutions (SIS).Shahram specializes in ASP.NET 1.x/2.0, XML Web services, ADO.NET 1.x/2.0, .NET1.x/2.0 technologies, XML technologies, 3D Computer Graphics, HI/Usability, andDesign Patterns. Shahram has extensive expertise in developing ASP.NET 1.x/2.0custom server controls and components. He has more than 10 years of experiencein object-oriented programming. He uses a variety of Microsoft tools andtechnologies such as SQL Server 2000 and 2005. Shahram writes articles ondeveloping ASP.NET 2.0 custom server controls and components, ADO.NET 2.0, andXML technologies for various industry-leading magazines such as asp.netPRO magazine, Microsoft MSDN Online,and CoDe magazine. Reach him at mailto:[email protected].
About the Author
You May Also Like