5,695,118 members and growing! (14,026 online)
Email Password   helpLost your password?
Web Development » ASP.NET Controls » General     Intermediate

Data Binding in WebControls

By Shaun Wilde

An article on how to actually support data binding in your WebControl such that you can manipulate them in the Properties window.
C#.NET 1.0, Win2K, WinXP, Windows, .NET, ASP.NET, Visual Studio, Dev

Posted: 4 Oct 2002
Updated: 4 Oct 2002
Views: 206,616
Bookmarked: 130 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
44 votes for this Article.
Popularity: 7.71 Rating: 4.69 out of 5
1 vote, 2.6%
1
0 votes, 0.0%
2
1 vote, 2.6%
3
5 votes, 13.2%
4
31 votes, 81.6%
5

Introduction

Over the past few months I have been creating a variety of WebControls some of which have needed access to a DataSource such as a DataSet in order to work properly. Adding a DataSource to your WebControl is relatively easy however the tricky part is when you need to interact with that property in the Properties window e.g. in the same manner as you would for a DropDownList control etc. Now if you are lucky and you get the query right in MSDN you will come across the following article Implementing a Web Forms Data-Bound Control Designer. However you will notice like I did that the sample just does not work as advertised. While attempting to debug it I posted a message on the microsoft.public.dotnet.framework.aspnet.webcontrols newsgroup and I received the following answer from Mike Moore "DataSource property and DataBinding thread" which eventhough was not 100% what I wanted put me on the right track. However an interesting comment, see the supplied source for the IDataSourceProvider interface, was in the source supplied by Mike Moore that made me look further into how to add data binding capabilities to a WebControl such that you can manipulate them in the properties window.

What I intend to do is explain each step of the process and describe what you actually need to do support data binding in a WebControl against what the MSDN sample says you have to do. For this demonstration I created a very simple control that does nothing other than expose properties that can be used to bind to a data source, a table within that data source and finally some fields that reside in that table in the same manner as a listbox.

A Simple Data Bound Control and its Designer

The source available for download contains the code for SimpleDataBoundControl the class view of which is shown below.

Now in order to support data binding in the properties window we need to add a designer to the control. It is this designer that does all the hard work while you are working with your control at design time. The source to the final implementation of SimpleDataBoundControlDesigner is also supplied in the download. To link the WebControl with its designer you use the class attribute Designer, since I prefer to keep the control and its related designer together in the same library I find it easier to use the typeof version.

[DefaultProperty("DataSource"),
ToolboxData("<{0}:SimpleDataBoundControl runat="server"></{0}:SimpleDataBoundControl>"),
Designer(
    typeof(ManyMonkeys.Web.ControlLibrary.Design.SimpleDataBoundControlDesigner))]
public class SimpleDataBoundControl : System.Web.UI.WebControls.WebControl , 
    INamingContainer
{
    ...
}

The DataSource Property

DataSource is the common member name for a property that is used to bind to a data source such as a DataSet. Before we can bind the other properties we need to make sure that this property is set up correctly. We use the designer to allow us to represent DataSource which is an object type property as a string type. To the designer class we add a DataSource property that is of type string and which looks like the extract below.

public class SimpleDataBoundControlDesigner : ...
{
    ...

    public string DataSource 
    {
        get 
        {
            DataBinding binding = DataBindings["DataSource"];
            if (binding != null) 
                return binding.Expression;
            return string.Empty;
        }
        set 
        {                
            if ((value == null) || (value.Length == 0)) 
                base.DataBindings.Remove("DataSource");
            else 
            {
                DataBinding binding = DataBindings["DataSource"];
                if (binding == null) 
                    binding = new DataBinding("DataSource", typeof(IEnumerable), 
                        value);
                else 
                    binding.Expression = value;
                DataBindings.Add(binding);
            }

            OnBindingsCollectionChanged("DataSource");
        }
    }    
}

We also need to add a type converter called DataSourceConverter to the above property so that it will correctly enumerate the available data sources that exist on the WebForm and present them in the Properties window as a combobox. To add this converter we need to override the PreFilterProperties method and add the TypeConverter attribute dynamically to the DataSource property at Design runtime.

public class SimpleDataBoundControlDesigner : ...
{
    ...

    protected override void PreFilterProperties(IDictionary properties) 
    {
        base.PreFilterProperties(properties);
        PropertyDescriptor prop = (PropertyDescriptor)properties["DataSource"];
        if(prop!=null)
        {
            AttributeCollection runtimeAttributes = prop.Attributes;
            // make a copy of the original attributes but make room for one extra 

            // attribute ie the TypeConverter attribute

            Attribute[] attrs = new Attribute[runtimeAttributes.Count + 1];
            runtimeAttributes.CopyTo(attrs, 0);
            attrs[runtimeAttributes.Count] = new 
                TypeConverterAttribute(typeof(DataSourceConverter));
            prop = TypeDescriptor.CreateProperty(this.GetType(), "DataSource", 
                typeof(string),attrs);
            properties["DataSource"] = prop;
        }            
    }
}

The final step in implementing the DataSource property in SimpleDataBoundControl is to include the DesignerSerializationVisibility attribute such that the data source is saved in the HTML in the following style, DataSource="<%# dataSet11%> rather than as DataSource="dataset11".

public class SimpleDataBoundControl : ...
{
    ...

    private object _dataSource=null;
    [
    Bindable(true),
    Category("Data"),
    DefaultValue(null),
    Description("The datasource that is used to populate the list with items."),
    // needs to be hidden otherwise we don't save the property for some reason

    DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
    ]
    public object DataSource
    {
        get { return _dataSource; }
        set
        {
            if ((value == null) || (value is IListSource) || (value is IEnumerable)) 
                _dataSource = value;
            else
                throw new Exception("Invalid datasource.");
        }
    }
}

Now we should have a control for which we can select a data source from a list of available data sources.

The IDataSourceProvider interface

As you can see there has been no need to implement the IDataSourceProvider interface as described in the MSDN sample and alluded to by the sample provided via the newsgroup. In fact if you read the MSDN documentation for IDataSourceProvider you will see that is only required for when you use the DataMemberConverter and DataFieldConverter type converters. The fact that the sample doesn't use IDataSourceProvider will explain why the given code does not work when we try to implement the properties that use the DataMemberConverter and DataFieldConverter as it was probably never able to be tested and debugged.

A designer that implements the IDataSourceProvider interface requires two methods to be supplied:

  • GetSelectedDataSource which appears to be only used by DataMemberConverter
  • and
  • GetResolvedSelectedDataSource which appears to be only used by DataFieldConverter

Since it is required to implement the properties that use DataMemberConverter before we implement those that use DataFieldConverter we will deal with the respective method implementations of IDataSourceProvider at that time.

The DataMember Property

The DataMember is used to select a particular table from within a supplied data source such as a DataSet or if it is empty then the control should use the first available table or DataView. All of the work to implement a DataMember property such that in the Properties window it will be represented as a combobox with a list of available tables is done in the designer class which in this case is SimpleDataBoundControlDesigner.

First we need to create a property for DataMember that we will use to attach a type converter attribute.

public class SimpleDataBoundControlDesigner : ...
{
    ...

    public string DataMember
    {
        get
        {
            return ((SimpleDataBoundControl)this.Component).DataMember;
        }
        set
        {
            ((SimpleDataBoundControl)this.Component).DataMember = value;
        }
    }

}

and then in PreFilterProperties we attach a type converter, in this case a DataMemberConverter, in the same manner as we did for the DataSource property.

public class SimpleDataBoundControlDesigner : ...
{
    ...

    protected override void PreFilterProperties(IDictionary properties) 
    {
        ...

        prop = (PropertyDescriptor)properties["DataMember"];
        if(prop!=null)
        {
            AttributeCollection runtimeAttributes = prop.Attributes;
            // make a copy of the original attributes but make room for one extra 

            // attribute ie the TypeConverter attribute

            Attribute[] attrs = new Attribute[runtimeAttributes.Count + 1];
            runtimeAttributes.CopyTo(attrs, 0);
            attrs[runtimeAttributes.Count] = new TypeConverterAttribute(
                typeof(DataMemberConverter));
            prop = TypeDescriptor.CreateProperty(this.GetType(), "DataMember", 
                typeof(string),attrs);
            properties["DataMember"] = prop;
        }            

    }
}

Now for the DataMemberConverter to work correctly we need to implement the IDataSourceProvider interfaces and in particular the GetSelectedDataSource() method. Unfortunately the implementation provided in the MSDN sample does not work when you use a DataSet and this is because a DataSet does not support IEnumerable however it does support IListSource. So we can add this interface into the check in the MSDN supplied code (see comments below).

public class SimpleDataBoundControlDesigner : ...
{
    ...

    object IDataSourceProvider.GetSelectedDataSource() 
    {
        object selectedDataSource = null;
        string dataSource = null;

        DataBinding binding = DataBindings["DataSource"];
        if (binding != null) 
        {
            dataSource = binding.Expression;
        }

        if (dataSource != null) 
        {
            ISite componentSite = Component.Site;
            if (componentSite != null) 
            {
                IContainer container = (IContainer)componentSite.GetService(
                    typeof(IContainer));

                if (container != null) 
                {
                    IComponent comp = container.Components[dataSource];
                    // Added the IListSource test as DataSet doesn't 

                    // support IEnumerable

                    if ((comp is IEnumerable) || (comp is IListSource)) 
                    {
                        selectedDataSource = comp;
                    }
                }
            }
        }

        return selectedDataSource;
    }

    IEnumerable IDataSourceProvider.GetResolvedSelectedDataSource() 
    {
        return null;
    }

}

We have only implemented a simple implementation for GetResolvedSelectedDataSource method as it does not appear to be needed for the DataMemberConverter to work.

Now we should have a control for which we can select a table, using a listbox, from a list of available tables for a selected data source.

The DataTextField and DataValueField Properties

The DataTextField and DataValueField properties are to be used to select a particular field from the default or selected table from a preselected data source. Again all the work required to allow us to select from a list of available fields is also done in the designer class. Again we add a property that is used to attach the required type converter, which is DataFieldConverter, to and we also add the type converter to the attributes in the PreFilterProperties method.

public class SimpleDataBoundControlDesigner : ...
{
    ...

    public string DataTextField
    {
        get
        {
            return ((SimpleDataBoundControl)this.Component).DataTextField;
        }
        set
        {
            ((SimpleDataBoundControl)this.Component).DataTextField = value;
        }
    }

    public string DataValueField
    {
        get
        {
            return ((SimpleDataBoundControl)this.Component).DataValueField;
        }
        set
        {
            ((SimpleDataBoundControl)this.Component).DataValueField = value;
        }
    }

    protected override void PreFilterProperties(IDictionary properties) 
    {
        ...

        prop = (PropertyDescriptor)properties["DataTextField"];
        if(prop!=null)
        {
            AttributeCollection runtimeAttributes = prop.Attributes;
            Attribute[] attrs = new Attribute[runtimeAttributes.Count + 1];
            // make a copy of the original attributes but make room for one extra 

            // attribute ie the TypeConverter attribute

            runtimeAttributes.CopyTo(attrs, 0);
            attrs[runtimeAttributes.Count] = new TypeConverterAttribute(
                typeof(DataFieldConverter));
            prop = TypeDescriptor.CreateProperty(this.GetType(), "DataTextField", 
                typeof(string),attrs);
            properties["DataTextField"] = prop;
        }            

        prop = (PropertyDescriptor)properties["DataValueField"];
        if(prop!=null)
        {
            AttributeCollection runtimeAttributes = prop.Attributes;
            Attribute[] attrs = new Attribute[runtimeAttributes.Count + 1];
            // make a copy of the original attributes but make room for one extra 

            // attribute ie the TypeConverter attribute

            runtimeAttributes.CopyTo(attrs, 0);
            attrs[runtimeAttributes.Count] = new TypeConverterAttribute(
                typeof(DataFieldConverter));
            prop = TypeDescriptor.CreateProperty(this.GetType(), "DataValueField", 
                typeof(string),attrs);
            properties["DataValueField"] = prop;
        }            
    }


}

Now that all is needed is the GetResolvedSelectedDataSource method for IDataSourceProvider to be implemented. The MSDN sample supplies the following implementation.

IEnumerable IDataSourceProvider.GetResolvedSelectedDataSource() 
{
    return (IEnumerable)((IDataSourceProvider)this).GetSelectedDataSource();
}

However as already mentioned a DataSet does not support IEnumerable and so the above code causes an exception to be thrown and does not work. For this to work for a DataSet we need to drill down to the DataViews that exist in a DataSet and we also need to choose the DataView based on a table alreade preselected in the DataMember property. The following implementation has been tested to work with a DataSet.

public class SimpleDataBoundControlDesigner : ...
{
    ...

    IEnumerable IDataSourceProvider.GetResolvedSelectedDataSource() 
    {
        object selectedDataSource = 
            ((IDataSourceProvider)this).GetSelectedDataSource();

        DataView dataView = null;

        if (selectedDataSource is DataSet)
        {
            // find the correct table or if non set the look up the first table

            DataSet dataSet = (DataSet)selectedDataSource;
            DataTable dataTable = null;

            if ((DataMember != null) && (DataMember.Length>0))
                dataTable = dataSet.Tables[DataMember];
            else
                dataTable=dataSet.Tables[0];

            // we found a table so lets just get its default view

            if (dataTable!=null) 
            {
                dataView = dataTable.DefaultView;
            }
        }
        else if (selectedDataSource is DataTable)
        {
            // just get the default view since we have just been given a table

            dataView = ((DataTable)selectedDataSource).DefaultView;
        }
        else if (selectedDataSource is IEnumerable)
        {
            // might as well just see if it will cast as this is 

            // the MS sample's default

            return selectedDataSource as IEnumerable; 
        }

        return dataView as IEnumerable;
    }

}

Now we should have a control for which we can choose a field from a selected table.

The DesignTimeData Class

The DesignTimeData class is found in the .NET Framework and it can be used to implement the methods of IDataSourceProvider interface. The code below shows a much simpler implementation of the methods required by an implementation of IDataSourceProvider interface using the DesignTimeData class.

public class SimpleDataBoundControlDesigner : ...
{
    ...

    object IDataSourceProvider.GetSelectedDataSource() 
    {
        DataBinding binding;
        binding = this.DataBindings["DataSource"];
        if (binding != null)
            return DesignTimeData.GetSelectedDataSource(this.Component, 
                binding.Expression);
        return null;
    }

    IEnumerable IDataSourceProvider.GetResolvedSelectedDataSource() 
    {
        DataBinding binding;
        binding = this.DataBindings["DataSource"];
        if (binding != null)
            return DesignTimeData.GetSelectedDataSource(this.Component, 
                binding.Expression, this.DataMember);
        return null;
    }

}    

Comments

Please take the time to vote for this article and/or to comment about it on the boards below. All suggestions for improvements will be considered.

History

  • 02/10/92 - Initial Version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Shaun Wilde


All articles are supplied as-is, as a howto on a particular task that worked for me in the past. None of the articles are supposed to be out-of-the-box freeware controls and nor should they be treated as such. Caveat emptor.

Been involved in programming from the early '80s. First on my Spectrum, then an Amstrad. Did lots of Fortran while at University before becoming involved in Forth, Prolog, Pascal, Occam, C and eventually C++. Eventually started programming on the Windows platform using Borland’s OWL framework, during my postgraduate years. When I started work in '94, I learnt MFC followed by COM and ATL and never looked back. Now working exclusively in .NET, working on WinForms, ASP.NET and the Compact Framework using C# and VB.NET. Using every bit of the .NET framework I can such as WebServices and writing my own controls for my .NET Portal, and for the Compact Framework. Still lots I don't know, but the end of the universe hasn't happened yet - I wonder if I'll have time?

I was a permanent employee for a number of companies until Dec 2000 before going independent after the DotCom I ws involved with went DotBomb. Now supplying consulting services to various institutions in London and surrounding regions i.e. UK Smile.

Now started using BizTalk and the latest .NET framework - yeha.
Occupation: Architect
Location: United Kingdom United Kingdom

Other popular ASP.NET Controls articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 25 of 35 (Total in Forum: 35) (Refresh)FirstPrevNext
QuestionHow can I get this to work?memberstodgey13:41 7 Nov '08  
AnswerRe: How can I get this to work?memberShaun Wilde21:49 7 Nov '08  
GeneralIn ASP.NET 2.0...memberthany.org23:32 9 May '06  
GeneralRe: In ASP.NET 2.0...memberShaun Wilde0:44 10 May '06  
GeneralDesign Time Data SupportmemberHNaeem23:15 3 Aug '05  
GeneralRe: Design Time Data SupportmemberShaun Wilde13:50 4 Aug '05  
GeneralDataSource is nullmemberThiago H M Fernandes12:24 29 Jul '05  
GeneralRe: DataSource is nullmemberShaun Wilde13:40 29 Jul '05  
GeneralRe: DataSource is nullmemberThiago H M Fernandes14:08 29 Jul '05  
GeneralProperty Builder UpdatesmemberHNaeem3:12 20 Jul '05  
GeneralRe: Property Builder UpdatesmemberShaun Wilde13:37 29 Jul '05  
GeneralDatasetmemberHNaeem3:03 20 Jul '05  
GeneralRe: DatasetmemberShaun Wilde13:42 29 Jul '05  
GeneralProperty BuildermemberHNaeem4:27 19 Jul '05  
GeneralRe: Property BuildermemberShaun Wilde9:01 19 Jul '05  
Generalcontrol disappear when run web applicationmemberNisarat23:38 12 Jun '05  
GeneralRe: control disappear when run web applicationmemberShaun Wilde14:27 13 Jun '05  
GeneralBinding Data with control textboxmemberNisarat23:54 17 May '05  
GeneralRe: Binding Data with control textboxmemberShaun Wilde9:04 18 May '05  
GeneralDataMember is not saving at design timememberKondala Rao23:50 16 Mar '05  
GeneralRe: DataMember is not saving at design timememberShaun Wilde8:58 18 May '05  
GeneralHow to do it in user controlsussIKdev7:37 21 Oct '04