DataSource and BindingList

(by Joe Williams for Blayd Software)

Note: this article relates to the .Net Framework version 2.0, some of the classes and class members referenced in this article were not available in earlier framework versions.

Introduction

Data binding in ASP.Net and Windows Forms has been with us for a while and has been increasing in functionality, flexibility and ease of use with each release of the .Net Framework. Most of us are familiar with the techniques required to retrieve data from a database into a DataTable, DataView or DataSet and there are now a variety of components and controls that are capable of data binding to any of these types. However, we don't always want to expose these types of data lists directly to user interface components or controls and sometimes we may not be able to expose them or indeed the data for our lists may not (originally) come from a database, so these types may not be the best or easiest type of list structure to use. In many systems, such as line of business or commercial component or class library systems, there is a requirement to expose lists of business or server objects to the user interface tier or client application. In this type of situation we need to provide a list of business objects from the middle tier of the system or from a server component to which components or controls hosted in the user interface tier can easily bind.

In this article we going to discuss the development of a server, class library, project that provides a list of data objects which can be easily data bound to a user interface component or control. Each data object will expose the properties of a Windows Service installed on the machine on which the server component is running. The list will, therefore, contain the details of all of the Windows Services installed on the machine. To demonstrate two way binding we will make the data objects updatable and we will allow data objects to be added to and removed from the list. However, any such changes made by the data bound user interface component or control will not be reflected back to the actual Windows Service being represented by the data object. Therefore, the data object may be changed, the list may be changed and the user interface component or control will update to reflect the changes but the underlying Windows Service object will not change.

We will show as much of the source code as we have space for, however, we will not able to show it all. You can download the full source code for this article using the link provided in the "Article Options" on the left of the page. The source code shown in this article will be in C# but the download sample code is available as either a C# or a VB Visual Studio 2005 solution containing two projects, a server component class library containing the data binding list and a Windows Forms application with a BindingSource component and a DataGridView control that are used to bind to and display the list data.

The Data or Business Class that Clients can Bind To

The sample data or business class is named ServiceInfo and it is used to surface the properties of a single Windows Service. The Windows Service property values are retrieved using an instance of the System.ServiceProcess.ServiceController class. An instance of the ServiceInfo class is a simple data object, it does not hold a reference to the underlying ServiceController and it is not capable of making changes to the Windows Service that it is representing.

The following ServiceController properties are surfaced by the ServiceInfo class:

DisplayName
The friendly or display name of the service. The ServiceInfo class permits a data bound control to update this property.
MachineName
The name of the computer on which the service is running. The ServiceInfo class permits a data bound control to update this property.
ServiceName
The unique name of the service. The ServiceInfo class permits a data bound control to update this property.
ServiceType
The type of service. The ServiceInfo class surfaces this property value as a comma delimited string representation of the set ServiceType enumerated flag bits.
Status
The current status of the service. The ServiceInfo class surfaces this property as a string representation the ServiceControllerStatus enumerated value.

To add value to the ServiceInfo class it also surfaces three Windows Service properties that are not available in the ServiceController class, these properties are as follows:

Description
The description of the service. The ServiceInfo class permits a data bound control to update this property.
LogOnAs
The Windows account name under which the service runs. The ServiceInfo class permits a data bound control to update this property.
StartMode
The start mode of the service. The ServiceInfo class surfaces this property as a string representation of the ServiceStartMode enumerated value. In addition to the values specified in the ServiceStartMode enumeration, the ServiceInfo class also provides a string representation of the two extra, device driver only, values of "Boot" and "System".

Although the ServiceInfo class implements "setters" for some of its properties, this functionality is only provided to demonstrate two way binding i.e. the updating of data items by a data bound user interface component or control. Changing the value of one of the ServiceInfo class properties only updates the data representation of the Windows Service it does not update the actual service.

The values for the three extra or extended properties are retrieved from the Windows Registry for each Windows Service during the initialization of each ServiceInfo instance. As coded in the demonstration source code, the extended properties behave as expected when running the sample code on a Windows XP machine, however, if you run the sample code on a Windows Vista machine, the Description property will not contain the correct data. This is due to differences in the way that the relevant Registry keys are used on each of the operating systems. Although frustrating, this issue does not really have any impact on the main purpose of the sample code, so we resisted the temptation to delve too deeply into the Registry issue and the thought of producing operating system aware Registry retrieval code in a demonstration project that does not really have anything to do with the Registry seemed a bit excessive!

ExpandPartial listing of the ServiceInfo class:

As mentioned earlier the ServiceInfo class is essentially a fairly simple data object that represents the properties of a Windows Service, however, there a few points worthy of further discussion. The class implements the INotifyPropertyChanged interface in order to notify clients, typically binding clients, that a property value has changed. The INotifyPropertyChanged interface is contained in the System.ComponentModel namespace and defines a single member, the PropertyChanged event. When an object, such as a ServiceInfo class instance, is added to an instance of the BindingList class, the BindingList examines the object to see if it implements the INotifyPropertyChanged interface. If it does, the BindingList sinks the PropertyChanged event to enable it to react and notify its bound clients when one of the objects in its list is changed. We will be looking at the BindingList class in more detail later in the article.

The ServiceInfo class implements a default, parameterless, constructor which can be used to initialize a new instance of the class with default values. We will see later in the article how the inclusion of a default constructor in its listed objects influences the behaviour of the BindingList class at runtime. The protected, internal, constructor is used to initialize a new instance of the ServiceInfo class with the property values from a ServiceController instance. The ServiceInfo class does not maintain a reference to the ServiceController passed to the constructor, it just retrieves the required property values.

We have listed the MachineName property to illustrate several points relating to properties that can be data bound. The property has a setter, therefore, the ServiceInfo class is able to participate in two way data binding. The inclusion of a property setter allows a data bound user interface component or control to update the MachineName property, although in this particular example updating the property has no affect, the functionality is provided solely to demonstrate the process. If we allow a property of a data bound class to be updated, we need to raise the PropertyChanged event each time that it is updated. Raising the PropertyChanged event notifies all data bound clients that the ServiceInfo instance has changed, enabling them to update their display to reflect the change. The MachineName property is decorated with the BindableAttribute, from the System.ComponentModel namespace and is set to False to deter user interface designers from listing the property in the default set of properties to bind to. Clients can still bind to the property, both at design time and runtime, however, by using the attribute with a value of False we are, at least, declaring that we would rather clients didn't bind to this property!

The DataSource Component that Provides the Data List

Having developed the ServiceInfo class to provide the properties of a Windows Service we now have to decide how we are going to present instances of the class to client applications. An instance of the ServiceInfo class represents a single, installed, Windows Service and there will be many installed services on most machines, therefore, we need to provide client applications with a list of ServiceInfo instances to represent all of the Windows Services installed on the machine. We want to populate and maintain the service list in the server or business component rather than forcing the client application developer to write the code to load the service data, that is the main functional purpose of the server or business component. We do, therefore, have to provide a populated list of ServiceInfo instances that clients can bind to, however, placing the code to load the Windows Service data in the list class does not make much sense, so we need another class to populate and manage the list and expose the list to client applications. In this kind of situation a data source component provides an ideal solution, we can include a set of properties that client applications can use to configure their list requirements, a method or methods that actually populate the list and a method that enables clients to retrieve an instance of the populated list and bind to it.

The ServiceDataSource class extends the Component class to provide the developer of the user interface application with component designer functionality. We want developers who use our server or business component to be able to specify most, if not all, of the list configuration options at design time rather than having to write code.

ExpandPartial listing of the ServiceDataSource component:

The ServiceDataSource component class implements the IListSource interface which is located in the System.ComponentModel namespace and defines two members, the ContainsListCollection property and the GetList method. The ServiceDataSource component is not itself a list to which user interface components or controls can bind, however, by implementing the IListSource interface it defines its purpose as a supplier of a list to which components or controls can bind. Because it implements the IListSource interface the ServiceDataSource component can be assigned to the DataSource property of several user interface components and controls, most notably, from an application developer's point of view, the System.Windows.Forms.BindingSource component.

The ContainsListCollection property is used to specify whether the list provided by the ServiceDataSource component is a collection or a collection of collections. The ServiceDataSource component implementation always returns False because its list references a collection of objects not a collection of collections. The GetList method is called by bound clients to retrieve a reference to the list supplied by the ServiceDataSource component. This method may only be called once by a bound client if the list implements the IBindingList interface, as thereafter the bound client can use the IBindingList.ListChanged event to react to changes to the list. The ServiceDataSource component delegates calls to the IListSource.GetList method to its own GetServiceList method, casting the returned value to IList.

As you will see in the next section of the article, the list returned by the ServiceDataSource component does implement the IBindingList interface, located in the System.ComponentModel namespace, therefore, the ServiceDataSource component has to assume that bound clients will sink the IBindingList.ListChanged event. This is the reason why the ServiceDataSource component only creates and then caches a single instance of its list, rather than creating a new instance on each call to either the IListSource.GetList or the GetServiceList method. If it created a new list instance on each call, any event handlers in bound clients would be referencing the old list instance rather than the newly created instance and would not, therefore, receive the IBindingList.ListChanged event from the new instance. Clients can rebuild the entire list by calling the RefreshList method which simply clears the ServiceInfo instances from the cached list and then repopulates it with newly created ServiceInfo instances to reflect the current Windows Service data.

The ServiceDataSource component implements three properties that client application developers can use to configure the contents of the service data list. The ListDevices property, which defaults to False, can be used to include device driver services in the list and the ListServices property, which defaults to True, can be used to include normal services in the list. The MachineName property, which defaults to "." i.e. the local machine, can be used to specify the machine from which to list Windows Services. The topic of loading Windows Service data from a remote machine is beyond the scope of this article, however, you should be aware that the sample code for this article also accesses the Registry to retrieve Windows Service property data. Therefore, if you specify a remote machine, the code will need to access the Registry on the remote machine as well as the Windows Service data. The sample code will do this, providing the necessary protocols are permitted access to the target machine and the necessary permissions are in place.

All of the ServiceDataSource component's properties can be set in the component designer, therefore, it is possible for an application developer to utilize the ServiceDataSource component and display its service data list without having to write any code.

The BindingList that contains the Data or Business Objects

Data binding in the .Net Framework is, as it should be, interface based i.e. to provide data that a component or control can bind to, a class has to implement one or more of the various list and/or data binding interfaces. For complex or list-based data binding, a class can implement IList or even IEnumerable (requires the BindingSource component to be used by an application developer), both of these interfaces are located in the System.Collections namespace. However, for two way data binding with change notification and sorting, a class should implement the IBindingList interface from the System.ComponentModel namespace. Implementing the IBindingList interface can be tricky, however the .Net Framework now contains a concrete, generic, implementation of IBindingList in the BindingList<T> class, also located in the System.ComponentModel namespace, which can be considered as a default implementation of IBindingList. The BindingList base class can be used, as is, just be specifying the type that is to be contained in the list when creating an instance, however, by default, BindingList does not support searching or sorting. Searching may be a "nice to have" for a small list but is likely to be a necessity for a large list and sorting is, we feel, essential if the list is intended to be displayed by a user interface control. The DataGridView control, for example, has built-in support for sorting, if the list that it is bound to supports it, therefore an application developer gets sorting for free, so may well expect it! The generic BindingList class is a good starting point for providing a data list that user interface components and controls can bind to, however, in a lot of situations, you will have to extend it to provide your own searching and sorting functionality.

For the demonstration Windows Service data list which is the subject of this article we have extended the BindingList class to develop the ServiceBindingList class, a data list which supports searching and sorting. For demonstration purposes we have implemented two way data binding i.e. a ServiceInfo instance contained in the data list can be updated by a bound component or control and ServiceInfo instances can be added to or removed from the data list. The BindingList base class contains three public properties that require further comment, the AllowEdit, AllowNew and AllowRemove properties dictate whether items in the list can be edited and whether items can be added to or removed from the list. All of these properties have setters with public scope and they can, therefore, be set by a bound component or control. The AllowNew property defaults to True if the type contained in the list has a default constructor or if the AddingNew event is handled. If the AddingNew event is not handled or if the list type does not have a default constructor, the AllowNew property defaults to False. You may need to give careful thought to how you implement or expose these properties in your subclass and whether or not you implement a default constructor in your list type, if you don't want a bound component or control to dictate whether or not your data list allows items to be edited, added or removed.

ExpandPartial listing of the ServiceBindingList class:

The ServiceBindingList class extends the generic BindingList<T> class which extends the generic Collection<T> class from the System.Collections.ObjectModel namespace. However, neither BindingList<T> or Collection<T> support searching or sorting but we can pass a list object to the Collection<T> constructor which it will wrap and use as storage for the list items. Therefore, the ServiceBindingList constructors either create an empty instance of or accept an instance of the generic List<T> class that is then passed, via the BindingList constructor, to the Collection constructor to be used as storage for the list items. The generic List class does support searching and sorting, therefore, we can support searching and sorting in the ServiceBindingList class without having to provide our own searching and sorting algorithms, we can just delegate searching and sorting to the List instance maintained in the Collection base class.

There are several protected BindingList properties that we have to override in the ServiceBindingList class to inform callers that the list supports searching and sorting, the relevant properties are as follows: IsSortedCore (shown in the listing above), SortDirectionCore, SortPropertyCore, SupportsSearchingCore and SupportsSortingCore.

The ServiceBindingList class implements searching by overriding the protected BindingList FindCore method which returns the index position of the specified list item. The ServiceBindingList implementation of the FindCore method delegates the search to the internal List instance maintained in the Collection base class. The method calls the List FindIndex method specifying the private ServiceBindingList FindMatch method as the generic Predicate delegate. The FindMatch method performs a case insensitive comparison of the ServiceInfo ServiceName property to determine equality, the ServiceBindingList only supports searching on the ServiceName property as this is the only Windows Service property that has to be unique.

Sorting the ServiceBindingList is implemented by overriding the protected BindingList ApplySoreCore method, the source code for the override is included in the listing above. We recommend that you code this method defensively and ignore the call, rather than throw an exception, if you can't perform the requested sort. This method is unlikely to be called from user code, it will normally be called by the component or control that is bound to the list and in our experience exceptions thrown from this method are often not handled in the calling code and may percolate back into the runtime, which may cause the client application to be aborted. The ServiceBindingList implementation ensures that the PropertyDescriptor is not null and that it references either the ServiceInfo class or a subclass of it, the method also implements a default sort direction if the specified value is invalid and tests all internal references for null before using them. The method calls the protected virtual ServiceBindingList GetComparer method to retrieve the generic IComparer<T> instance required by the internal List instance to perform the sort. The ServiceBindingList GetComparer method returns an initialized instance of an internal implementation of the IComparer<T> interface which performs a culture aware, case insensitive, comparison of the specified property value. Finally the ApplySortCore method raises the ListChanged event with a value of Reset to notify all bound clients that the list has been rebuilt.

As we have provided an implementation of the ApplySortCore method we also need to provide an implementation of the RemoveSortCore method. The RemoveSortCode method is called from the BindingList implementation of the IBindingList RemoveSort method. The documentation for this IBindingList interface method describes its purpose as follows: "Removes any sort applied using ApplySort". The concept of removing a sort or un-sorting a list is an interesting one, we can't think of an occasion when we have required this functionality, however, we are sure that it is possible to do it if required! The ServiceBindingList override of the protected BindingList RemoveSortCore method is a bit of a copout as it just resorts the list using, assumed, default values, we don't consider it necessary or practical to provide a way of un-sorting the list.

There are, at least, two important Collection class methods that we need to override in the ServiceBindingList class to avoid null elements being added to the list. The protected Collection InsertItem and SetItem methods are overridden to check that the passed in ServiceInfo is not null. An exception is thrown if an attempt is made to add or set a null element or the base class implementation is called if the ServiceInfo parameter is valid.

Using ServiceDataSource and ServiceBindingList in a Client Application

The ServiceDataSource component can, of course, be used solely from code or an application developer can bind a user interface control to the component from code, however, because ServiceDataSource derives from the Component base class, an application developer can add the ServiceDataSource to a Form and use the component designer to set its properties and bind it to another component or a data aware control.

Using the ServiceDataSource component and the ServiceBindingList that it provides at design time is relatively easy for an application developer. In a Windows Forms project, the developer needs to add, position and size a DataGridView control on a Form, then add a BindingSource component and a ServiceDataSource component. With the ServiceDataSource component selected, the developer can set any required property values, although, in most situations the defaults will be fine. With the BindingSource component selected, assign the ServiceDataSource to the DataSource property in the Properties Window. With the DataGridView control selected, assign the BindingSource to the DataSource property in the Properties Window. Click the ellipsis button of the Columns property in the Properties Window and select the ServiceInfo properties to bind to in the resulting dialog. The application is now ready to run and will display, by default, a list of the Windows Services installed on the machine in the DataGridView control.

Conclusion

The ability to bind one or more user interface controls to data or business objects singularly or in list form is undoubtedly a great productivity boost for application developers. However, for component or middle tier developers, coding data objects and/or data lists to which user interface controls can bind still presents considerable challenges. For example, how do you present a read only list, with sorting support, using the existing interfaces available in the .Net Framework? In our experience this is a fairly common requirement and although it can be done, if you don't like throwing not supported exceptions from interface members and we don't, it is, perhaps, not as straight forward as it should be. When and how do you persist changes made by a bound user interface control, property change notifications are fine in some situations, however, if a user changes several properties of the same data object, do you really want to update the data store after each change? The persistence issue becomes more of a problem if you are using a central data store and the application has many users. Again, there are ways to implement a sensible persistence strategy, however, usually this involves adding extra code, which the bound control knows nothing about, that has to be called by the application developer, such as a Save method that persists all of the changed properties in a single call to the data store, for example. Data binding does, however, fit well in some situations and server or business components that provide it will usually be more popular with application developers than components that do not. So there is an incentive for component developers to provide data or business objects that can participate in data binding, we just need one or two more interfaces to make the process a bit more intuitive...