Blog Home  Home Feed your aggregator (RSS 2.0)  
ASP.NET Designers - The DataSourceDesigner class - Manuel Abadia's ASP.NET stuff
 
# Wednesday, April 12, 2006

In previous posts we studied the functionality offered by ControlDesigner and we coded a custom data source sample. We’ll add design time functionality to the CustomDataSource. The base class to use as starting point when adding a designer to a DataSourceControl is DataSourceDesigner. DataSourceDesigner implements the IDataSourceDesigner interface:

 

public interface IDataSourceDesigner

{

    bool CanConfigure { get; }

    bool CanRefreshSchema { get; }

 

    event EventHandler DataSourceChanged;

    event EventHandler SchemaRefreshed;

 

    void Configure();

    void RefreshSchema(bool preferSilent);

 

    DesignerDataSourceView GetView(string viewName);

    string[] GetViewNames();

 

    void ResumeDataSourceEvents();

    void SuppressDataSourceEvents();

}

 

 

This interface is very similar to the IDataSource interface (If you’re not familiar with the IDataSource interface look at my posts from January) but it has more methods and properties:

 

public interface IDataSource

{

    DataSourceView GetView(string viewName);

    ICollection GetViewNames();

 

    event EventHandler DataSourceChanged;

}

 

For a DataSourceControl we have associated DataSourceViews and for a DataSourceDesigner we have associated DesignerDataSourceViews.

 

In addition to exposing the DesignerDataSourceViews and generating an event when the underlying data changes, the DataSourceDesigner can do more things:

·         Configure the data source: provide a GUI to quickly set up the main properties for the data source.

·         Refresh schema information: update the structure of the data exposed by the data source (more on this later).

 

The properties CanConfigure and CanRefresh properties return true if the designer has implemented that functionality and the methods Configure and RefreshSchema will be called by the user when he wants to Configure the data source or refresh the schema.

 

The implementation of DataSourceDesigner overrides the ActionList property to add two more items to the smart tag: “Configure Data Source…” and “Refresh Schema”. These items are added only if the associated CanXXX property is true. When the user clicks on an item, the associated method (Configure or RefreshSchema) is called. If you plan to support any of these options, you have to override the method because the implementation throws a NotSupportedException.

 

Before continuing with the rest of the DataSourceDesigner class lets compare the DataSourceView and the DesignerDataSourceView classes:

 

 

public abstract class DesignerDataSourceView

{

    protected DesignerDataSourceView(IDataSourceDesigner owner, string viewName);

 

    public IDataSourceDesigner DataSourceDesigner { get; }

    public string Name { get; }

 

    public virtual bool CanDelete { get; }

    public virtual bool CanInsert { get; }

    public virtual bool CanPage { get; }

    public virtual bool CanRetrieveTotalRowCount { get; }

    public virtual bool CanSort { get; }

    public virtual bool CanUpdate { get; }

 

    public virtual IDataSourceViewSchema Schema { get; }

    public virtual IEnumerable GetDesignTimeData(int minimumRows, out bool isSampleData);

}

public abstract class DataSourceView

{

    protected DataSourceView(IDataSource owner, string viewName);

 

    public string Name { get; }

 

    public virtual bool CanDelete { get; }

    public virtual bool CanInsert { get; }

    public virtual bool CanPage { get; }

    public virtual bool CanRetrieveTotalRowCount { get; }

    public virtual bool CanSort { get; }

    public virtual bool CanUpdate { get; }

 

    protected EventHandlerList Events { get; }

 

    public event EventHandler DataSourceViewChanged;

 

    public virtual void Select(DataSourceSelectArguments arguments, DataSourceViewSelectCallback callback);

    public virtual void Insert(IDictionary values, DataSourceViewOperationCallback callback);

    public virtual void Update(IDictionary keys, IDictionary values, IDictionary oldValues, DataSourceViewOperationCallback callback);

    public virtual void Delete(IDictionary keys, IDictionary oldValues, DataSourceViewOperationCallback callback);

 

    protected internal abstract IEnumerable ExecuteSelect(DataSourceSelectArguments arguments);

    protected virtual int ExecuteInsert(IDictionary values);

    protected virtual int ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues);

    protected virtual int ExecuteDelete(IDictionary keys, IDictionary oldValues);

 

    protected virtual void OnDataSourceViewChanged(EventArgs e);

 

    protected internal virtual void RaiseUnsupportedCapabilityError(DataSourceCapabilities capability);

}

 

As you can see both classes have similarities. The CanXXX properties are the same in both classes. The main difference between the interfaces is that the DataSourceView class has methods to perform CRUD (Create, Retrieve, Update and Delete) operations (synchronous or asynchronous) while the DesignerDataSourceView has only two methods:

·         Schema: returns an object with information about the fields in the data source. This is used by other design time UIs in order to provide a list of fields to select (as we will see soon).

·         GetDesignTimeData: returns data to use in design time (the data can be real or not).

 

The implementation of the DesignerDataSourceView returns false for all the CanXXX properties, null for the Schema method and some fictitious data for the GetDesignTimeData method (take a look to the DesignTimeData class if you want to return fictitious data).

 

The Schema property needs more explanation as there are a lot of classes/interfaces involved. The return type for the Schema property is IDataSourceViewSchema. This interface is related to the IDataSourceSchema:

 

public interface IDataSourceSchema

{

    IDataSourceViewSchema[] GetViews();

}

 

The IDataSourceSchema interface has only one method called GetViews that returns an array of IDataSourceViewSchemas. An implementation of the IDataSourceSchema allows the design time architecture to discover information about the types we’re exposing in design time. As a data source can have multiple views, the information about the type returned by a view is handled by a class that implements IDataSourceViewSchema. The IDataSourceViewSchema interface is:

 

public interface IDataSourceViewSchema

{

    string Name { get; }

 

    IDataSourceViewSchema[] GetChildren();

    IDataSourceFieldSchema[] GetFields();

}

 

The Name property returns the name of the view we’re interested in. As the IDataSourceViewSchema is used also in hierarchical data sources, it has a GetChildren method to obtain the schema used by other hierarchy levels. If we are not using a hierarchical data source we can return null if we have to implement this interface. The interesting method in this interface is the GetFields method that returns an array of IDataSourceFieldSchema, describing all fields of the data source. The IDataSourceFieldSchema is:

 

public interface IDataSourceFieldSchema

{

    Type DataType { get; }

    bool Identity { get; }

    bool IsReadOnly { get; }

    bool IsUnique { get; }

    int Length { get; }

    string Name { get; }

    bool Nullable { get; }

    int Precision { get; }

    bool PrimaryKey { get; }

    int Scale { get; }

}

 

 

As you can see, the schema of a field is fully described with the IDataSourceFieldSchema.

 

As this can be a bit confusing lets see an example. For the DataSet there is a class called DataSetSchema that implements the IDataSourceSchema interface and provides information about the types exposed by the DataSet. The DataSetSchema GetViews method returns a DataSetViewSchema for each DataTable in the DataSet. The GetFields method in the DataSetViewSchema class returns a DataSetFieldSchema for each column in the DataTable. The DataSetFieldSchema implements the IDataSourceFieldSchema interface using the information of the DataColumn.

 

To avoid implementing those 3 interfaces for our data sources there is a class called TypeSchema that implements the IDataSourceSchema interface that returns schema information for a type using reflection. You can pass a type to the constructor of the class and it will return the schema information. It can handle 5 distinct cases:

·         A DataTable: returns an array containing one element of type DataSetViewSchema

·         A DataSet: returns an array with as many DataSetViewSchema as DataTables

·         An object that implements the generic IEnumerable: returns an array with one element that implement IDataSourceViewSchema and returns type information of the first element of the generic IEnumerable collection.

·         An object that implements IEnumerable: returns an array with one element that implement IDataSourceViewSchema and returns type information of the first element of the generic IEnumerable collection.

·         Another Type: returns an array with one element that implement IDataSourceViewSchema and returns type information using TypeDescriptor.GetProperties and the DataObjectFieldAttribute (that provides information about if a property is primary key, identity, nullable and the length in bytes).

 

 

Now it’s time to go back and complete our study of the DataSourceDesigner class:

 

public class DataSourceDesigner : ControlDesigner, IDataSourceDesigner

{

    public DataSourceDesigner();

 

    public override DesignerActionListCollection ActionLists { get; }

    public virtual bool CanConfigure { get; }

    public virtual bool CanRefreshSchema { get; }

    protected bool SuppressingDataSourceEvents { get; }

 

    public event EventHandler DataSourceChanged;

    public event EventHandler SchemaRefreshed;

 

    public virtual void Configure();

    public virtual void RefreshSchema(bool preferSilent);

 

    public override string GetDesignTimeHtml();

 

    public virtual DesignerDataSourceView GetView(string viewName);

    public virtual string[] GetViewNames();

 

    protected virtual void OnDataSourceChanged(EventArgs e);

    protected virtual void OnSchemaRefreshed(EventArgs e);

 

    public virtual void ResumeDataSourceEvents();

    public virtual void SuppressDataSourceEvents();

 

    public static bool SchemasEquivalent(IDataSourceSchema schema1, IDataSourceSchema schema2);

    public static bool ViewSchemasEquivalent(IDataSourceViewSchema viewSchema1, IDataSourceViewSchema viewSchema2);

}

 

We covered the most important methods and properties of the class: ActionLists, CanConfigure, CanRefreshSchema, Configure, RefreshSchema, GetView and GetViewNames when talking about the IDataSourceDesigner interface.

 

The SuppressingDataSourceEvents property returns true if we have called the SuppressDataSourceEvents but we haven’t called ResumeDataSourceEvents. This is implemented as a counter. Initially the counter value is 0, and SuppressingDataSourceEvents returns false. Each time you call SuppressDataSourceEvents, the counter is incremented. When a DataSourceChanged or SchemaRefreshed event is thrown, if we’re suppressing the events, the event will not fire but a flag will be to let us know we have missed an event. When ResumeDataSourceEvents is called, the counter is decremented and when it’s 0, if a DataSourceChanged or SchemaRefreshed event was thrown while the events where suppressed, it will be fired now.

 

You may wonder why you would want to suppress events. If you are showing a dialog to the user to configure the data source you don’t want to update design time related stuff unless the user accepts the changes.

 

The implementation of the GetDesignTimeHtml calls to CreatePlaceHolderDesignTimeHtml in order to return a table with the control type and ID.

 

The static methods of the class, SchemasEquivalent and ViewSchemasEquivalent are useful if you need comparing two IDataSourceSchemas or IDataSourceViewSchemas.

 

This completes our study about the DataSourceDesigner and related classes/interfaces. I hope this has been useful as some of these classes/interfaces are poorly documented/explained in the MSDN documentation.

 

In a future post we’ll add design time support to our custom data source control.

Copyright © 2014 Manuel Abadia. All rights reserved.
DasBlog 'Portal' theme by Johnny Hughes.