How to detect if ASP.NET control properties contain DataBinding expressions?
-
06-07-2019 - |
Question
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.
Solution
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
.
OTHER TIPS
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.