Under the Hood

Access Compile-time Code Programmatically

CoreCoder

LANGUAGES: C#

ASP.NET VERSIONS: 1.x

 

Under the Hood

Access Compile-time Code Programmatically

 

By Dino Esposito

 

Some things in life are unforgettable when they happen for the first time your first blue-screen-of-death, perhaps your first kiss. To this list, I would add your first ASP.NET error. How special can an ASP.NET error be to get mentioned in such company as your first kiss? The first time I got a compile error from ASP.NET I believe it was only called ASP+ at the time one thing caught my eye: the possibility to take a look at the source code of the page. I m not talking about the source code of the .aspx page being edited through Visual Studio.NET. I m simply talking about the source code of the dynamically created class that ASP.NET builds after parsing the contents of the .aspx file and any attached code-behind class. Figure 1 demonstrates this.

 


Figure 1: Accessing the complete compilation source in case of compile errors.

 

When you get a compile error from ASP.NET, you re shown a page with summary information about the error, plus a couple of links. One of these links expands to show the detailed compiler output; the other link details the complete compilation source. I would be a liar if I said that I ve found this information helpful in more than only a few situations. In many cases, this information doesn t help you determine the cause of the error; to ascertain what happened, the stack trace is normally more instrumental. Yet, I was pleasantly surprised to find that developers were authorized to snoop into the internals of ASP.NET. So I decided that I had to learn more about the feature the ability to access compile-time code programmatically.

 

The Temporary Folder

As usual, what is a strong commitment on Sunday night becomes a faded memory on Monday morning when you get back to the reality of work. Swamped with a heap of less-enticing tasks, for months I failed to investigate further that astounding feature of ASP.NET until a client suggested that I replace the default ASP.NET identity (the ASPNET or NETWORK SERVICE account) with a personalized one. The client didn t really want to grant the ASP.NET worker process more privileges than strictly necessary; he just wanted to use a custom account to feel free to modify it later without affecting other applications.

 

The ASP.NET worker process does not run under the aegis of the almighty SYSTEM account, and does not impersonate by default. To change the system account, you edit the section of the configuration file and indicate the new account:

 

 

You can change it to any existing account as long as the selected account is granted a minimum set of privileges. Aside from password settings and rights assignments, such as Deny logon locally and Log on as a service , the default ASP.NET account is given some NTFS permissions to operate on certain folders and create temporary files and assemblies. Guess what happened? We changed the system account with another account that lacked all required NTFS permissions. As a result, ASP.NET stopped working.

 

The ASP.NET system account must be granted full access to the GAC, the .NET Framework installation path, the Windows system folder, the Web site root, and the Temporary ASP.NET Files folder. What s the Temporary ASP.NET Files folder? This folder represents the file system subtree in which all temporary files are generated. ASP.NET is granted full control over the entire subtree. Permissions on this subtree are granted both to the ASP.NET worker process account and the impersonated user, if any. It is below this folder that ASP.NET creates and stores any intermediate and transient files needed to process a request. To programmatically retrieve the source code of the .aspx page being compiled, you ought to look into this directory.

 

Locate the Temporary Folder

Temporary files generated for ASP.NET pages are cached in the Temporary ASP.NET Files folder. Here s the path for version 1.1 of the .NET Framework:

 

%SystemRoot%\Microsoft.NET\Framework\v1.1.4322\

 Temporary ASP.NET Files

 

The physical path depends on the version of the .NET Framework you installed. The directory path for version 1.0 of the .NET Framework includes a subdirectory named v1.0.3705. Beta 1 of ASP.NET 2.0 operates under the v2.0.40607 subdirectory. By looking at the subdirectories of Temporary ASP.NET Files, you have a quick way to figure out how many versions of ASP.NET are currently installed on a server machine.

 

The Temporary ASP.NET Files folder has one child directory per each application executed. The name of the subfolder matches the name of the virtual directory of the application. Pages that run from the Web server s root folder are grouped under the Root subfolder.

 

Page-specific assemblies are cached in a subdirectory placed a couple of levels down the virtual directory folder. It is fairly difficult to make any sense of the names of these child directories. Names are the result of a hash algorithm based on some randomized factor along with the application name. A typical path is shown in the following listing (the last two directories have fake but realistic names):

 

\Framework

 \v1.1.4322

   \Temporary ASP.NET Files

     \MyWebApp

       \3678b103

         \e60405c7

 

Regardless of the actual algorithm implemented to determine the folder names, from within an ASP.NET application the full folder path is retrieved using the following, fairly simple, code:

 

string tempAspNetDir = HttpRuntime.CodegenDir;

 

What are the contents of this folder? The folder contains any dynamically generated assemblies for the page, including the assembly with the run-time page class. The folder also stores the compiler s output. When the page runs in debug mode, the folder also contains the source file of the class created parsing the source of the .aspx file. It is worth noticing that these files are always created in the folder, regardless of the debug settings. However, they are deleted at the end of the request if the page isn t running in debug mode. Figure 2 shows the typical contents of a temporary folder.

 


Figure 2: Typical contents of a Temporary ASP.NET Files folder.

 

Get the Dynamic Assembly

The name of the dynamically created class to process the page is determined using a clear and well-known algorithm. The class belongs to the ASP namespace and has the name that results from the .aspx file name once you replace the dot (.) with the underscore (_). For example, ASP.Default_aspx. So how does the ASP.NET runtime determine, instead, the assembly name for a particular .aspx page?

 

As you can see in Figure 2, the temporary folder contains a few XML files with a particular naming convention: [filename].[hashcode].xml.

 

If the page is named, say, default.aspx, the corresponding XML file can be named like this: default.aspx.2cf84ad4.xml. What s the purpose of this companion XML file?

 

The XML file is created when the page is compiled. The following code snippet demonstrates the typical contents of the file:

 

 hash="fffffeda266fd5f7">

 

 

I ll say more about the schema of the file in a moment. For now, it will suffice to look at the assem attribute. The attribute value is simply the name of the assembly (without an extension) created to execute the default.aspx page. The file c5gaxkyh.dll is the assembly that represents the default.aspx page. The other assembly you see in Figure 2 is the compiled version of the global.asax file. (If not specified, a standard global.asax file is used.) The objects defined in these assemblies can be viewed with any class browser tool, including Microsoft IL Disassembler, ILDASM.exe.

 

As mentioned, if the Debug attribute of the @Page directive is set to true, in the same folder as the assembly you ll also find the source code of the page class. The source code is a Visual Basic.NET or C# file, according to the value of the Language attribute of the .aspx page. The name of the source file takes the form of assembly_name.0.ext, where assembly_name denotes the name (without extension) of the assembly and ext denotes the language extension. For example, c5gaxkyh.0.cs is the C# source file for default.aspx.

 

It is interesting to note that the middle 0 is a constant in ASP.NET 1.x, but is a variable in ASP.NET 2.0. The middle index refers to the number of distinct files that contribute to the creation of the page class. This number is invariably 1 (a 0-based index is used) in ASP.NET 1.x, but can be a larger number in ASP.NET 2.0. For example, ASP.NET 2.0 pages based on a master page are composed of two source files with the middle index equal to 0 and 1, respectively.

 

While I m on the subject of ASP.NET 2.0, it would be nice to make some statements about the new compilation model and its underlying implementation. What is stated in this article applies to ASP.NET 1.x, but is also largely true for ASP.NET 2.0. The schema of the aforementioned XML file changes in ASP.NET 2.0, and new file formats are introduced, but the overall model of file parsing and class creation remains unchanged.

 

Detect Page Changes

As mentioned earlier, the dynamically compiled assembly is cached and used to serve any future request for the page. However, changes made to an .aspx file will automatically invalidate the assembly, which will be recompiled to serve the next request. The link between the assembly and the source .aspx file is kept in the XML file we mentioned a bit earlier. Let s recall it:

 

 hash="fffffeda266fd5f7">

 

 

The name attribute of the node contains the full path of the file associated with the assembly whose name is stored in the assem attribute of the node. On the other hand, the type attribute contains the name of the class that renders the .aspx file in the assembly. The actual object running when, say, default.aspx is served, is an instance of a class named ASP.Default_aspx.

 

Based on the Win32 file notification change system, this ASP.NET feature enables developers to quickly build applications with a minimum of process overhead. Users, in fact, can just hit save to cause code changes to immediately take effect within the application. In addition to this development-oriented benefit, deployment of applications is greatly enhanced by this feature, as you can simply deploy a new version of the page that overwrites the old one.

 

When a page is changed, it s recompiled as a single assembly (or as part of an existing assembly), and reloaded. ASP.NET ensures that the next user will be served the new page outfit by the new assembly. Current users, on the other hand, will continue viewing the old page served by the old assembly. The two assemblies are given different names (because they are randomly generated) and therefore can happily live side by side in the same folder, as well as be loaded in the same AppDomain.

 

Refresh Assemblies

When a new assembly is created for a page as the effect of an update, ASP.NET verifies whether the old assembly can be deleted. If the assembly contains only that page class, ASP.NET attempts to delete the assembly. Often, though, it finds the file loaded and locked, and the deletion fails. In this case, the old assembly is renamed by adding a .DELETE extension. (All executables loaded in Windows can be renamed at any time, but they cannot be deleted until they are released.) Renaming an assembly in use is no big deal in this case because the image of the executable is already loaded in memory and there will be no need to reload it later. The file, in fact, is destined for deletion. Notice that .DELETE files are cleaned up when the directory is next accessed in sweep mode, so to speak. The directory, in fact, is not scavenged each time it is accessed, but only when the application is restarted or an application file (global.asax or web.config) changes.

 

Each ASP.NET application is allowed a maximum number of page recompiles (with 15 as the default) before the whole application is restarted. The threshold value is set in the machine.config file. If the latest compilation exceeds the threshold, the AppDomain is unloaded and the application is restarted. Bear in mind that the atomic unit of code you can unload in the CLR is the AppDomain, not the assembly. Put another way, you can t unload a single assembly without unloading the whole AppDomain. As a result, when a page is recompiled, the old version stays in memory until the AppDomain is unloaded because either the Web application exceeded its limit of recompiles or the ASP.NET worker process is taking up too much memory.

 

A lot of temporary files are deeply involved with the processing of a single HTTP request. In this article, I ve guided you through the hidden files and folders where the .aspx source is parsed and compiled.

 

No sample code accompanies this article. The download message appears erroneously in the March issue of asp.netPRO.

 

Dino Esposito is a Wintellect trainer and consultant who specializes in ASP.NET and ADO.NET. Author of Programming Microsoft ASP.NET and Introducing ASP.NET 2.0, both from Microsoft Press, Dino also helped several companies architect and build effective products for ASP.NET developers. Dino is the cofounder of http://www.VB2TheMax.com, a popular portal for VB and .NET programmers. Write to him at mailto:[email protected] or join the blog at http://weblogs.asp.net/despos.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish