Creating a ControlBuilder for the page itself

One user on my previous post on ProcessGenerateCode asked how he could associate a ControlBuilder not with a control, but with the page itself.  There is in fact a way to do it, and it’s another one of those things that have never really been advertized.  The trick is that instead of associating the ControlBuilder using the standard ControlBuilderAttribute, you need to use a FileLevelControlBuilderAttribute.  Let’s walk through a little example.

First, we create a custom page type with that attribute:

 [FileLevelControlBuilder(typeof(MyPageControlBuilder))]
public class MyPage : Page {
}

We could do all kind of non-default thing in the derived Page, but I’ll keep it simple.  Now let’s write the ControlBuilder for it:

 // Note that it must extend FileLevelPageControlBuilder, not ControlBuilder!
public class MyPageControlBuilder : FileLevelPageControlBuilder {
    public override Type GetChildControlType(string tagName, IDictionary attribs) {
        // If it's a Label, change it into our derived Label
        Type t = base.GetChildControlType(tagName, attribs);
        if (t == typeof(Label)) {
            t = typeof(MyLabel);
        }

        return t;
    }
}

The most important thing to note here is that it extends FileLevelPageControlBuilder, not ControlBuilder.  If you extend ControlBuilder, it may seem like it works on simple pages but some things will break (e.g with master pages).

As for what we do in that class, it’s just some random thing to demonstrate that it is in fact getting called.  Here, we change the type of all Labels to a derived type which slightly modifies the output:

 public class MyLabel : Label {
    public override string Text {
        set { base.Text = "[[" + value + "]]"; }
    }
}

And then of course, we need to tell our aspx page to use our base class:

 <%@ Page Language="C#" Inherits="MyPage" %>

The full runnable sample is attached below.

FileLevelControlBuilder.zip

Comments

  • Anonymous
    November 21, 2008
    PingBack from http://www.alvinashcraft.com/2008/11/21/dew-drop-november-21-2008/

  • Anonymous
    November 22, 2008
    I know this is just an example scenario for the file-level control builder, but in fact for this specific scenario, there isn't a need to write a control builder. Instead one could just remap Label to MyLabel in web.config using the tag mapping feature.

  • Anonymous
    November 23, 2008
    Right, as Nikhil says tag remapping can be used to easily change a control type. This was just some random example to quickly demonstrate that the Page ControlBuilder does in fact get called. There is no limit to what you can do in your ControlBuilder, especially once you start using ProcessGeneratedCode (see my previous post).

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

  • Anonymous
    December 22, 2008
    This is not working for me. I've configured my website (not web app) to have my base page: [FileLevelControlBuilder(typeof(BasePageBuilder))]    public class BasePage : Page {} My BasePageBuilder: public class BasePageBuilder : FileLevelPageControlBuilder    {        public override Type GetChildControlType(string tagName, System.Collections.IDictionary attribs)        {            return base.GetChildControlType(tagName, attribs);        }    } And when I put the debug on it, it does not ever hit it. Anything I'm missing?

  • Anonymous
    December 22, 2008
    Vi, please see the attached sample which is also using a Web Site and not a Web App.  If that one works for you, you should be able to identify what is different about your app that causes it not to work.

  • Anonymous
    December 22, 2008
    Well it works on the sample. But it still does not hit the debug point on the GetChildControlType override. When does that get called because it works. Thanks for the reply.

  • Anonymous
    December 22, 2008
    It only gets called at the time the page gets parsed and compiled, so if it's already compiled you would indeed not hit this.  Try just resaving the aspx page and requesting it again, and you'll see that BP getting hit.

  • Anonymous
    December 22, 2008
    I figured out the difference. It doesn't appear to work when your page is set to use a seperate code file. (Code-Behind). That sound right?

  • Anonymous
    December 22, 2008
    You're right!  The good news is that it's easy to fix.  You just need to add CodeFileBaseClass="MyPage" to the @page directive of the aspx.  This is needed because the aspx parser doesn't actually know what base type your code file uses (since it can't parse the code).  So it needs that little hint.

  • Anonymous
    December 22, 2008
    Yup, that did it!! Awesome, thanks!

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

  • Anonymous
    July 08, 2010
    No problem, thanks David! I am curious as to how to do both instance (i.e. the instance of the control in a parent page) processing as well as type (i.e. the ascx file that defines the control) processing. It seems that having both a ControlBuilder as well as a FileLevelControlBuilder doesn't do the trick, and using just FileLevelControlBuilder seems to only process the definition ascx file. Thanks in advance!

  • Anonymous
    July 09, 2010
    M B: not sure I've ever tried this.  Does the instance case work if you just set the ControlBuilder?  Note that you need to have it extend UserControlControlBuilder.

  • Anonymous
    March 19, 2012
    Hello David, I'm not sure if my post is relevant but there is no one else who could help me I'm trying to implement a Server Control that uploads files with or without an UpdatePanel around it. I use an IFrame technique for it, and everything works fine if I create in run time an aspx file with FileUpload control and then I call this page from an IFrame. But my constraints are that no additional file is created, it should be accessed as a resource from a DLL of my Server Control. The WebResource.axd doesn't work for aspx pages, so I use VirtualPath. Again everything works fine if add some code in Global.asax Application_Start method to register VirtualPathProvider. But another constraint is that the only thing a user should do is to place this control from a toolbox on the page and not bother with additional code. So I need some code to be placed in Global.asax.cs file during design or parse time and if this file doesn't exist, to create it. And all of it - BEFORE the first build I presume this is possible using ControlBuilder class for my Server Control. I just can't find out how. Could you help me? I don't have much code yet, here is what I've got [ToolboxData("<{0}:FileUploader runat=server />")] [ControlBuilder(typeof(CustomControlBuilder))] public class FileUploader : WebControl {    protected override void RenderContents(HtmlTextWriter output)    {        output.Write(string.Format("<iframe src='{0}' style='border:1px solid green'></iframe>", VirtualResourcePath + "/FileUpload.aspx"));    } }     public class CustomControlBuilder : ControlBuilder {    public override object BuildObject()    {        return base.BuildObject();    } } Could you please help me out, or advise another way of implementing that?

  • Anonymous
    March 19, 2012
    @Pavel this seems different from what this post discusses. Did you try posting your question on a public forum like StackOverflow of http://forums.asp.net/? This is a better way to get more eyes on it, while no one else will see it here :)