views:

292

answers:

2

I have a custom control which inherits from System.Web.UI.Control and some of its properties can be declaratively set using databinding expressions. e.g.

<foo:Foo runat="server" MyFoo="<%# this.GetFoo() %>" />

Now, when I do that I need to call .DataBind() on the control (or one of its parents) to evaluate these expressions.

What I would like to be able to do is detect if any properties were set this way and just automatically have the custom control call this.DataBind() after OnPreRender or there about.

So the question: how do I detect if databinding expressions are waiting to be executed?

I'm convinced that in some ControlBuilder or DataBindContext class lives the information needed to determine this. I've hunted around with Reflector and cannot seem to find it.

I should add, that I don't want to pay the overhead of executing DataBind() if no direct properties have been assigned this way. This is why I'd like to detect before hand. This class is extremely light but I'd like the ability to declaratively set properties without needing any code behind.

A: 

There is an internal ArrayList called SubBuilders on the ControlBuilder class. For each databinding expression TemplateParser enocunters, ProcessCodeBlock() adds a CodeBlockBuilder object with a BlockType property CodeBlockType.DataBinding to SubBuilders.

So if you can get a handle to the ControlBuilder you want, you should be able to reflectively iterate over SubBuilders and look for objects of type CodeBlockBuilder where BlockType == CodeBlockType.DataBinding.

Note of course this is all kinds of nasty and I'm really suspicious this is the best way to solve your core problem. If you take two steps back and look at the original problem, maybe post that on Stackoverflow instead - there's plenty of super-smart people who can help come up with a good solution.

Rex M
That was the original problem: data binding expressions sometimes present, I'd like to not have to add code behind if I don't have to which means the control itself should call `DataBind` and I didn't want to pay the performance hit to always be `DataBind`ing. I was looking through how `System.Web.UI.WebControls.BaseDataBoundControl` knows to auto-bind but I thought its way was too heavy/clunky for this simple need. I wanted the actual binding information rather than tracking like they do. Sounds like the actual info may end up more expensive (reflection) but I'll check it out first. Thanks.
McKAMEY
+1  A: 

Doing some deeper looking into ControlBuilder, I noticed that the compiled factory for each control instance will attach a DataBinding event handler when there are data binding expressions present. I've found that checking for this seems to be a very reliable method for determining if data binding needs to occur. Here is the basis of my solution to the problem:

using System;
using System.Reflection;
using System.Web.UI;

public class AutoDataBindControl : Control
{
 private static readonly object EventDataBinding;
 private bool needsDataBinding = false;

 static AutoDataBindControl()
 {
  try
  {
   FieldInfo field = typeof(Control).GetField(
    "EventDataBinding",
    BindingFlags.NonPublic|BindingFlags.Static);

   if (field != null)
   {
    AutoDataBindControl.EventDataBinding = field.GetValue(null);
   }
  }
  catch { }

  if (AutoDataBindControl.EventDataBinding == null)
  {
   // effectively disables the auto-binding feature
   AutoDataBindControl.EventDataBinding = new object();
  }
 }

 protected override void DataBind(bool raiseOnDataBinding)
 {
  base.DataBind(raiseOnDataBinding);

  // flag that databinding has taken place
  this.needsDataBinding = false;
 }

 protected override void OnInit(EventArgs e)
 {
  base.OnInit(e);

  // check for the presence of DataBinding event handler
  if (this.HasEvents())
  {
   EventHandler handler = this.Events[AutoDataBindControl.EventDataBinding] as EventHandler;
   if (handler != null)
   {
    // flag that databinding is needed
    this.needsDataBinding = true;

    this.Page.PreRenderComplete += new EventHandler(this.OnPreRenderComplete);
   }
  }
 }

 void OnPreRenderComplete(object sender, EventArgs e)
 {
  // DataBind only if needed
  if (this.needsDataBinding)
  {
   this.DataBind();
  }
 }
}

This solution disables itself if no DataBinding event handler is attached or if the control is manually data bound (directly or via a parent).

Note that most of this code is just jumping through hoops to be able to test for the existence of the event. The only reflection needed is a one-time lookup to get the object used as the key for EventDataBinding.

McKAMEY