ProcessGeneratedCode: A hidden gem for Control Builder writers

If you’ve ever written any non-trivial ASP.NET control, you’re probably familiar with the concept of a Control Builder.  Basically, it’s a class that you associate with your control and that affects the way your control gets processed at parse time.  While ControlBuilder has been around since the ASP.NET 1.0 days, a very powerful new feature was added to it in 3.5 (i.e. VS 2008).  Unfortunately, we never had a chance to tell people about it, and a web search reveals that essentially no one knows about it!  Pretty unfortunate, and obviously, the point of this post is to change that. :-)

So what is this super cool feature?  Simply put, it lets the ControlBuilder party on the CodeDom tree used for code generation of the page.  That means a ControlBuilder can inspect what’s being generated, and make arbitrary changes to it.

Warning: this post assumes some basic knowledge of CodeDom.  If you are not familiar with it, you may want to get a basic introduction to it on MSDN or elsewhere before continuing.

 

How do you use this?

To use this feature, all you have to do is override the new ProcessGeneratedCode() method on ControlBuilder.  here is what this method looks like:

 //
// Summary:
//     Enables custom control builders to access the generated Code Document Object
//     Model (CodeDom) and insert and modify code during the process of parsing
//     and building controls.
//
// Parameters:
//   codeCompileUnit:
//     The root container of a CodeDOM graph of the control that is being built.
//
//   baseType:
//     The base type of the page or user control that contains the control that
//     is being built.
//
//   derivedType:
//     The derived type of the page or user control that contains the control that
//     is being built.
//
//   buildMethod:
//     The code that is used to build the control.
//
//   dataBindingMethod:
//     The code that is used to build the data-binding method of the control.
public virtual void ProcessGeneratedCode(CodeCompileUnit codeCompileUnit, CodeTypeDeclaration baseType, CodeTypeDeclaration derivedType, CodeMemberMethod buildMethod, CodeMemberMethod dataBindingMethod);

 

So basically you get passed a bunch of CodeDom objects and you get to party on them.  It may seem a bit confusing at first to get passed so many different things, but they all make sense in various scenarios.

  • The CodeCompileUnit is the top level construct, which you would use for instance if you wanted to generate new classes.
  • Then you have the two CodeTypeDeclarations, which represent the classes that are generated for this page.  The reason there are two has to do with how the page is generated.  First, there is a partial base class just has control declaration, and gets merged with the code behind class the user writes (in Web Sites, Web Applications are a bit different).  Then we derive another class from it, which has the bulk of the code that makes the page run.  I know this seems confusing, so let me give you a rule of thumb: when in doubt, use baseType rather than derivedType.
  • Finally, we have the two CodeMemberMethods, which represent methods that relate to the particular control that’s being built.  One is for the method that builds the control (e.g. assigns its properties from the markup, …), and the other one deals with data binding.

Tip to make more sense of all that stuff: a great way to learn more about the code ASP.NET generate is simply to look at it!  To do this, add debug=”true” on your page, add a compilation error in there (e.g. <% BAD %>) and request the page.  In the browser error page, you’ll be able to look at all the generated code.

 

How about a little sample to demonstrate?

Let’s take a look at a trivial sample that uses this.  It doesn't do anything super useful but does demonstrate the feature.  First, let’s write a little control that uses a ControlBuilder:

 [ControlBuilder(typeof(MyGeneratingControlBuilder))]
public class MyGeneratingControl : Control {
    // Control doesn't do anything other than generate code via its ControlBuilder
}

Now in the ControlBuilder, let’s implement ProcessGeneratedCode so that it spits out a little test property:

 // Spit out a property that looks like:
//protected virtual string CtrlID_SomeCoolProp {
//    get {
//        return "Hello!";
//    }
//}
var prop = new CodeMemberProperty() {
    Attributes = MemberAttributes.Family,
    Name = ID + "_SomeCoolProp",
    Type = new CodeTypeReference(typeof(string))
};

prop.GetStatements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression("Hello!")));

baseType.Members.Add(prop);

 

So  it just generates a string property with a name derived from the control ID.  Now let’s look at the page:

     <test:MyGeneratingControl runat="server" ID="Foo" />

 

And finally, let’s use the generated property in code.  The simple presence of the this tag allows me to write:

 Label1.Text = Foo_SomeCoolProp;

 

And the really cool things is that Visual Studio picks this up, giving you full intellisense on the code generated by your ControlBuilder.  How cool is that! :)

 

Full runnable sample is attached to this post.

 

What about security?

At first glance, it may seem like this feature gives too much power to ControlBuilders, letting them inject arbitrary code into the page that’s about to run.  The reality is that it really doesn’t let an evil control do anything that it could have done before.  Consider those two cases:

  • Full trust: if the Control and ControlBuilder are running in full trust, then they can directly do anything that they want.  They gain nothing more from  generating code that does bad things
  • Partial trust: if the Control and ControlBuilder are running in partial trust, then they can’t do much damage themselves directly, and can attempt to do additional damage indirectly via code they generate.  However, if the app is running in partial trust (as is typical in hosted environments), then the dynamically generated page code also runs in partial trust.  So effectively, it can’t do any more than the control could do directly.

 

Conclusion

ProcessGeneratedCode is a pretty powerful feature, giving your ControlBuilders full control over the code generation.  It’s also a pretty advanced feature, and you can certainly shoot yourself in the foot with it if you’re not careful.  So be careful!

ProcessGeneratedCodeTestSite.zip