DropDownListField

Add Foreign Key Editing Capability to Your GridView and DetailsView Controls without Writing a Single Line of Code

Dr. Shahram

October 30, 2009

17 Min Read
ITPro Today logo

asp:Feature

LANGUAGES:C# | VB.NET

ASP.NETVERSIONS: 2.0

 

DropDownListField

Add Foreign Key Editing Capability to Your GridView andDetailsView Controls without Writing a Single Line of Code

 

By Dr. Shahram Khosravi

 

The foreign/primary key pairs establish relationship amongdatabase tables. The value of a foreign key field in a given record is one ofthe existing values of its corresponding primary key field. ASP.NET pagedevelopers must take extra steps to allow users to display and edit the foreignkey fields of a row in data-bound controls such as GridView and DetailsView.

 

The state of the row that contains a foreign key fielddetermines what steps must be taken. The following steps are normally takenwhen the containing row is not in the Edit or Insert state:

  • The current foreign key value is one of thevalues of its corresponding primary key field. Most database tablesautomatically generate the primary key value of a record when the record isadded to its table. Therefore, the actual foreign key value is anauto-generated integer that does not mean anything to users. However, the tablethat contains the primary key field normally exposes another field with moremeaningful values to users. For instance, consider a database that containstables named Products and Categories. The Products table has a foreign keyfield named CategoryID. The Categories table contains the corresponding primarykey field, i.e. CategoryID. The Categories table also exposes a field namedCategoryName, which is more meaningful to users. Page developers extract thecurrent value of the field that makes more sense to users, e.g. CategoryName.

  • Display the current value of the user-friendlyfield (e.g. CategoryName) as simple text.

 

The following steps are taken when the containing row isin the Edit or Insert state where users are allowed to edit the current valueof the foreign key field:

  • Write code to access the database and extractall legal values of the foreign key field and the field with more meaningfulvalues to users, e.g. CategoryName in the previous example.

  • Instantiate a DropDownList server control andbind it to the data extracted in the first step.

  • Set the DataValueField property of theDropDownList control to the name of the corresponding primary key field.

  • Set the DataTextField property of theDropDownList control to the name of the user-friendly field (e.g. CategoryName).

  • Extract the current value of the foreign keyfield.

  • Set the SelectedIndex of the DropDownListcontrol to the index of the current value of the user-friendly field (e.g.CategoryName).

  • Allow users to select a null value for theforeign key field because most database tables allow null values for theirforeign key fields.

 

Because none of the existing fields (i.e. BoundField,HyperLinkField, ImageField, CheckBoxField, CommandField, ButtonField, andTemplateField fields) of data-bound controls such as GridView and DetailsViewprovide built-in support for the above nine steps, page developers have nochoice but to take care of all the steps themselves. This article will designand implement a new field named DropDownListField that will automatically takecare of all the above nine steps. Page developers will be able to declarativelyuse the DropDownListField instances without writing a single line of code!

 

Both GridView and DetailsView controls consist of fields.They are called fields because theymostly display the values of their respective database fields. GridView andDetailsView controls render their fields as columns and rows, respectively. Allfields are the instances of the descendants of the DataControlField class. Theexisting descendants are the BoundField, ImageField, HyperLinkField,CheckBoxField, CommandField, ButtonField, and TemplateField classes.

 

Most of these field classes internally use server controlsto display the values of their respective database fields. For example,ImageField and CheckBoxField objects internally use Image and CheckBox servercontrols, respectively, to display their field values. The data type of thefield and the state of its containing row determine the type of server controlused to display the value of the field. For instance, an ImageField object usesan Image server control to display its field value when its containing row isin normal state, and a TextBox server control when itscontaining row is in the Edit or Insert state.

 

This article will design and implement a new field namedDropDownListField that will use a DropDownList server control to display allthe legal values of its field when its containing row is in the Edit or Insertstate. The DropDownListField field will display the current value of its fieldas simple text when its containing row is not in the Edit or Insert state. Thisarticle will derive the DropDownListField class from the BoundField classbecause the BoundField class provides all the necessary base functionality whenthe containing row is not in the Edit or Insert state, such as:

  • Extracting the current value of the field whosename is the value of the DataField property. The DropDownListField overridesthis property and defines a new property named DataTextField to replace itbecause DataTextField is a more appropriate name than DataField.

  • Displaying the current value as simple text ifthe current value is not null.

  • Displaying the value of the NullDisplayTextproperty if the current value is null.

  • Displaying the value of the HeaderText propertyas a simple text if sorting is disabled and as a hyperlink if sorting isenabled.

  • Raising the GridView.Sorting event whenAllowSorting is enabled and the header hyperlink is clicked.

 

The main shortcoming of the BoundField class is that itdisplays the current value of the field in a TextBox control when thecontaining row is in the Edit or Insert state. The TextBox control is not theappropriate server control for editing foreign key fields because it allowsusers to enter any value and does not restrict the values to the legal ones.The DropDownListField class overrides the InitializeDataCell, OnDataBindField,and ExtractValuesFromCell methods of the BoundField class to add the supportsneeded when the containing row is in the Edit or Insert state. Figure 1 showsall the properties and methods of the DropDownListField class. The followingsections will present the implementation of these properties and methods.

 

public class DropDownListField : BoundField

{

 public override stringDataField {get; set; }

 public virtual stringDataTextField {get; set; }

 public virtual stringSkinID {get; set;}

 public virtual boolEnableTheming {get; set;}

 public virtual stringDataValueField {get; set;}

 public virtual stringDataSourceID {get; set;}

 protected override voidOnDataBindField(Object sender,

                                         EventArgse);

 protected override voidInitializeDataCell(DataControlFieldCell cell,

                                            DataControlRowStaterowState);

 public override voidExtractValuesFromCell(IOrderedDictionary dictionary,

                                            DataControlFieldCellcell,

                                            DataControlRowStaterowState,

                                            boolincludeReadOnly);

}

Figure 1: The propertiesand methods of the DropDownListField class.

 

Overriding the InitializeDataCell Method

The BoundField class exposes a method namedInitializeDataCell that contains the code that generates the appropriate HTMLmarkup text for the data cell. The InitializeDataCell method takes twoarguments. The first argument is the DataControlFieldCell instance beinginitialized. The second argument is the state of the containing row.

 

Which HTML markup text the BoundField class implementation of the InitializeDataCell method emits depends on the state ofits containing row. If the containing row is not inthe Edit or Insert state, the method simply registers the OnDataBindFieldmethod as the callback for the DataBinding event of the respectiveDataControlFieldCell instance. When the DataBinding event of the cell israised, the OnDataBindField Method extracts the current value of the respectivefield (the name of the field is the value of the DataField property). If thecurrent value is null, the value of the NullDisplayText property is displayed.Otherwise, the current value is displayed as simple text.

 

The BoundField class implementation of theInitializeDataCell method in normal state is exactly what we need. However, theBoundField class implementation of the method when the containing row is inthe Edit or Insert state is not acceptable because the method instantiates aninstance of the TextBox control. We need an implementation that instantiates aninstance of the DropDownList control. This is why the DropDownListField classoverrides the InitializeDataCell method. The DropDownListField class calls thebase version of the InitializeDataCell method when the containing row is not inthe Edit or Insert state because the behavior of the base version is exactlywhat we need. However, the DropDownListField class provides its ownimplementation when the containing row is in the Edit or Insert state.

 

The DropDownListField class implementation of theInitializeDataCell method instantiates an instance of the DropDownList controland sets its DataSourceID property to the value of the DataSourceID property ofthe DropDownListField instance. It is the responsibility of page developers toset the DataSourceID property of the DropDownListField instance to the value ofthe ID property of the appropriate data source control in the containing page.Page developers must also set the DataTextField and DataValueField propertiesof the DropDownListField class to the names of the appropriate database fields.This allows the DropDownListField instance to automatically populate itsDropDownList control with the valid values of the foreign key field:

 

DropDownList ddl = new DropDownList();

ddl.DataSourceID = DataSourceID;

ddl.DataTextField = DataTextField;

ddl.DataValueField = DataValueField;

 

As discussed in the introduction, one of the requirementsfor the DropDownListField class is that it must set the selected value of theDropDownList control to the current value of the respective foreign key field.This is done in a callback registered for the DataBound event of theDropDownList control. The DropDownList control inherits the DataBound eventfrom the BaseDataBoundControl class. There is a difference between theDataBound event that the BaseDataBoundControl class exposes and the DataBindingevent that the Control class exposes.

 

The DataBinding event is raised before the data isactually bound, and the DataBound event is raised after the data bindingprocess finishes. Because the selected value of the DropDownList control mustbe set after the control is bound to its data source, it is set within thecallback for the DataBound event. The InitializeDataCell method registers theOnDataBindField method as the callback for the DataBound event of theDropDownList control:

 

if (DataTextField.Length != 0 && DataValueField.Length !=0)

   ddl.DataBound += newEventHandler(OnDataBindField);

 

Figure 2 illustrates the code for the InitializeDataCellmethod.

 

protected override void InitializeDataCell(DataControlFieldCellcell,

                                          DataControlRowStaterowState)

{

 if ((rowState &DataControlRowState.Edit) != 0 ||

      (rowState &DataControlRowState.Insert) != 0)

 {

    DropDownList ddl = newDropDownList();

    ddl.SkinID = SkinID;

    ddl.EnableTheming =EnableTheming;

    ddl.DataSourceID =DataSourceID;

    ddl.DataTextField =DataTextField;

    ddl.DataValueField =DataValueField;

    if(DataTextField.Length != 0 && DataValueField.Length != 0)

        ddl.DataBound +=new EventHandler(OnDataBindField);

      cell.Controls.Add(ddl);

 }

 else

    base.InitializeDataCell(cell,rowState);

}

Figure 2: TheInitializeDataCell method initializes the current data cell object.

 

Handling the DataBound Event

When the DataBinding event of the cell is raised, theOnDataBindField method is called to display the current value in display mode,i.e. as simple text. When the DataBound event of the DropDownList control israised, the OnDataBindField method is called to display the current value inedit mode, i.e. as the selected item of the DropDownList control.

 

The reason the OnDataBindField method is called when theDataBinding event of the cell is raised is that the BoundField version of theInitializeDataCell method registers the OnDataBindField method as the callbackfor the DataBinding event of the cell. The reason the OnDataBindField method iscalled when the DataBound event of the DropDownList control is raised is thatthe DropDownListField version of the InitializeDataCell method registers theOnDataBindField method as the callback for the DataBound event of theDropDownList control.

 

Before the OnDataBindField method can display the currentvalue in the edit or insert mode, it must extract the value. TheOnDataBindField method uses the parent control of the cell to access the value:

 

IDataItemContainer container =

  (IDataItemContainer)cell.Parent;

object dataItem = container.DataItem;

 

The parent of the cell is a row of type GridViewRow inGridView controls and of type DetailsViewRow in DetailsView controls. Boththese types implement the IDataItemContainer interface. IDataItemContainerexposes a single property named DataItem of type Object. The dataItem objectrepresents the record of the database to which the row is bound (e.g. the GridViewRowor DetailsViewRow object). After we access the dataItem object we can use theDataBinder class to extract the current value of the field whose name is thevalue of the DataValueField property

 

object dataValueField = DataBinder.Eval(dataItem,

                                       DataValueField);

 

The value of the DataValueField property is the name ofthe foreign key field.

 

After the OnDataBindField method extracts thedataValueField value it takes two additional actions. Because most databasetables allow null values for foreign key fields, the OnDataBindField methodinserts a new item into the DropDownList control and sets its Text property tothe value of the NullDisplayText property:

 

if (NullDisplayText.Length > 0)

 ddl.Items.Insert(0, newListItem(NullDisplayText, "-1"));

 

The next section will delve into the details of theExtractValuesFromCell method. This method extracts the SelectedValue of theDropDownList control and inserts it into an IDictionary container that ispassed in as its first argument. The OnDataBindField method sets the value ofthe Value property of the newly added item to the string -1 to signal theExtractValuesFromCell method to insert a null value in the IDictionarycontainer.

 

The OnDataBindField method then sets the SelectedIndex ofthe DropDownList control to the respective index of dataValueField if dataValueFieldis not equal to DBNull. Otherwise, it configures the DropDownList control todisplay the newly added item as its selected item:

 

if (dataValueField.Equals(DBNull.Value))

    ddl.SelectedIndex = 0;

 else

    ddl.SelectedIndex =ddl.Items.IndexOf(

     ddl.Items.FindByValue(dataValueField.ToString()));

 

Figure 3 shows the code for the OnDataBindField method.

 

protected override void OnDataBindField(Object sender,

                                       EventArgse)

{

 DropDownList ddl2;

 DataControlFieldCellcell;

 ddl2 = sender asDropDownList;

 if (ddl2 == null)

 {

   base.OnDataBindField(sender, e);

   return;

 }

   cell = (DataControlFieldCell)ddl2.Parent;

 IDataItemContainercontainer =

   (IDataItemContainer)cell.Parent;

 object dataItem =container.DataItem;

 object dataValueField =DataBinder.Eval(dataItem,

                                         DataValueField);

 DropDownList ddl =cell.Controls[0] as DropDownList;

 if (ddl != null)

 {

   if(NullDisplayText.Length > 0)

       ddl.Items.Insert(0,new ListItem(

                        NullDisplayText,"-1"));

    if(dataValueField.Equals(DBNull.Value))

        ddl.SelectedIndex= 0;

    else

        ddl.SelectedIndex= ddl.Items.IndexOf(

         ddl.Items.FindByValue(dataValueField.ToString()));

 }

}

Figure 3: TheOnDataBindField method is called when either the cell s DataBinding event orthe DropDownList s DataBound event is raised.

 

Extracting Values from Cells

GridView and DetailsView controls allow users to editdatabase fields. Users click on the Update button after they make the desiredchanges. GridView and DetailsView controls are equipped with internal handlersto handle the Update event. The handlers call the ExtractRowValues method ofthe GridView or DetailsView control, which in turn calls theExtractValuesFromCell methods of its cells. The ExtractRowValues methodprovides each ExtractValuesFromCell method with a container of typeIOrderedDictionary. Each ExtractValuesFromCell method extracts the value of itscell and inserts the value into the container. The internal handler for theUpdate event then uses these values in its internal data access code to updatethe underlying database fields.

 

The ExtractValuesFromCell method of the DropDownListFieldclass extracts the selected value of the DropDownList control and inserts itinto the IDictionary container passed in as its first input argument. Recall thatthe OnDataBindField method inserted a new item with the Value property of -1 into the DropDownList control. This item allows users to select a null valuefor the foreign key field. The ExtractValuesFromCell method checks theSelectedValue property of the DropDownList control before it inserts the iteminto the IDictionary container. If the value is -1 , the method inserts a nullvalue instead:

 

if (ddl.SelectedValue == "-1")

 

Figure 4 shows the code for the ExtractValuesFromCellmethod.

 

public override void ExtractValuesFromCell(IOrderedDictionarydictionary,

                                          DataControlFieldCell cell,

                                          DataControlRowStaterowState,

                                          boolincludeReadOnly)

{

 if (cell.Controls.Count >0)

 {

    DropDownList ddl =cell.Controls[0] as DropDownList;

    if (ddl == null)

        throw newInvalidOperationException("DropDownListField could

                                            notextract control.");

    string dataValueField= ddl.SelectedValue;

    if(dictionary.Contains(DataValueField))

    {

       if (dataValueField== "-1")

           dictionary[DataValueField] = DBNull.Value;

       else

           dictionary[DataValueField] = int.Parse(dataValueField);

    }

    else

    {

       if (dataValueField== "-1")

           dictionary.Add(DataValueField,DBNull.Value);

        else

           dictionary.Add(DataValueField, int.Parse(dataValueField));

    }

 }

}

Figure 4:ExtractValuesFromCell extracts the selected value of the DropDownList controland inserts the value into the IDictionary container.

 

Appearance Properties

The DataControlField class exposes a property of typeStyle named ControlStyle. The DataControlField class internally uses the valueof the ControlStyle property to set the style properties of the server controlthat the DataControlField instance renders. In the case of theDropDownListField class, the ControlStyle property is applied to theDropDownList control that the class contains.

 

The ControlStyle property is not the only styling optionavailable. ASP.NET 2.0 comes with a new feature named Themes. A theme isimplemented as a subfolder under the Themes folder (the name will change toapp_themes in the Beta 2 version). The subfolder must have the same name as thetheme. A theme subfolder consists of one or more skin files and theirrespective image and cascading stylesheet files. Because ASP.NET 2.0 merges allthe skin files of a theme into a single skin file page, developers can use asmany skin files as necessary to organize the theme folder. Themes are assignedto the containing page, not the individual controls.

 

The @Page directive in ASP.NET 2.0 exposes a new attributenamed Theme, which is set to the name of the desired theme. Because all themesare subfolders of the Themes folder, the ASP.NET framework knows where to findthe assigned theme. A skin file includes one or more control skins. A controlskin defines the appearance properties of a class of server controls. Thedefinition of a control skin is very similar to the declaration of an instanceof the control in a page. This does not mean that all properties of a servercontrol can be set in its skin. In general, only the appearance properties canbe included and set in a control skin.

 

If the SkinID property of a control skin is not set, thecontrol skin is treated as the default skin. A default skin is automaticallyapplied to the control instances whose SkinID properties are not set. If theSkinID property of a control skin is set, it will be applied only to thecontrol instances whose SkinID property is set to the same value.

 

The DataControlField class exposes two new properties,SkinID and EnableTheming. It is the responsibility of page developers to setthe SkinID property of the DropDownListField object to the value of the SkinIDproperty of the desired DropDownList control skin. The EnableTheming propertyof the DropDownListField object allows page developers to enable/disabletheming for the DropDownList control server.

 

The InitializeDataCell method sets the SkinID and EnableThemingproperties of the DropDownList control to the values of the SkinID andEnableTheming properties of the DropDownListField object. Themes give pagedevelopers full control over the appearance properties of the DropDownListcontrol that the DropDownListField object renders.

 

Declarative Programming without Writing a Single Line of Code

Page developers can use the DropDownListField classdeclaratively without writing a single line of code! Figure 5 shows a page thatdeclaratively uses an instance of the DropDownListField.

 

<%@ Page Language="C#" Theme="YellowTheme"%>

<%@ Register TagPrefix="custom"Namespace="CustomFields" %>

"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

 

 

        AutoGenerateColumns="false" AllowSorting="true"     DataSourceID="GridViewSource"AutoGenerateEditButton="true"     DataKeyNames="ProductID">                           HeaderText="Product Name"SortExpression="ProductName">                               DataValueField="CategoryID"DataTextField="CategoryName"           DataSourceID="DropDownListSource"SortExpression="CategoryName"           HeaderText="Category Name" NullDisplayText="Unknown"/>                    Runat="Server" SortParameterName="sortExpression"     TypeName="Product" SelectMethod="Select"UpdateMethod="Update" />        ConnectionString="<%$ ConnectionStrings:MyConnectionString%>"     SelectCommand="Select * From Categories" />   Figure 5: Pagedevelopers use the instances of the DropDownListField class declarativelywithout writing a single line of code.   Notice the page doesn t include a single line of C# orVB.NET code; it s all done declaratively. Theming is enabled for the internalDropDownList instance by setting its SkinID property to the SkinID of thecontrol skin defined in the theme named YellowTheme. The YellowTheme is verysimple. It sets the background and foreground colors of the internalDropDownList control to blue and yellow, respectively. However, page developerscan apply their own complex themes to the DropDownList control.   Any type of data source control that presents tabularviews of its underlying data store can be bound to the internal DropDownListcontrol. Page developers do not have to bind both the GridView and the internalDropDownList controls to the same type of data source control. Figure 5 bindsthe GridView control to an instance of the ObjectDataSource class and theinternal DropDownList control to an instance of the SqlDataSource control. TheProduct class used in the ObjectDataSource control is a custom class thatprovides basic database operations, such as Select and Update.  Conclusion In this article you learned how to design and implement anew class named DropDownListField that inherits from the BoundField class. TheDropDownListField class provides built-in support for the following featureswhen the containing row is not in the Edit or Insert state: Extracts the current value of the fields whosenames are the values of the DataValueField (i.e. foreign key field) andDataTextField (i.e. the field with more useful values to users) properties. Displays the current value of the user-friendlyfield as simple text. Raises sort event if sorting is enabled.   The DropDownListField class inherits the above three featuresfrom the BoundField class. The DropDownListField class provides its ownbuilt-in support for the following features when the containing row is in theEdit or Insert state: Extracts all the legal values of the fieldswhose names are the values of the DataValueField (i.e. foreign key field) andDataTextField (i.e. the field with more useful values to users) properties. Instantiates an instance of the DropDownListcontrol. Populates the corresponding DropDownList controlwith the legal values. Configures the DropDownList control to displaythe current value of the user-friendly field. Extracts the selected value of the DropDownListand inserts the value into the appropriate IDictionary object. The extractedvalue is used in update data operations. Allows users to select a null value for itsrespective foreign key field. Supports new ASP.NET 2.0 themes.   The sample code accompanying this article is available fordownload.  Dr. Shahram Khosravi is aSenior Software Engineer with Schlumberger Information Solutions (SIS), theleading provider of software solutions for the oil and gas industry. Hespecializes in ASP.NET, XML Web services, .NET technologies, 3D ComputerGraphics, HI/Usability, and Design Patterns. Shahram has extensive expertise inwriting ASP.NET custom server controls. He has more than 10 years of experiencein object-oriented programming. Reach him at mailto:[email protected].      

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like