Condividi tramite


Templates - Part 1

ASP.NET is, to perhaps oversimplify things, a template parser at heart. An HTTP Handler (anything that implements IHTTPHandler) can be configured to process HTTP web requests and render results for those requests back to the socket. This inferface is quite simple; IHTTPHandler::ProcessRequest is called which is given an HttpContext representing the HTTP request made to the server. ProcessRequest writes a bunch of bytes out to HttpContext.Response (an HTTPResponse object.) IIS handles the rest.

This is great abstraction because it doesn’t force people who like managed code (like me) to use ASP.NET, which might not meet their needs. Nothing would stop you from writing your own HTTP handler and completely inventing your own ASP.NET. In fact, SharePoint has its own HTTP handler that is able to build dynamic web part pages using serialized instances of “web part” controls from a content database, and map other requests to static “layouts” pages to an installation directory containing these files. There are certain charting components that can handle HTTP requests to GIF files and dynamically build a GIF image and write out the bytes to the response stream. IIS is able to map different file extensions to different IHTTPHandler classes via the web.config file.

For those not so ambitious, ASP.NET conveniently provides its own HTTP hander known as System.Web.UI.PageHandlerFactory. This is what you use out of the box and does all the work opening your ASPX files, parsing junk, building Page objects, etc.

PageHandlerFactory figures out what file you’re looking for, opens the file on disk, and parses this file. A “Page” object is created to represent the page with all of its controls. ASP.NET uses a series of server side markup tags to represent these web controls. These web controls are marked with a “runat=server” attribute. For each control it finds, the ASP.NET page parser instantiates an instance of this control, adds it to the control hierarchy, calls all sorts of various methods on it during the life cycle of the Page object, and is able to build a response stream at the end. Other static text is represented as LiteralControl objects. During the Render stage of the page, ASP.NET’s Page class simply calls Render on its top level controls which recursively call Render on each of their child controls.

ASP.NET’s template parsing code is pretty flexible, and in fact you can take advantage of this code in your own controls. You may have noticed certain web controls that have properties of type “ITemplate”. These properties are useful in declarative code in an ASPX file, but not all that useful in code behinds. One example is the Repeater control. This control can repeat some text for each item in some sort of enumerable data source, and can access properties of the current data item. For example:

<ASP:Repeater id="MyList" runat="Server">

   <HeaderTemplate>

      <table>

   </HeaderTemplate>

   <ItemTemplate>

      <tr><td><%# DataBinder.Eval(Container.DataItem, "MyItem") %></td></tr>

   </ItemTemplate>

   <FooterTemplate>

      </table>

   </FooterTemplate>

</ASP:Repeater>

This control takes a data source, enumerates through it and builds a table with the items found in the datasource. So what are “HeaderTemplate” and “ItemTemplate”? Are they built in to ASP.NET? How are they implemented? Well, you might have already spotted there are ITemplate properties on the Repeater class. ASP.NET is kind enough to build an object that implements ITemplate and set it to these properties of your control for you. So then what does the Repeater control do with them? How can it programmically access the contents of this template and do something useful with it? Also, what is “Container.DataItem” and “DataBinder.Eval?” Let’s take a look at what’s going on here.

 

So DataBinder is a .NET framework class that provides various data binding functionality, you may recognize it if you read my blog on building your own data bound controls.  Eval is a static method that can grab a property (“MyItem”) from an object that exposes a property descriptor (Container.DataItem). If you have no idea what I’m talking about, go read the aforementioned blog. So what is Container? There is no Container class in the .NET framework. Well, Container is an object that represents the current control being generated. In the case of our Repeater control, this is a RepeaterItem object which maps to a specific item in the data source.

 

When Repeater is databound, it loops through its data set and for each item it finds, it creates a RepeaterItem control. RepeaterItem has a DataItem property that the Repeater control sets to the current item in the data source.

 

When each RepeaterItem is initialized, Repeater will build the child control collection of that RepeaterItem object by calling the InstantiateIn method of ITemplate:

 

myTemplate.InstantiateIn(item);

myTemplate is an ITemplate that ASP.NET set for us based on what was in the ASPX file, and item is the RepeaterItem control being initialized. Once this is called, item.Controls will be populated with all the controls defined in the template. Some of these might be other server controls, and others would be LiteralControls representing static text. This is no different than had you called Controls.Add a bunch of times. Inside my template, I can access that instance of RepeaterItem using the “Container” object. Get it?

 

In my next part of this two part series, I’ll be going into a bit more detail and apply some of these techniques to real world scenarios. But in the mean time, go build a template-able control and see how everything works. They’re quite cool and make it easy for developers using your control to really control the UI layout without having to recompile code and set dozens of properties programmically.

 

Have fun!

 

Mike

Comments

  • Anonymous
    April 12, 2006
    Hey Mike,

    What about the new .NET 2.0 DataBinding magic methods like <%#Eval("MyItem")%> and for two-way databinding between richdata controls and datasources: <%#Bind("MyItem")%>.  Could you go into detail on exactly how these work?  I've never found it documented and those methods really do appear to not actually exist from what I've seen.  Is there aw ay to create a method similar to that?  Basically, something that has Container.DataItem as an intrinsic parameter?

    Thanks,

    Aaron
  • Anonymous
    April 12, 2006
    Aaron--

    Both Eval and Bind are handled through the System.Web.UI.ControlBuilder class.  These are actually parsed via regular expressions internally, so you don't have much control over them.  So technically, they're not "methods" they're just recognized keywords that get parsed by ControlBuilder.

    If you wanted to create your own such methods, I'm pretty sure you'd have to create your own control builder.  You can specify a control builder to use with the ControlBuilderAttribute attribute.  I've never done this before, but there's an MSDN article that gets into a bit of detail:

    http://msdn2.microsoft.com/en-us/library/system.web.ui.controlbuilderattribute(VS.80).aspx

    One thing you might want to try (I'm not sure if it'll work)..  Write a control builder derived from System.Web.UI.ControlBuilder, you can then override the virtual AppendSubBuilder method:

    public override void AppendSubBuilder(ControlBuilder subBuilder)

    Then say:

    CodeBlockBuilder builder = subBuilder as CodeBlockBuilder;

    If builder is not null and builder.BlockType == CodeBlockType.DataBinding, then I think this should give you access to the actual data binding expression between the <%# and %> tags.  This should be the string in builder.Content and you can parse it any way you want.  If subBuilder is not a CodeBlockBuilder, then call base.AppendSubBuilder() for normal handling of the expression.

    Like I said, I haven't tried this but I think this should get you started.  Let me know how it goes!

    Mike