ASP.NET VERSIONS: 2.0
ASP.NET 2.0 Compilation Engine Enhanced
By Dino Esposito
When introduced quite a few years ago, ASP.NET made a key point of using compiled code versus the interpreted code typical of classic ASP and other technologies and languages for the Web. You write ASP.NET pages by specifying the page layout using the ASPX markup and fleshing it out with some compilable code written in a variety of languages. The code to compile goes to a separate but linked class file; the URL to invoke from the client browser points to the page layout saved to an ASPX extension. When a request is made, the Web server retrieves the ASPX resource and ultimately passes a reference to it down to the ASP.NET runtime. What happens next was, for ASP.NET, quite innovative.
In ASP.NET, a system component known as the page parser reads the markup and builds a tree-based representation of the page contents. In doing so, the parser employs a new code object model the CodeDOM. Abstractly speaking, the CodeDOM model builds an in-memory and language-agnostic representation of a piece of code. In ASP.NET, the CodeDOM model describes a class with a user interface and behavior. The user interface is expressed through a tree of server controls; the behavior is expressed through a set of event handlers written in the companion code file. In addition, the page class inherits a base behavior from a root page the System.Web.UI.Page class. Each <asp:XXX> tag is mapped to an instance of a control class; each attribute, instead, is mapped to a control property. The CodeDOM model is finally translated to a compilable class written in a supported language and mixed with the code file provided by the developer.
This is the essence of the ASP.NET compilation process; the process is repeated for each page when it s requested the first time in the application lifetime.
In ASP.NET 2.0, the compilation model underwent some changes and optimizations. One of the additions to the compilation engine is quite curious: ASP.NET 2.0 now supports no-compile pages. As I ll show in a moment, no-compile pages play a precise role in the overall context of ASP.NET and fit perfectly in some real-world scenarios.
What Is a No-compile Page?
If ASP.NET pages make a point of containing compiled code, what s the point of no-compile pages? And what s the difference between no-compile pages and old-fashioned static HTML pages? Let s clear the misconceptions and list some facts.
No-compile pages are regular ASP.NET pages with a few key restrictions. The most important restriction is that no-compile pages are not allowed to contain compilable code. Aside from this, no-compile pages can happily work with server controls and advanced features such as master pages and AJAX extensions. Put another way, no-compile pages do undergo a compilation process, but it s a simplified process optimized for very special scenarios. All in all, the no-compile attribute in the name is a bit misleading. It doesn t mean the page is not compiled before being served to the user. Instead, it refers to the fact that the page is not allowed to contain code that needs compiling, and also doesn t support a bunch of related page attributes. In particular, the following @Page attributes are not supported:
You get a compile-time error if any of these is used in an .aspx page. What does it mean? Quite simply, you can t have a companion code file for a no-compile page with event handlers. In addition, you can t enable asynchronous processing for the page and have no access to the session state. As you may know, code files (i.e., files like default.aspx.cs) are not the only possible way to get some user-defined code inside an ASP.NET page. For example, you could also use code blocks to define executable code within a page, as shown here:
<asp:label runat="server" id="Label1" Text="
<%# Calendar1.SelectedDate %>">
Unfortunately, code blocks are not allowed in no-compile pages, and you get a compile-time exception if you ever try to use them. Another option you have to bring C# or Visual Basic .NET code into ASP.NET pages is based on the <script> tag:
<script runat="server" language="text/c#">
As mentioned, the key restriction with no-compile pages is that no compilable code can be attached to the page; everything else is allowed. You can have AJAX scripts, declarative partial rendering, master pages, grid and details view, data source controls, and themes. In general, if you can craft a rich ASP.NET page using declarative controls a codeless ASP.NET page you can then easily transform that page in a no-compile page. This ability clearly sets a fundamental difference with static HTML pages. No-compile pages are therefore compiled by the ASP.NET runtime, but they take a different and shorter route. As further confirmation that no-compile pages are indeed compiled, note that @Page attributes like Debug, Inherits, Theme, and ClassName are fully supported and, if used, do not raise any compile or run-time exception.
Creating a No-compile Page
How would you create a no-compile page? Once you have an ASP.NET page that fulfills the expectations of no-compile pages, simply set the CompilationMode attribute to Never in the @Page directive. Here s the typical header of a no-compile page:
<%@ Page Language="C#" CompilationMode="Never" %>
The Language attribute is allowed because it indicates the language that the runtime will use for the intermediate class that results from the page markup. However, for a no-compile page, the value of the Language attribute affects only the ASP.NET runtime, but has no impact on the developer. The CompilationMode attribute can take any of the following values from the CompilationMode enum type:
The default value is Always, meaning that each page is treated as a fully-compilable page unless otherwise specified. You can set the compilation mode either on a per-page basis through the @Page directive or globally through the web.config file:
<pages compilationMode="Never" />
If you use the Auto setting, you tell the ASP.NET runtime to examine the characteristics of the page markup and decide what s appropriate for it. If the page doesn t present any attributes that would prevent the no-compile behavior, it is treated as a no-compile page; otherwise, the compilation engine proceeds as in the default case and compiles the page normally.
In which way does the compilation process differ when the page is marked as no-compile? Normally, the compilation process entails the following steps:
- Parsing the ASPX markup to keep track of elements in the source code, including server controls, code blocks, and HTML literals.
- Generating a language-agnostic CodeDOM tree that represents the page class to be constructed. The class being built will inherit from the class specified through the Inherits attribute in the @Page directive.
- Building a first partial temporary CS or VB class from the CodeDOM tree. The language of choice depends on the Language attribute read out of the @Page directive.
- Generation of a temporary second partial class to add protected members for all controls in the page. This class adds a member to the class so any code that refers to, say, Label1, can be successfully executed. The binding between the Label1 member and a valid class instance is made in the code generated in the previous step.
- Compilation of the two partial classes into an assembly. The assembly is saved in the ASP.NET temporary folder on the Web server. Depending on the settings for page compilation, you can have one assembly per page or multiple pages packed in the same assembly. These assemblies are invalidated and recreated when the source of the page is modified.
This process is slightly different for a no-compile page. For a no-compile page, the first step (parsing the ASPX markup) is the same as for a regular page. The rest of the process is different:
- Parsing the ASPX markup to keep track of elements in the source code, including server controls, code blocks, and HTML literals.
- For each trackable element, the ASP.NET runtime builds a control builder ; that is, an instance of a special class that knows how to create a piece of code that is equivalent to the markup. The control builder for the page and child controls is cached and reused later.
These two steps happen only once and are repeated only when the page markup changes. Even from this short description, it should be clear from where the benefit of a no-compile page is derived.
When Is a No-compile Page Helpful?
For the ASP.NET worker process, dealing with a no-compile page is significantly easier and faster. Compiling a page is only phase one in the overall execution of a request. Phase two entails running the page class to get its response for the browser. For regular pages, this means loading the assembly that contains the page class and invoking the ProcessRequest method. Derived from System.Web.UI.Page, each page class then implements the IHttpHandler interface; this is from where the ProcessRequest method comes. For a no-compile page, getting the response is a matter of asking the page s control builder to instantiate the page, along with all child controls. Each and every ASP.NET control is associated with a control builder class (see ASP.NET Control Builders).
As a result, getting the response of a no-compile page is considerably faster as the instance is created directly without loading any additional assembly. Of course, this model works only if the page is all about server controls, with no dependencies on any external piece of code. This is both the major strength and most significant weakness of no-compile pages.
For the typical ASP.NET developer building a typical Web site, no-compile pages are of little help. They may work great for codeless pages obtained by simply combining a bunch of controls, but not beyond this point. On the other hand, if you have a large ASP.NET site, turning off compilation for a set of pages may increase scalability. In the end, I believe that no-compile pages are in ASP.NET 2.0 mostly to facilitate the migration of the SharePoint codebase to the ASP.NET 2.0 platform.
Tricks to Add Code to a No-compile Page
I said that no-compile pages don t let you inject managed code in the application; let s explore a couple of documented tricks to partially work around this limitation.
The first option you have is to use dynamic expressions. A new feature in ASP.NET 2.0, dynamic expressions are a kind of compile-time code block (see Figure 1).
<asp:SqlDataSource runat="server" id="SqlDataSource1"
ConnectionString="<%$ ConnectionStrings:LocalNWind %>"
ProviderName="<%$ ConnectionStrings:LocalNWind.ProviderName %>"
Figure 1: Dynamic expressions are a kind of compile-time code block.
Dynamic expressions serve the purpose of setting control properties at parse time, without relying on the data binding mechanism. To grab the sense of dynamic expressions, think back to the code generated when a data binding (#-expression) is used. In this case, the property assignment is made inside the DataBinding event handler. If you use a dynamic expression, the assignment is made in the method that builds the control.
Dynamic expressions wrap a piece of code that can be embedded inside an ASP.NET page, including no-compile pages. ASP.NET 2.0 defines a few inbox expressions, for connection strings, application settings, and resources; however, custom expressions can be built, as well. It s as easy as deriving a new class from the ExpressionBuilder base class. In doing so, a Boolean flag in the expression class indicates whether the expression can be embedded in a no-compile page. Note that you must enable this support explicitly in the code of the expression class.
Another trick is deriving the page from a custom base class that defines a few handlers for common control events. The problem in this case is that even if there s, say, a Label1 control in the page, because of the different compilation engine there will be no Label1 member added to the page class (see the fourth step of the regular compilation process).
The following code shows a trick that lets you retrieve and set the Text property of the Label1 control:
void Page_Load(object sender, EventArgs e)
Label Label1 = (Label) Page.FindControl("Label1");
Label1.Text = DateTime.Now.ToShortDateString();
The idea is that you retrieve the control reference before accessing and setting its properties.
What Will Come Up?
The role of no-compile pages is destined to grow in importance as the future ASP.NET platform (past the Orcas release scheduled in a matter of weeks by the time you read this) unveils its features. You ve probably heard about upcoming support for dynamic languages in future ASP.NET platforms. This improvement passes through significant extensions being made to the structure of no-compile pages.
To enable support for dynamic languages Python, for example in ASP.NET, the compilation mechanism will be redesigned to include an extended version of no-compile pages that support languages other than static languages such as C#. Basically, a custom page parser is defined that allows external code to kick in and work side by side with the parser and instruct it on what to do. Add this to the extensions being made to the CLR for enabling dynamic languages and you have the big picture of the future ASP.NET platform in front of you. For a quick summary, take a look at http://blogs.msdn.com/hugunin/archive/2007/04/30/a-dynamic-language-runtime-dlr.aspx.
Dino Esposito is a mentor with Solid Quality Mentors (http://www.solidq.com) and specializes mainly in ASP.NET and AJAX solutions. He s the author of Introducing Microsoft ASP.NET AJAX (Microsoft Press, May 2007) and wrote the two volumes of Programming Microsoft ASP.NET 2.0, also for Microsoft Press. Late-breaking news is available at http://weblogs.asp.net/despos.