Friday, September 28, 2007

Extending BoundField

After working with ASP.NET 2.0 on a couple of projects, one thing keeps biting... Databinding is very powerful but it's also very cumbersome. Even simple things like adding validation to a templated databound control (DetailsView, GridView) is horrible because it requires turning BoundFields into TemplateFields. Now, the BoundField I like; it's clean, simple and helps keep the UI code to a minimum. The TemplateField is an end to a means but it's not the way to go for complex controls. Don't even get me started on maintaining code that uses the TemplateField and all sorts of inline trickery. Believe me; been there done that and did NOT like it. So, what is it that's lacking from the basic fields for databinding? Well, for starters, there's no support for validation. There is no way to control the way a field is rendered; what if I wanted multi line text? or tune the width of the field? There is no way to drop in one of those nifty Ajax control extenders. You may also have noticed that data formatting by the BoundField is quirky to say the least, to say it's broken would be more accurate. Finally, to top it off, there is no such thing as a DropDownField. I'll save that for another post.

Pimp my DataBoundField
Now... what can we do to relieve the pain? Preferably without writing a new BoundField from scratch; we just want to expand it's capabilities. As usual, Lutz Roeders .Net Reflector helps us get a good insight into how BoundField and it's derivatives actually work. A quick peek under the hood reveals a good starting point to hook up some nice customizations:

protected virtual void InitializeDataCell(DataControlFieldCell cell, DataControlRowState rowState) I won't bore you with the details but this method is invoked after the field is created (through InitializeCell) and needs to have the DataCell (the container that will hold the controls that render the field) configured. The default implementation will look for a text box control and configure it for databinding. The text box is, not surprisingly, the control used to render the BoundField in edit and insert mode. With a handle to the TextBox and a way to get to it's container it looks like we have all the requirements for a quick episode of Pimp My DataBoundField.
Note: You could instead override InitializeCell. However, that method is invoked for all types of cells including footers, headers etc. InitializeDataCell is called only on cells that will render actual data so no extra code is needed to detect what type of cell we're working on.

Overriding BoundField
Overriding the BoundField is nothing special, just make sure you override the CreateField method as well to return your special flavour of BoundField.

protected override DataControlField CreateField()
{
return new MyBoundField();
}
Adding validation
With the insight gained above it's actually quite simple to add validation support to BoundField.
protected override void InitializeDataCell(
DataControlFieldCell cell,
DataControlRowState rowState )
{
base.InitializeDataCell( cell, rowState );

// Find the textbox
TextBox text = FindTextBox( cell );

// Could be null if field is readonly or not in edit or insert mode
if ( null != text )
{

// setting the id to help the validator find the field
text.ID = DataField;

// Add a RequiredFieldValidator
RequiredFieldValidator required =
  new RequiredFieldValidator();
required.ErrorMessage = "This is a required field";
required.Display = ValidatorDisplay.Dynamic;
required.ControlToValidate = text.ID;
required.Text = "";
cell.Controls.Add( required );
}
}

You can of course add all kinds of properties to configure the control (Required, ErrorMessage etc.).

Custom validation
In addition to the RequiredFieldValidator you could add support for custom validation using a CustomValidator. By exporting the ServerValidate event on the field you can declaratively bind a validation handler in the page to the field.

<alanta:customvalidatingfield datafield="name" headertext="Name"

onvalidatefield="Validate"/>

Check the sample code for more details.

More cool features
As you can see, it's quite easy to customize the BoundField with some useful features. Adding support for multi line text and specifying the width and height of the input field is straight forward since we have direct access to the TextBox. Add the appropriate properties and tweak the TextBox control in the InitializeDataCell method. Same goes for the Ajax Control Extenders from the Ajax Control Toolkit. Add the extender you need in the InitialzeDataCell and presto.

Date field
A more interesting data field control can be created by combining these techniques. A date field could benefit from a CalendarExtender so users can easily pick a date. Validation can help enforce the entered date is valid and within an allowed range. Please check the sample code for details.