Udostępnij za pośrednictwem


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

Comments

  • Anonymous
    November 19, 2008
    David has an excellent post about a pretty cool ASP.NET feature that you almost certainly don’t know

  • Anonymous
    November 19, 2008
    Please post some practical examples.

  • Anonymous
    November 20, 2008
    Nice Feature! But does it work at a web application project? At a website it works fine!

  • Anonymous
    November 20, 2008
    John, this will work in a Web Application as well.  However, in that case note that the code you generate will only be available to code you write inline within the aspx page (e.g. <% %> blocks), and not from the code behind class.  This is because the code behind is built by VS way before the ControlBuilder even comes into play.

  • Anonymous
    November 20, 2008
    Hello David! That's really neat. Now I have a question. I built a tast-app, wrote a ControlBuilder and applied it to the Page-type and... nothing. Did the same to a regular control and it worked. So, is there a way for me to modify the page's own CodeDom-tree? Specifically, I'd like to change the type of a control to a derived type while the page is being generated. Thanks, Michael

  • Anonymous
    November 20, 2008
    Hi Michael, Try using a FileLevelControlBuilderAttribute instead.  e.g. [FileLevelControlBuilder(typeof(YourPageControlBuilder))]

  • Anonymous
    November 20, 2008
    Thanks, David! It worked like a charm and for what I need it looks like I could even only need to overwrite GetChildControlType. Michael :)

  • Anonymous
    November 20, 2008
    One user on my previous post on ProcessGenerateCode asked how he could associate a ControlBuilder not

  • Anonymous
    November 20, 2008
    Michael, I just wrote a short post about this.  Please make sure you read the part about using the right base class for your builder.

  • Anonymous
    November 23, 2008
    Pick of the week: How Obama Helped Fix Digg Bugs General Vista Bridge Sample Library : A library that allows access to the new features in Vista using managed code. Spike Code and Source Control : K. Scott Allen presents a number of reasons why you should

  • Anonymous
    December 19, 2008
    A Little Holiday Love From The ASP.NET MVC Team

  • Anonymous
    February 18, 2009
    Please post corrections/new submissions to the Dynamic Data Forum . Put FAQ Submission/Correction in

  • Anonymous
    June 04, 2009
    Earlier this week, I wrote a post on using a BuildProvider to create ActionLink helpers .&#160; That