Browse DevX
Sign up for e-mail newsletters from DevX


Write Your Own Provider For the ASP.NET DataGrid : Page 3

The ASP.NET DataGrid is a very powerful control, capable of displaying anything that can be represented in tabular format. Find out how you can use it more effectively by creating custom data providers to populate your grids.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

Determining Properties Dynamically
No solution has ever been perfect in the world of software engineering, and the previous example is no exception. In fact, it has a very significant shortcoming: Now that the DataGrid isn't discovering the column structure, it means you have to know the structure of your columns before you can deploy the solution. That means that if the XML schema changes, you would have to recompile your code. Worse, you would have to create a new class for every possible XML schema, which is outright impossible. In addition, your column names are limited to valid identifiers of the Common Language Runtime (CLR) languages, so even a column name as common as First Name would be illegal because it contains a space.

It would be great if the grid obtained column information from one of the built-in lookup structures of the .NET collection framework, for example, a Dictionary or HashTable, but unfortunately, the framework doesn't work that way. The grid control always uses the properties of the provider class to get the column names—unless your class implements the ICustomTypeDescriptor interface, in which case, the DataGrid queries that interface first. That behavior gives you the opportunity to dynamically define your column information at runtime.

ICustomTypeDescriptor, by itself, is a very detailed interface, providing type information for every conceivable aspect of a .NET component. However, it turns out that the DataGrid only needs the GetProperties() member function; therefore you can safely provide a minimal implementation for the remaining members.

Your GetProperties() method must build a list of the column names that you want the grid to display, and must return this information as a PropertyDescriptorCollection object, which is essentially a structured collection of PropertyDescriptor objects. The PropertyDescriptor class has a few abstract methods, so you must derive your own class from it, and override the abstract methods appropriately.

With this information at hand, start by creating a ColumnStructure class. Note that you must call the base class constructor with the name parameter, which names the column. Also note that the name you provide when creating the new ColumnStructure is fixed—you cannot rename it, because there's no public Name property for your code to set later.

public class ColumnStructure : PropertyDescriptor { private string name; public ColumnStructure(string name) : base(name, null) { this.name = name; }

The ColumnStructure class will not be serializable, and is read-only, so you can provide empty implementations for the methods the grid will not call.

public override bool IsReadOnly { get { return true; } } public override void SetValue(object component, object value) { } public override bool CanResetValue( object component) { return false; } public override void ResetValue( object component) { } public override bool ShouldSerializeValue (object component) { return false; }

The ComponentType method should return a Type object for the component that hosts the property. In this example, the RowStructure class, described in detail later in this article, is the container.

public override Type ComponentType { get { return typeof(RowStructure); } }

On the other hand, the PropertyType method returns the type of the property itself. Here, both regionId and regionDescription are strings.

public override Type PropertyType { get { return typeof(string); } }

When the grid needs to obtain the value of a column, it calls the GetValue() method, and passes in the row object. This means that your property must know how to retrieve its value by examining the component passed to it.

public override object GetValue( object component) { return ((RowStructure)component).Node.Attributes [name].Value; } }

Next comes the RowStructure class. As you can guess, a RowStructure instance stores the data for the entire row, and acts as the binding element between your provider and ColumnStructure. Note how the code defines the public property Node solely to enable a ColumnStructure to examine the node in the GetValue() method.

public struct RowStructure : ICustomTypeDescriptor { private XmlNode node; public RowStructure(XmlNode node) { this.node = node; } public XmlNode Node { get { return node; } }

The GetProperties() method of the ICustomTypeDescriptor interface is how you can return a customized property set. This method is where you analyze your row, and return its column information.

// ICustomTypeDescriptor interface // .... public PropertyDescriptorCollection GetProperties() { PropertyDescriptor[] pd = new PropertyDescriptor[node.Attributes.Count]; for (int i = 0; i < node.Attributes.Count; i++) pd[i] = new ColumnStructure(node.Attributes[i].Name); return new PropertyDescriptorCollection(pd); } }

The two classes you have seen so far provide the bulk of the implementation. The RegionProvider class from the previous scenario essentially stays the same, since the way the XML file is traversed remains unchanged. The only difference is, each time the DataGrid invokes the Current() property, your code returns a RowStructure object to the grid, rather than a Region.

public object Current { get { try { XmlNode node = root.ChildNodes[current]; return new RowStructure(node); } catch(Exception) { } throw new InvalidOperationException(); }

The infrastructure to dynamically determine property names can be confusing. Yet, the basics are quite straightforward, as illustrated in Figure 4.

Figure 4. Dynamic Property Determination
After these improvements, the provider is a lot more flexible than it was. You can now change the attributes of your rows at will without going through the trouble of modifying your Region class each time. As you have seen, creating a custom data provider frees you from reliance on ADO.NET and lets you populate the DataGrid with fewer intermediaries, enabling a more structured relationship between the DataGrid and the real source of your data.

Hasmet Akgun is an independent consultant based in New York City. When not helping his clients deploy and coexist .NET framework solutions with legacy technologies, he enjoys writing articles for the developer community, and dreams of adopting another cat. Reach him by e-mail at h.akgun@acm.org.
Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date