Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Creating a custom DataSourceControl with full design time support

4.50/5 (11 votes)
19 Jun 20078 min read 1   984  
An article on creating a DataSourceControl with full design time support

Sample Image - CustomDataSourceDesigner.png

Introduction

This article shows how to create a custom DataSourceControl and how to add full design time support to it.

Background

This article assumes that you're familiar with DataSourceControls and that you know how design time infrastructure works. If this is not the case, take a look to the following articles.

For DataSourceControls:

For design time infrastrucutre:

Creating the custom DataSourceControl

The data source we're going to code will be able to retrieve data but not modify it. It supports the Select operation only. It will be similar to ObjectDataSource, but only to retrieve data. It will have a TypeName property that will hold the name of a class and a SelectMethod property that will hold the method to call in that class. To avoid writing a lot of code, we'll call static methods only. We'll also have a collection of parameters to pass to the SelectMethod (SelectParameters). I'll explain the main tasks to perform when creating a DataSourceControl, but I won't explain in detail what a method or a property does. The code should have enough comments in the complex areas for you to be able to follow me.

The first thing to do when implementing a DataSourceControl is to choose how many DataSourceViews we're going to have and code the IDataSource related methods. In this sample, we'll have one view only:

C#
public class CustomDataSource : DataSourceControl
{
 
    protected static readonly string[] _views = { "DefaultView" };
 
    protected CustomDataSourceView _view;
 
 
    protected override DataSourceView GetView(string viewName)
    {
        if ((viewName == null) || ((viewName.Length != 0) && 
            (String.Compare(viewName, "DefaultView", 
            StringComparison.OrdinalIgnoreCase) != 0))) 
        {
            throw new ArgumentException("An invalid view was requested", 
                "viewName");
        }
 
        return View;
    }
 
    protected override ICollection GetViewNames()
    {
        return _views;
    }
 
    protected CustomDataSourceView View
    {
        get
        {
            if (_view == null) {
                _view = new CustomDataSourceView(this, _views[0]);
                if (base.IsTrackingViewState) {
                    ((IStateManager)_view).TrackViewState();
                }
            }
            return _view;
        }
    }
}

As the CustomDataSourceView is the class that does all of the job, the best approach is to store the properties in that class. However, we need to expose those properties in the CustomDataSource class to let the user modify them in the property grid. So, we need to add this to the CustomDataSource class:

C#
[Category("Data"), DefaultValue("")]
public string TypeName
{
    get { return View.TypeName; }
    set { View.TypeName = value; }
}
 
[Category("Data"), DefaultValue("")]
public string SelectMethod
{
    get { return View.SelectMethod; }
    set { View.SelectMethod = value; }
}
 
[PersistenceMode(PersistenceMode.InnerProperty), Category("Data"), 
    DefaultValue((string)null), MergableProperty(false), 
    Editor(typeof(ParameterCollectionEditor), 
    typeof(UITypeEditor))]
public ParameterCollection SelectParameters
{
    get { return View.SelectParameters; }
 }

And add this to the CustomDataSourceView class:

C#
public class CustomDataSourceView : DataSourceView, IStateManager
{
    protected bool _tracking;
    protected CustomDataSource _owner;
    protected string _typeName;
    protected string _selectMethod;
    protected ParameterCollection _selectParameters;
 
    public string TypeName
    {
        get
        {
            if (_typeName == null) {
                return String.Empty;
            }
            return _typeName;
        }
        set
        {
            if (TypeName != value) {
                _typeName = value;
                OnDataSourceViewChanged(EventArgs.Empty);
            }
        }
    }
 
    public string SelectMethod
    {
        get
        {
            if (_selectMethod == null) {
                return String.Empty;
            }
            return _selectMethod;
        }
        set
        {
            if (SelectMethod != value) {
                _selectMethod = value;
                OnDataSourceViewChanged(EventArgs.Empty);
            }
        }
    }
 
    public ParameterCollection SelectParameters
    {
        get
        {
            if (_selectParameters == null) 
            {
                _selectParameters = new ParameterCollection();
                    _selectParameters.ParametersChanged += 
                new EventHandler(ParametersChangedEventHandler);
                if (_tracking) 
                {
                    ((IStateManager)_selectParameters).TrackViewState();
                }
            }
            return _selectParameters;
        }
    }
 
    protected void ParametersChangedEventHandler(object o, EventArgs e)
    {
        OnDataSourceViewChanged(EventArgs.Empty);
    }
 
    public CustomDataSourceView(CustomDataSource owner, string name)
        : base(owner, name)
    {
        _owner = owner;
    }
 }

Note that when a property changes, the OnDataSourceViewChanged method is called to force a re-bind. Also note that the CustomDataSourceView class implements the IStateManager to support custom view state management. In this case, we use it to save SelectParameters. The state management in the CustomDataSource class is:

C#
protected override void LoadViewState(object savedState)
{
    Pair previousState = (Pair) savedState;

    if (savedState == null) 
    {
        base.LoadViewState(null);
    } 
    else 
    {
        base.LoadViewState(previousState.First);
 
        if (previousState.Second != null) 
        {
            ((IStateManager) View).LoadViewState(previousState.Second);
        }
    }
}
 
protected override object SaveViewState()
{
    Pair currentState = new Pair();
 
    currentState.First = base.SaveViewState();
 
    if (_view != null) 
    {
        currentState.Second = ((IStateManager) View).SaveViewState();
    }
 
    if ((currentState.First == null) && (currentState.Second == null)) 
    {
        return null;
    }
 
        return currentState;
}
 
protected override void TrackViewState()
{
    base.TrackViewState();
 
    if (_view != null) 
    {
        ((IStateManager) View).TrackViewState();
    }
}
 
protected override void OnInit(EventArgs e)
{
    base.OnInit(e);
 
    // handle the LoadComplete event to update select parameters
    if (Page != null) 
    {
        Page.LoadComplete += new EventHandler(UpdateParameterValues);
    }
}

We use a pair to store the view state. The first element is used to store the parent's view state and the second element is used to store the view's view state. For CustomDataSourceView, the state management is:

C#
bool IStateManager.IsTrackingViewState
{
    get    { return _tracking; }
}
 
void IStateManager.LoadViewState(object savedState)
{
    LoadViewState(savedState);
}
 
object IStateManager.SaveViewState()
{
    return SaveViewState();
}
 
void IStateManager.TrackViewState()
{
    TrackViewState();
}
 
protected virtual void LoadViewState(object savedState)
{
    if (savedState != null) 
    {
        if (savedState != null)
        {
            ((IStateManager)SelectParameters).LoadViewState(savedState);
        }
    }
}
 
protected virtual object SaveViewState()
{
    if (_selectParameters != null)
    {
        return ((IStateManager)_selectParameters).SaveViewState();
    } 
    else 
    {
        return null;
    }
}
 
protected virtual void TrackViewState()
{
    _tracking = true;
 
    if (_selectParameters != null)    
    {
        ((IStateManager)_selectParameters).TrackViewState();
    }
 }

We need to evaluate SelectParameters on every request because if the parameters have changed, we have to rebind:

C#
protected override void OnInit(EventArgs e)
{
    base.OnInit(e);
 
    // handle the LoadComplete event to update select parameters
    if (Page != null) 
    {
        Page.LoadComplete += new EventHandler(UpdateParameterValues);
    }
}
 
protected virtual void UpdateParameterValues(object sender, EventArgs e)
{
    SelectParameters.UpdateValues(Context, this);
}

The only thing left to do is the actual selection from CustomDataSourceView:

C#
protected override IEnumerable ExecuteSelect(
    DataSourceSelectArguments arguments)
{
    // if there isn't a select method, error
    if (SelectMethod.Length == 0) 
    {
        throw new InvalidOperationException(
            _owner.ID + ": There isn't a SelectMethod defined");
    }
 
    // check if we support the capabilities the data bound control expects
    arguments.RaiseUnsupportedCapabilitiesError(this);
 
    // gets the select parameters and their values
    IOrderedDictionary selParams = 
        SelectParameters.GetValues(System.Web.HttpContext.Current, _owner);
 
    // gets the data mapper
    Type type = BuildManager.GetType(_typeName, false, true);
 
    if (type == null) 
    {
        throw new NotSupportedException(_owner.ID + ": TypeName not found!");
    }
 
    // gets the method to call
    MethodInfo method = type.GetMethod(SelectMethod, 
        BindingFlags.Public | BindingFlags.Static);
 
    if (method == null) 
    {
        throw new InvalidOperationException(
            _owner.ID + ": SelectMethod not found!");
    }
 
    // creates a dictionary with the parameters to call the method
    ParameterInfo[] parameters = method.GetParameters();
    IOrderedDictionary paramsAndValues = 
        new OrderedDictionary(parameters.Length);
 
    // check that all parameters that the method needs are 
    // in the SelectParameters
    foreach (ParameterInfo currentParam in parameters) 
    {
        string paramName = currentParam.Name;
 
        if (!selParams.Contains(paramName)) 
        {
            throw new InvalidOperationException(_owner.ID + 
                ": The SelectMethod doesn't have a parameter for " + 
                paramName);
        }
    }
 
    // save the parameters and its values into a dictionary
    foreach (ParameterInfo currentParam in parameters) 
    {
        string paramName = currentParam.Name;
        object paramValue = selParams[paramName];
 
        if (paramValue != null) 
        {
            // check if we have to convert the value
            // if we have a string value that needs conversion
            if (!currentParam.ParameterType.IsInstanceOfType(paramValue) && 
                (paramValue is string)) 
            {
 
                // try to get a type converter
                TypeConverter converter = 
                    TypeDescriptor.GetConverter(currentParam.ParameterType);
                if (converter != null) 
                {
                    try 
                    {
                        // try to convert the string using the type converter
                        paramValue = converter.ConvertFromString(null, 
                            System.Globalization.CultureInfo.CurrentCulture, 
                            (string)paramValue);
                    } 
                    catch (Exception) 
                    {
                        throw new InvalidOperationException(
                            _owner.ID + ": Can't convert " + 
                            paramName + " from string to " + 
                            currentParam.ParameterType.Name);
                    }
                }
            }
        }
 
        paramsAndValues.Add(paramName, paramValue);
    }
 
    object[] paramValues = null;
 
    // if the method has parameters, create an array to 
    // store parameters values
    if (paramsAndValues.Count > 0) 
    {
        paramValues = new object[paramsAndValues.Count];
        for (int i = 0; i < paramsAndValues.Count; i++) 
        {
            paramValues[i] = paramsAndValues[i];
        }
    }
 
    object returnValue = null;
 
    try 
    {
        // call the method
        returnValue = method.Invoke(null, paramValues);
    } 
    catch (Exception e) 
    {
        throw new InvalidOperationException(
            _owner.ID + ": Error calling the SelectMethod", e);
    }

    return (IEnumerable)returnValue;
 }

This code is far from production code. For example, there can be several methods with the same name as the SelectMethod, but with different parameters. The parameter conversion doesn't handle reference and generic types well. There isn't support for DataSet and DataTable types, as they don't implement IEnumerable. You'll also have to extract the underlying DataView to work with them. However, adding all of those "extra features" will make things harder to understand.

Now we are going to create a designer for our CustomDataSource control. The main tasks that have to be performed by DataSourceDesigner are:

  • configuring the data source
  • exposing schema information

Also, we have to expose at least one DesignerDataSourceView. A DataSource control exposes one or more DataSourceViews and a DataSourceDesigner exposes one or more DesignerDataSourceViews:

C#
private static readonly string[] _views = { "DefaultView" };
 
public override DesignerDataSourceView GetView(string viewName)
{
    if ((viewName == null) || ((viewName.Length != 0) && 
        (String.Compare(viewName, "DefaultView", 
        StringComparison.OrdinalIgnoreCase) != 0)))
    {
        throw new ArgumentException("An invalid view was requested", 
            "viewName");
    }
 
    return View;
}
 
public override string[] GetViewNames()
{
    return _views;
}

As you can see, the code is very similar to that used in the custom data source to expose the custom data source view. As our data source will only retrieve data, the default implementation of DesignerDataSourceView is enough for all CanXXX properties. In order to quickly configure our custom DataSource, we'll provide a GUI that will let us choose the TypeName and the SelectMethod using DropDownLists:

Design time

In order to be able to show the Configure Data Source dialog we need to override the CanConfigure property and implement the Configure method:

C#
public override bool CanConfigure
{
    get { return true; }
}
 
public override void Configure()
{
    _inWizard = true;
 
    // generate a transaction to undo changes
    InvokeTransactedChange(Component, 
        new TransactedChangeCallback(ConfigureDataSourceCallback), 
        null, "ConfigureDataSource");
    _inWizard = false;
}
 
protected virtual bool ConfigureDataSourceCallback(object context)
{
    try 
    {
        SuppressDataSourceEvents();
 
        IServiceProvider provider = Component.Site;
        if (provider == null)
        {
            return false;
        }
 
        // get the service needed to show a form
        IUIService UIService = 
            (IUIService) provider.GetService(typeof(IUIService));
        if (UIService == null)
        {
            return false;
        }
 
        // shows the form
        ConfigureDataSource configureForm = 
            new ConfigureDataSource(provider, this);
        if (UIService.ShowDialog(configureForm) == DialogResult.OK)
        {
            OnDataSourceChanged(EventArgs.Empty);
            return true;
        }
    } 
    finally 
    {
        ResumeDataSourceEvents();
    }
    return false;
 }

As the GUI will change several properties at a time, we have to create a transacted change in order to provide undo functionality. The form fills the first drop-down list with all available types using the type discovery service instead of reflection. Why? Because using reflection, we can only get all types of the compiled assemblies. However, we can add more types without having compiled the project. We can also have types that don't compile and the type discovery service will also show them. So, it is a lot better to use the type discovery service instead of reflection.

In the code, we haven't removed types that will probably not be candidates for our TypeName property -- i.e. generic types, interfaces -- in order to keep code as simple as possible:

C#
private void DiscoverTypes()
{
    // try to get a reference to the type discovery service
    ITypeDiscoveryService discovery = null;
    if (_component.Site != null) 
    {
        discovery = 
            (ITypeDiscoveryService)_component.Site.GetService(
            typeof(ITypeDiscoveryService));
    }
 
    // if the type discovery service is available
    if (discovery != null) 
    {
        // saves the cursor and sets the wait cursor
        Cursor previousCursor = Cursor.Current;
        Cursor.Current = Cursors.WaitCursor;
 
        try 
        {
            // gets all types using the type discovery service
            ICollection types = discovery.GetTypes(typeof(object), true);
            ddlTypes.BeginUpdate();
 
            ddlTypes.Items.Clear();
  
            // adds the types to the list
            foreach (Type type in types) 
            {
                TypeItem typeItem = new TypeItem(type);
                ddlTypes.Items.Add(typeItem);
            }
        } 
        finally 
        {
            Cursor.Current = previousCursor;
            ddlTypes.EndUpdate();
        }
    }
 }

The TypeItem class is a class used to store types in the drop-down list. When a type is selected from the first drop-down list, the other drop-down list gets populated with the methods of the selected type:

C#
private void FillMethods()
{
    // saves the cursor and sets the wait cursor
    Cursor previousCursor = Cursor.Current;
    Cursor.Current = Cursors.WaitCursor;
 
    try 
    {
        // gets all public methods (instance + static)
        MethodInfo[] methods = 
            CustomDataSourceDesigner.GetType(_component.Site, TypeName).
            GetMethods(BindingFlags.Public | BindingFlags.Static | 
            BindingFlags.Instance | BindingFlags.FlattenHierarchy);
        ddlMethods.BeginUpdate();
 
        ddlMethods.Items.Clear();
 
        // adds the methods to the dropdownlist
        foreach (MethodInfo method in methods) 
        {
            MethodItem methodItem = new MethodItem(method);
            ddlMethods.Items.Add(methodItem);
        }
    } 
    finally 
    {
        Cursor.Current = previousCursor;
        ddlMethods.EndUpdate();
    }
}

To quickly get and set TypeName and SelectMethod from and to the form, we have defined those properties in the form as follows:

C#
internal string TypeName
{
    get 
    {
        // gets the selected type
        TypeItem selectedType = ddlTypes.SelectedItem as TypeItem;
 
        // return the selected type
        if (selectedType != null)
        {
            return selectedType.Name;
        } 
        else 
        {
            return String.Empty;
        }
    }
    set 
    {
        // iterate through all the types searching for the requested type
        foreach (TypeItem item in ddlTypes.Items)
        {
            // if we have found it, select it
            if (String.Compare(item.Name, value, true) == 0) 
            {
                ddlTypes.SelectedItem = item;
                break;
            }
        }
    }
}
 
internal string SelectMethod
{
    get 
    {
        // gets the select method
        string methodName = String.Empty;
 
        if (MethodInfo != null) 
        {
            methodName = MethodInfo.Name;
        }
 
        return methodName;
    }
    set    
    {
        // iterate through all the types searching for the requested type
        foreach (MethodItem item in ddlMethods.Items) 
        {
            // if we have found it, select it
            if (String.Compare(item.MethodInfo.Name, value, true) == 0) 
            {
                ddlMethods.SelectedItem = item;
                break;
            }
        }
    }
}
 
internal MethodInfo MethodInfo
{
    get 
    {
        MethodItem item = ddlMethods.SelectedItem as MethodItem;
 
        if (item == null) 
        {
            return null;
        }
 
        return item.MethodInfo;
    }
}

Note that to simplify code when the SelectMethod property is set, the selected method from the drop-down list will be the first method with the same name as SelectMethod. No parameters are checked to simplify the code, but for production code you'll probably want to check that the parameters match.

In the FillMethods method, the type is obtained using the GetType method that used the resolution service. This is for the same reasons that we specified before for using the type discovery service. In order to simplify the code, we have not removed some methods that will certainly not be the proper method, like property getters and setters or abstract methods.

C#
internal static Type GetType(IServiceProvider serviceProvider, 
    string typeName)
{
    // try to get a reference to the resolution service
    ITypeResolutionService resolution = 
        (ITypeResolutionService)serviceProvider.
    GetService(typeof(ITypeResolutionService));
    if (resolution == null) 
    {
        return null;
    }
 
    // try to get the type
    return resolution.GetType(typeName, false, true);
}

When the user clicks the Accept button in the Configure data source form, the code that gets executed is:

private void bOK_Click(object sender, EventArgs e)
{
    // if the type has changed, save it
    if (String.Compare(TypeName, _component.TypeName, false) != 0) 
    {
        TypeDescriptor.GetProperties(
            _component)["TypeName"].SetValue(_component, TypeName);
    }
 
    // if the select method has changed, save it
    if (String.Compare(SelectMethod, _component.SelectMethod, false) != 0) 
    {
        TypeDescriptor.GetProperties(
            _component)["SelectMethod"].SetValue(_component, SelectMethod);
    }
 
    // if there is method selected, refresh the schema
    if (MethodInfo != null) 
    {
        _designer.RefreshSchemaInternal(MethodInfo.ReflectedType, 
            MethodInfo.Name, 
            MethodInfo.ReturnType, true);
    }
}

We save the Type and the SelectMethod and refresh the schema. To provide schema information, we have to return true in the CanRefreshSchema method and we have to implement the RefreshSchema method. When we provide schema information, the controls can provide field pickers -- i.e. columns for a GridView -- and generate templates based on the schema information, i.e. a DataList bound to our data source control. However, we cannot return true for the CanRefreshSchema because we can return schema information only if the user has configured the data source:

C#
public override bool CanRefreshSchemablic override bool CanRefreshSchema
{
    get    
    {
        // if a type and the select method have been 
        // specified, the schema can be refreshed
        if (!String.IsNullOrEmpty(TypeName) && !String.IsNullOrEmpty(
            SelectMethod)) 
        {
            return true;
        } 
        else 
        {
            return false;
        }
   }
}

To implement the RefreshSchema method, we need to extract the schema information and generate the SchemaRefreshed event. If a data source control can provide schema information, the schema information will be retrieved from the property Schema from the underlying DesignerDataSourceView. However, the SchemaRefreshed event doesn't have to be raised every time, only if the data source returns a different schema. To see why this is important, think about this: if the data source is bound to a GridView, every time the RefreshSchema event is raised, the designer will ask if it has to regenerate the columns and the data keys. So, we're interested in raising the SchemaRefreshed event only when the schema changes. We use the designer state to store the previous schema. When the RefreshSchema method is called, we will check if the schema has changed, raising the SchemaRefreshed event only in that case. The code related to the RefreshSchema method is:

C#
internal IDataSourceViewSchema DataSourceSchema
{
    get 
    { 
        return DesignerState["DataSourceSchema"] as IDataSourceViewSchema; 
    }
    set 
    { 
        DesignerState["DataSourceSchema"] = value; 
    }
}

public override void RefreshSchema(bool preferSilent)
{
    // saves the old cursor
    Cursor oldCursor = Cursor.Current;

    try 
    {
        // ignore data source events while refreshing the schema
        SuppressDataSourceEvents();

        try 
        {
            Cursor.Current = Cursors.WaitCursor;

            // gets the Type used in the DataSourceControl
            Type type = GetType(Component.Site, TypeName);

            // if we can't find the type, return
            if (type == null) 
            {
                    return;
            }

            // get all the methods that can be used as the select method
            MethodInfo[] methods = 
                type.GetMethods(BindingFlags.FlattenHierarchy |
                BindingFlags.Static | BindingFlags.Instance | 
                BindingFlags.Public);

            MethodInfo selectedMethod = null;

            // iterates through the methods searching for the select method
            foreach (MethodInfo method in methods) 
            {
                // if the method is named as the selected method, select it
                if (IsMatchingMethod(method, SelectMethod)) 
                {
                    selectedMethod = method;
                    break;
                }
            }

            // if the SelectMethod was found, save the type information
            if (selectedMethod != null) 
            {
                RefreshSchemaInternal(type, selectedMethod.Name,
                selectedMethod.ReturnType, preferSilent);
            }
        } 
        finally 
        {
            // restores the cursor
            Cursor.Current = oldCursor;
        }
    } 
    finally 
    {
        // resume data source events
        ResumeDataSourceEvents();
    }
}

internal void RefreshSchemaInternal(Type typeName, 
    string method, Type returnType, bool preferSilent)
{
    // if all parameters are filled
    if ((typeName != null) && (!String.IsNullOrEmpty(method)) && 
        (returnType != null)) 
    {
        try 
        {
            // gets the old schema
            IDataSourceViewSchema oldSchema = DataSourceSchema;

            // gets the schema of the return type
            IDataSourceViewSchema[] typeSchemas = 
                new TypeSchema(returnType).GetViews();

            // if we can't get schema information from the type, exit
            if ((typeSchemas == null) || (typeSchemas.Length == 0))
            {
                DataSourceSchema = null;
                return;
            }

            // get a view of the schema
            IDataSourceViewSchema newSchema = typeSchemas[0];

            // if the schema has changed, raise the schema refreshed event
            if (!DataSourceDesigner.ViewSchemasEquivalent(
                oldSchema, newSchema))
            {
                DataSourceSchema = newSchema;
                OnSchemaRefreshed(EventArgs.Empty);
            }
        } 
        catch (Exception e)
        {
            if (!preferSilent)
            {
                ShowError(DataSourceComponent.Site, 
                    "Cannot retrieve type schema for " +
                    returnType.FullName + ". " + e.Message);
            }
        }
    }
 }

As you can see, we get MethodInfo for SelectMethod and get the return type. All hard work to expose schema information is done by the framework helper class, TypeSchema. Take a look at the articles at the beginning for more information about the TypeSchema class. The DesignerDataSource view exposes the saved schema:

C#
public override IDataSourceViewSchema Schema
{
    get 
    {
        // if a type and the select method have been 
        // specified, the schema information is available
        if (!String.IsNullOrEmpty(_owner.TypeName) && !String.IsNullOrEmpty(
            _owner.SelectMethod)) 
        {
            return _owner.DataSourceSchema;
        } 
        else 
        {
            return null;
        }
    }
}

The last thing that needs clarifying is that we have overridden the PreFilterProperties method in the CustomDataSourceDesigner class in order to modify how the TypeName and SelectMethod properties work. This is because when any of those properties change, the underlying data source and schema will probably change. So, we have to notify it to the associated designers:

C#
protected override void PreFilterProperties(IDictionary properties)
{
    base.PreFilterProperties(properties);

    // filters the TypeName property
    PropertyDescriptor typeNameProp = 
        (PropertyDescriptor)properties["TypeName"];
    properties["TypeName"] = TypeDescriptor.CreateProperty(base.GetType(), 
        typeNameProp, new Attribute[0]);

    // filters the SelectMethod property
    PropertyDescriptor selectMethodProp = 
        (PropertyDescriptor)properties["SelectMethod"];
    properties["SelectMethod"] = 
        TypeDescriptor.CreateProperty(base.GetType(), 
        selectMethodProp, new Attribute[0]);
}

public string TypeName
{
    get 
    { 
        return DataSourceComponent.TypeName; 
    }
    set    
    {
        // if the type has changed
        if (String.Compare(DataSourceComponent.TypeName, value, false) != 0)
        {
            DataSourceComponent.TypeName = value;

            // notify to the associated designers that this 
            // component has changed
            if (CanRefreshSchema)
            {
                RefreshSchema(true);
            } 
            else 
            {
                OnDataSourceChanged(EventArgs.Empty);
            }
            UpdateDesignTimeHtml();
        }
    }
}

public string SelectMethod
{
    get 
    { 
        return DataSourceComponent.SelectMethod; 
    }
    set    
    {
        // if the select method has changed
        if (String.Compare(DataSourceComponent.SelectMethod, 
            value, false) != 0)
        {
            DataSourceComponent.SelectMethod = value;

            // notify to the associated designers that this 
            // component has changed
            if (CanRefreshSchema && !_inWizard)
            {
                RefreshSchema(true);
            } 
            else 
            {
                OnDataSourceChanged(EventArgs.Empty);
            }

            UpdateDesignTimeHtml();
        }
    }
}

The full source code of the designer and the data source control is available in the downloads for this article. As you can see, adding design time support to a data source control is not terribly complicated, but you have to write quite a bit of code -- 1300 lines in this sample -- even for simple data sources. The more complex your data source, the more code you will have to write.

Points of interest

The design time support covered for this data source is the most common scenario: the data source control doesn't render any HTML at run time and it only exposes a form to configure the data source. However, a data source control can also render HTML in some cases -- take a look at the PagerDataSource -- being not only a data provider, but also a data consumer. If you want to render HTML with your data source control, you have a lot of work to do as the framework doesn't have any base classes for data source controls that also render HTML.

History

  • 01/03/2007 - Initial version
  • 06/19/2007 - Article edited and moved to the main CodeProject.com article base

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