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.

11 comments:

Jonathan said...

Great post, but how would you go about using another type of input control, such as a drop-down or custom input control, in place of the textbox all together?

Marnix said...

Hi Jonathan,
Using a custom input control is not not that different from the samples provided in this post. The ASP.NET BoundField implementation creates the text box in InitializeDataCell. So, when you override InitializeDataCell, instead of calling the base class, create your own control(s) as needed for the mode of the control (edit/view/insert).
Make sure you hook up the input control for databinding.
You'll also have to override ExtractValuesFromCell to return values from your input control.

I was planning to write a post on bound fields using custom input controls. Interested?

Sherri said...

Jonathan,

Take a look at this article: How to Create Custom Bound Fields. The author uses a textbox for editable fields, and a label for display-only fields - hope that will get you started.

Voetsjoeba said...

This is exactly what I was looking for, thanks!

Lane said...

Hi all,

I just found this and got the extended BoundField with a validation control to work beautifully. However, I'm using it in a GridView control that is hooked up to an ObjectDataSource and now the Update function appears to be broken. I have the gridview code in the aspx file wrapped in an Ajax update panel and as best as I can tell, something is bombing in the AjaxToolKit dll. I can't seem to track it down however. The edit/select functions of the GridView seem to work fine and other gridviews that aren't using the custom BoundField yet are still updating like they should.

Any ideas?

Thanks in advance.

Marnix said...

Hi Lane,

Do you get an exception? What type is the exception and what's the message?

Lane said...

Well, It appears that I get an exception but I'm not sure what it is. When stepping through the code, the GridView_RowUpdating method is getting called, but right after that, control seems to go to the AjaxControlToolkit.dll, for which I don't have the source, and errors out. The toolkit doesn't return an error specifically. It might be supressed in there somewhere but no exception get to the main page. I found some articles yesterday mentioning that Validation controls do not work inside Ajax update panels but they were from a couple of years ago and all seemed to think Microsoft was putting out an update for those so that they would. I realized after I posted that I also had it wrapped with an AjaxToolKit Collapsible panel. I took it out of that this morning but it still doesn't work. I'm going to place the grid out of the update panel and see if it works that way to try and narrow it down.

Thanks for the quick response!


Lane

Lane said...

Think I might have tracked it down. It appears my ObjectDataSource was throwing an error and the update panel was suppressing it. I'll post again in a bit with an update.

Thanks,

Lane

Lane said...

That was it. I had missed a boundfield that was marked as readonly when i replaced it with the custom boundfield and left it as editable which was throwing off the ObjectDataSource method and the AjaxToolkit was supressing the error.

Thanks again for the response and also for the great article!

ELHAMAT said...

Marnix,

The DataFormatString="{0:#,##0.00}" doesn't work with alanta:CustomValidatingField, thought it allow you to enter it in the control tag.

Thank you for the great job you did.

Reham

Anonymous said...

I solved this problem inheriting the BoundField and overriding the FormatDataValue method, worked like a charm. only nescessary thing is to put something like in web.config and swap for