Custom Data Binding for Server Controls
 
Published: 09 Mar 2004
Unedited - Community Contributed
Abstract
Data binding is as easy as 1, 2 and 3 for the page developer. However, creating server controls that will accept data from a custom data source can be tricky because few have an idea where to begin. Justin Lovell demonstrates how to do custom data binding.
by Justin Lovell
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 34746/ 70

Introduction

First Article – The Basics of Building Server Controls
Second Article – Reusing and Creating Server Controls

Third Article – Building Composite Server Controls

Fourth Article – The Basics of Templates in Server Controls (previous article)

 

Download the entire source demonstrated in this article

 

For every control developer, providing a simple interface for the page developer to data bind data to a custom server control is far harder than what you may anticipate. In this article, I will give a simple solution; yet, an effective solution for the control developer so that the custom data binding is no longer one of those silly worries.

 

Also in this article, I will demonstrate the data binding by building a simple list control. I will not go into much depth because this article will become too verbose. The objective of the article is to demonstrate the data binding technique – it is not here to show a complete server control.

 

The first question that control developers ask me is – “Where would I begin to create a custom data binding solution to my server control?” For some of you out there, you might have a feint idea where to begin – the starting point is to iterate through a collection.

 

To simply put it down, ASP.NET focuses on binding to controls that implement the ICollection interface. The only reason that I made mention of this is for anyone that wants to create a custom data source (create their own collection with their own objects), ensure that your object implements the ICollection interface – as long the objects inside the collection expose properties. I will put more emphasize on this a later stage in the article.

 

But for now, we will only bind to any object that implements the IEnumerable interface.

Convert Energy – You cannot destroy it

In Physical Science (if you did it to grade nine), you would have learnt that you cannot lose energy – you can only convert the energy to another form. For example, when a fire burns, it gives off heat. That heat is then transferred into a light energy.

 

Similarly, when you develop a control that data source that is given should be converted to an offline version. The only reason for the data source to be converted to an offline version is so that during post backs, that data can be used by your control to re-populate itself.

 

ADO.NET fits perfectly into this situation because, in summary, ADO.NET provides an offline abstract view of the data. Specifically, the DataTable will be best to be utilized because it is easily serialized into an XML mark-up (text).

 

So, what I have told you so far, you will need to do the following:

 

  1. Get an object in the data source that implements the ICollection interface.
  2. Convert the data source into a DataTable so that it may be used for every post back.

However, I have to look at my review of what must be done and pause at point one and look at point two… then the thought that the page developer might want to feed us a data reader. “Oh no,” one might exclaim.

 

That does not really affect us because we can skip straight into point two. We can cleverly convert a data reader into a DataTable within a flash. The code sample listed below takes the data reader and finds a DataTable make-up (that is, a schema table):

 

public object DataSource {
   get { return ViewState["DataSource"]; }
   set {
      if (DataSource is DataTable)
         ViewState["DataSource"] = value;
      else if (value is IDataReader) {
         IDataReader reader = (IDataReader)value;
         // gets the column based table (schema)
         DataTable table = reader.GetSchemaTable();
 
         while (reader.Read()) {
            DataRow row = table.NewRow();
            table.Rows.Add(row);
 
            foreach (DataColumn column in table.Columns)
               row[column.ColumnName] = reader[column.ColumnName];
         }

         DataSource = table;
      }
   }
}
Pretty much down hill from here

There is just one thing that must be considered for the data binding. That is to extract the data from the objects that you may receive from the collection. This can be simply done by falling back onto the famous DataBinder class:

 

DataBinder.Eval(obj, "ColumnName");

 

Depending on the type of object (“obj” in the above code example), it will determine the amount of reflection that will occur to get the value. If it is a DataRow class or DataRowView class (from the DataTable and DataView, respectively, the less reflection that will occur; however, for any other object, more reflection will be involved to get the value from the property (the name of the property is determined by the “ColumnName” parameter).

 

With all that said, you should deal with the data that will be used. For example, your control only needs to deal with two columns and a data reader is issued to us with ten columns, then we only need to save the two columns of every record to the internal data source. That will reduce the view state size greatly.

 

You might have a server control like the DataGrid control (or other template based server control). That will pose an additional obstacle that you may run into is that you want to save all the information that is presented in an object. This problem can be easily overcome by using Reflection. I will leave it at that point because it goes way out of scope of this article… in fact, you may have a look at the following articles:

 

Understanding Reflection – Part One

Understanding Reflection – Part Two

 

The last article is not needed for data binding to an entire collection; however, you will get a deeper understanding of .NET from this article. Just a few tips though:

 

  1. Look at listing properties in that object that you can read.
  2. Do not consider looking at objects and properties that their access level is not “public”. Your control will run super slow if you do so.
Adding something more essential to our Knowledge

Now we are going to apply our knowledge into practise. I will develop a simple server control that will accept a title field and a description field from the data binding. The only way that this list control can be populated is through data binding. I have provided a simple collection editor and such in this server control because this is a FAQ at the ww.asp.net forums.

 

Now, let’s get the item server control on the roll. Basically, I will create a composite server control with a title and a description labels. Here is the basic code for the child and the parent controls (will not be explained because previous articles have gone over these aspects):

 

public class TitleDescriptionItem : Control, INamingContainer {
   private DataRow pDataItem;
   protected Label cTitle = new Label();
   protected Label cDescription = new Label();


   // will be used later with the data binding logic
   public DataRow DataItem {
      get { return pDataItem; }
      set { pDataItem = value; }
   }


   // provides a wrapper property for the "title" control
   public string Title {
      get { return cTitle.Text; }
      set { cTitle.Text = value; }
   }


   // provides a wrapper property for the "description" control
   public string Description {
      get { return cDescription.Text; }
      set { cDescription.Text = value; }
   }


   protected override void CreateChildControls() {
      cTitle.Font.Bold = true;
      cTitle.ForeColor = Color.Blue;

      Controls.Add(cTitle);
      Controls.Add(new LiteralControl("<br>"));
      Controls.Add(cDescription);
   }
     
   // TODO: Binding logic (listed later in article)
}


public class TitleDescription : Control, INamingContainer {
   // TODO: Binding logic (listed later in article)
}

 

Now to spice up the TitleDescription control and the TitleDescriptionItem control. I am just going to add a few attributes. The objective is to allow a collection editor to add items to the TitleDescription control. Firstly, let’s do the simple modification to TitleDescriptionItem control:

 

[DefaultProperty("Title")]
public class TitleDescriptionItem : Control, INamingContainer {
   // ...
}

 

In the above code, it will give will the collection editor a “name” for the item. Now, to add a few more stuff to the TitleDescription control:

 

[ParseChildren(true, "Items")]
[PersistChildren(false)]
public class TitleDescription : Control, INamingContainer {
   private ControlCollection pItems;


   [NotifyParentProperty(true)]
   [PersistenceMode(PersistenceMode.InnerProperty)]
   [DesignerSerializationVisibility
      (DesignerSerializationVisibility.Content)]
   public ControlCollection Items {
      get { return pItems; }
   }


   // ...


   public TitleDescription() {
      pItems = new ControlCollection(this);
   }
}

 

The ParseChildren attribute and the PersistChildren attribute are there to tell the ASP.NET parser that the stuff that will appear between the control’s tags are not part of the control – it says to parse all the information off to the Items property.

 

And finally, the attributes on the Items property says to the ASP.NET parser that the data should appear in inner tags – yet seen as a property. The above will allow the syntax on the ASP.NET page:

 

<jlc:TitleDescription id="TitleDescription1" runat="server">
   <jlc:TitleDscription Title="Me" Description="Hello, my name is Justin" />
   <jlc:TitleDscription Title="You" Description="Hi, I am ..." />
</jlc:TitleDescription>

 

You could read a blog post that I have already made and ask any further questions there. Now that I shed some light on a rather simple matter, yet can be confusing, let’s get back onto track about data binding.

Binding Logic for the TitleDescription control

Now I am going to develop the data binding logic of the TitleDescription control. It will be explained after the listing:


private string pDataTitleField;
private string pDataDescriptionField;


// indicates which column the title value resides in
public string DataTitleField {
   get { return pDataTitleField; }
   set {
      if ((value == null) && (value.Length == 0))
         throw new ArgumentNullException();
      else
         pDataTitleField = value;
   }
}


// indicates which column the description value resides in
public string DataDescriptionField {
   get { return pDataDescriptionField; }
   set {
      if ((value == null) && (value.Length == 0))
         throw new ArgumentNullException();
      else
         pDataDescriptionField = value;
   }
}


public object DataSource {
   get { return ViewState["DataSource"]; }
   set {
      if (value is DataTable) {
         // finally accepts a data table – through conversion or straight 
         // from the page developer. However, we take precaution to that it is trimmed
         DataTable externalTable = (DataTable)value;
         DataTable internalTable = new DataTable();
         Hashtable columns = FindDataColumns();
 
         foreach (string key in columns.Keys)
            internalTable.Columns.Add(new DataColumn(key));
 
         foreach (DataRow eRow in externalTable.Rows) {
            DataRow iRow = internalTable.NewRow();
            internalTable.Rows.Add(iRow);
 
            foreach (DataColumn iColumn in internalTable.Columns)
               iRow[iColumn] = eRow[(string)columns[iColumn.ColumnName]];
         }
 
         ViewState["DataSource"] = internalTable;
      }
      else if (value is DataSet) {
         // deals with data sets and selects the first data table
         if (((DataSet)value).Tables.Count == 0)
            throw new Exception("The dataset does not have any tables.");
         else
            DataSource = ((DataSet)value).Tables[0];
      }
      else {
         // we are now dealing with more advanced objects.
         // Additional things need to be setup to ensure best performance.
         DataTable table = new DataTable();
         Hashtable columns = FindDataColumns();
 
         table.Columns.Add(new DataColumn("Title"));
         table.Columns.Add(new DataColumn("Description"));
 
         if (value is IDataReader) {
            // this deals with all the data readers
            int nextColumn = 0;
            IDataReader reader = (IDataReader)value; 
 
            while (reader.Read()) {
               DataRow row = table.NewRow();
               table.Rows.Add(row);
 
               foreach (string key in columns.Keys) {
                  if (columns[key] == null) {
                     row[key] = reader[nextColumn]; 
                     nextColumn++;
                  }
                  else
                     row[key] = reader[columns[key].ToString()]; 
               }
            } 
         }
         else if (value is IEnumerable) {
            // this deals with the data views, custom collections and XML nodes.
            // The problem is that this may be more time consuming
            foreach (object obj in (IEnumerable)value) {
               DataRow row = table.NewRow();
               table.Rows.Add(row);
 
               foreach (string key in columns.Keys)
                  row[key] = DataBinder.Eval(obj, columns[key].ToString()); 
            }
         }
         else
            // we cannot accept the data source – we do not recognize it
            throw new Exception("Invalid Data Source type.");
 
         DataSource = table;
      }
   }
}


private Hashtable FindDataColumns() {
   Hashtable columns = new Hashtable();
 
   // use the default column if none selected
   columns.Add("Title", DataTitleField);
   columns.Add("Description", DataDescriptionField);
 
   return columns;
}

 

Right – there are plentiful of comments in the above figure. I do not think that anyone will have a problem with it… but I have the feeling that I should add a simple summary. Essentially, what happens is that the data source is recycled on the fly until it reaches a DataTable state. It sort of bubbles a conversion – the above can even bind to XML – XMLNodes to be exact for now. But the DataTable class does not have to be the end objective of every data binding scenario: it can be XML itself – it all depends on you as the developer.

 

With the above code sample, it is picky on the columns that it does save – the reason is to down size view state. We could be given a data reader with ten columns and we only need two of them… why save the rest when we are not going to use it? The data in the view state can get tremendously big because we are saving data that we do not need in the first place.

 

However, if you do have a template based control – there is no telling what data is required. To combat this, you will unfortunately have to save the entire DataTable to the view state – and that is the very reason why controls like the DataGrid control and the Repeater control can have a huge footprint on the view state.

 

There is one thing missing from the TitleDescription control – and that is the DataBind method. If you have a quick breeze through the documentation, you would have noticed that there is a DataBind method on the Control class. The DataBind method is marked virtual so we can easily override it:

 

public override void DataBind() {
   if (DataSource != null) {
      foreach (DataRow row in ((DataTable)DataSource).Rows) {
         TitleDescriptionItem item = new TitleDescriptionItem();
 
         Items.Add(item);
         item.DataItem = row;
         item.DataBind();
      }
   }
   else
      throw new Exception("DataSource is null.");
}

 

That code seems simple enough – assign a sub-data source to the new TitleDescription control and bind it. Simple as that and the binding logic of that control falls onto the next page.

Binding logic of the TitleDescriptionItem control

We are eventually ending our ride down the hill. Basically, all the work has been done in the “basic” signature of the control. There are only two things that have to be done now.

 

One of the things that have to be done is override the DataBind method:

 

public override void DataBind() {
   if (DataItem != null) {
      Title = DataItem["Title"].ToString();
      Description = DataItem["Description"].ToString();
   }
}

 

Basically, we know what our transformed data source has – in other words, we know the columns that are located in the DataRow. That is about it… nothing else must be done to successfully data bind.

 

The other thing that is left on the agenda is performance. You might have noticed that there will be additional view state saved. That view state is from the controls inside the TitleDescriptionItem control. Because we do not need them to handle view state, we simply switch it off by overriding the EnableViewState property in the TitleDescriptionItem control:

 

public override bool EnableViewState {
   get { return false; }
   set { }
}
An Ending Note

All I did in this article is demonstrating the basics of data binding and some collection management in server controls. Obviously, you can add more binding features like XML and such but that will take some code. You might not even like the idea of the internal data source being a DataTable but you are free to turn to another “medium”… as long as it can be an offline abstract of the data (like XML).

 

To summarize the article:

 

  1. Manipulate the given data in the DataSource property into one data type. For example, transform it into a DataTable.
  2. Use the transformed data to populate the sub-controls.
  3. Bind the sub-data to the
  4. Save the transformed data into view state so it can be used during post backs.

 

You might want to watch Scott Mitchell for a while because he is also going to roll out a similar article at the ASP.NET Dev Centre.

 

Happy Programming!



User Comments

Title: Great Article!   
Name: NiQuil
Date: 2010-01-18 9:53:29 AM
Comment:
This helped me a lot! I am totally new to programming and this has been a great tutorial for me.
I am creating my own control library with databound controls, with XHTML output and taking into account the 125 (very) strict rules for the Dutch Government Guidelines for the web.

I noticed this is a Visual studio 2003 (?) project since I had to convert it to my 2008.

Isthis still "the way to go" on this subject, or have new developments, maybe on Microsoft's part, on this subject seen the light of day?

Kind regards,

NiQuil
Title: Why Doesn't It Work   
Name: Marc
Date: 2009-06-04 3:09:50 PM
Comment:
My TitleDescriptionItems never display. CreateChildControlsnever gets hit. Why?
Title: DataSourceID   
Name: Andrew
Date: 2008-06-20 11:11:26 AM
Comment:
Great article, however I was wondering how would one implement the DataSourceID and DataMember properties?

Product Spotlight
Product Spotlight 





Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-20 1:32:07 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search