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:

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!

                             

2 Comments

  • Nice! This is exactly what I've been searching for.
    I've wired up my ControlBuilderInterceptor in the web.config, I can see the methods OnProcessGeneratedCode and PreControlBuilderInit being called, however after some concerted fiddling, I realised I didn't need the separate ControlInterceptedEventArgs and event handler, I simply added a static method thus:
    public static void Intercept(Button control)
    {
    if (control.CausesValidation == false)
    {
    if (string.IsNullOrEmpty(control.Attributes["formnovalidate"]))
    {
    control.Attributes.Add("formnovalidate", "");
    }
    }
    }
    And now my HTML5 input types that would normally validate client side are not triggered when I click my "Cancel" button.
    My thanks to you for pointing me in the right direction.

  • Great! ;-)

Add a Comment

As it will appear on the website

Not displayed

Your website