ASP.NET Web Forms Extensibility: Control Builder Interceptors
After my previous post on Control Builders, what could possibly come next? Of course, Control Builder Interceptors! Not much documentation on this one, which is a shame, because it is an even more powerful feature that was recently introduced in ASP.NET 4.5.
A Control Builder Interceptor inherits from, unsurprisingly, ControlBuilderInterceptor. This is configured for the whole application, in the Web.config file, in the compilation section, by a controlBuilderInterceptorType (sorry, no link, since the ASP.NET 4.5 documentation is not online) attribute:
<compilation targetFramework="4.5" controlBuilderInterceptorType="MyNamespace.MyControlBuilderInterceptor, MyAssembly" />
Similarly to Control Builders, a Control Builder Interceptor allows us to:
- Set property values for controls declared on the markup (PreControlBuilderInit);
- Modify the code used for instantiating the control on the page or the page itself (OnProcessGeneratedCode).
Granted, less than Control Builders, but the point here is that this is fired for all markup-declared controls, not just those that have a specific Control Builder applied to. With that in mind, we can write code like this:
public class MyControlBuilderInterceptor : ControlBuilderInterceptor
{
//raised for every control on markup
public static event Action<ControlInterceptedEventArgs> ControlIntercepted;
public override void OnProcessGeneratedCode(ControlBuilder controlBuilder, CodeCompileUnit codeCompileUnit, CodeTypeDeclaration baseType, CodeTypeDeclaration derivedType, CodeMemberMethod buildMethod, CodeMemberMethod dataBindingMethod, IDictionary additionalState)
{
var controlDeclaration = buildMethod.Statements[0] as CodeVariableDeclarationStatement;
if (controlDeclaration != null)
{
var controlName = controlDeclaration.Name;
buildMethod.Statements.Insert(buildMethod.Statements.Count - 1, new CodeSnippetStatement(String.Concat(this.GetType().FullName, ".Intercept(@", controlName, ");")));
}
base.OnProcessGeneratedCode(controlBuilder, codeCompileUnit, baseType, derivedType, buildMethod, dataBindingMethod, additionalState);
}
public override void PreControlBuilderInit(ControlBuilder controlBuilder, TemplateParser parser, ControlBuilder parentBuilder, Type type, String tagName, String id, IDictionary attributes, IDictionary additionalState)
{
if ((attributes != null) && (attributes.Contains("Text") == true))
{
//make property value uppercase
attributes["Text"] = (attributes["Text"] as String).ToUpper();
}
base.PreControlBuilderInit(controlBuilder, parser, parentBuilder, type, tagName, id, attributes, additionalState);
}
public static void Intercept(Control instance)
{
var handler = ControlIntercepted;
if (handler != null)
{
handler(new ControlInterceptedEventArgs(instance));
}
}
}
And there you have it. By adding an event handler to MyControlBuilderInterceptor.ControlIntercepted, we can analyze and change the properties of every control:
[Serializable]
public sealed class ControlInterceptedEventArgs : EventArgs
{
public ControlInterceptedEventArgs(Control control)
{
this.Control = control;
}
public Control Control { get; private set; }
}
MyControlBuilderInterceptor.ControlIntercepted += e =>
{
var myControl = e.Control as MyControl;
if (myControl != null)
{
myControl.Text = myControl.Text.ToUpper();
}
};
Stay tuned for more extensibility points of your favorite framework!