Under the Hood

Access Compile-time Code Programmatically

Dino Esposito

October 30, 2009

11 Min Read
ITPro Today logo in a gray background | ITPro Today

CoreCoder

LANGUAGES: C#

ASP.NETVERSIONS: 1.x

 

Under the Hood

Access Compile-time Code Programmatically

 

By Dino Esposito

 

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

 


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

 

When you get a compile error from ASP.NET, you re shown apage with summary information about the error, plus a couple of links. One ofthese links expands to show the detailed compiler output; the other linkdetails the complete compilation source. I would be a liar if I said that I vefound this information helpful in more than only a few situations. In manycases, this information doesn t help you determine the cause of the error; toascertain what happened, the stack trace is normally more instrumental. Yet, Iwas pleasantly surprised to find that developers were authorized to snoopinto the internals of ASP.NET. So I decided that I had to learn more about thefeature the ability to access compile-time code programmatically.

 

The Temporary Folder

As usual, what is a strong commitment on Sunday nightbecomes a faded memory on Monday morning when you get back to the reality ofwork. Swamped with a heap of less-enticing tasks, for months I failed toinvestigate further that astounding feature of ASP.NET until a clientsuggested that I replace the default ASP.NET identity (the ASPNET or NETWORKSERVICE account) with a personalized one. The client didn t really want togrant the ASP.NET worker process more privileges than strictly necessary; hejust wanted to use a custom account to feel free to modify it later withoutaffecting other applications.

 

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

 

 

You can change it to any existing account as long as theselected account is granted a minimum set of privileges. Aside from passwordsettings and rights assignments, such as Deny logon locally and Log on as aservice , the default ASP.NET account is given some NTFS permissions to operateon certain folders and create temporary files and assemblies. Guess whathappened? We changed the system account with another account that lacked allrequired NTFS permissions. As a result, ASP.NET stopped working.

 

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

 

Locate the Temporary Folder

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

 

%SystemRoot%Microsoft.NETFrameworkv1.1.4322

 Temporary ASP.NET Files

 

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

 

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

 

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

 

Framework

 v1.1.4322

   Temporary ASP.NETFiles

     MyWebApp

       3678b103

         e60405c7

 

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

 

string tempAspNetDir = HttpRuntime.CodegenDir;

 

What are the contents of this folder? The folder containsany dynamically generated assemblies for the page, including the assembly withthe run-time page class. The folder also stores the compiler s output. When thepage runs in debug mode, the folder also contains the source file of the classcreated parsing the source of the .aspx file. It is worth noticing that thesefiles 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 runningin debug mode. Figure 2 shows the typical contents of a temporary folder.

 


Figure 2: Typical contents of a TemporaryASP.NET Files folder.

 

Get the Dynamic Assembly

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

 

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

 

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

 

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

 

 hash="fffffeda266fd5f7">    Default.aspx" />

 

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 issimply the name of the assembly (without an extension) created to execute thedefault.aspx page. The file c5gaxkyh.dll is the assembly that represents thedefault.aspx page. The other assembly you see in Figure 2 is the compiledversion of the global.asax file. (If not specified, a standard global.asax fileis used.) The objects defined in these assemblies can be viewed with any classbrowser tool, including Microsoft IL Disassembler, ILDASM.exe.

 

As mentioned, if the Debug attribute of the @Pagedirective is set to true, in the same folder as the assembly you ll also findthe 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. Thename of the source file takes the form of assembly_name.0.ext, where assembly_name denotes the name (withoutextension) of the assembly and extdenotes the language extension. For example, c5gaxkyh.0.cs is the C# sourcefile for default.aspx.

 

It is interesting to note that the middle 0 is a constant in ASP.NET 1.x, but is avariable in ASP.NET 2.0. The middle index refers to the number of distinctfiles that contribute to the creation of the page class. This number isinvariably 1 (a 0-based index is used) in ASP.NET 1.x, but can be a largernumber in ASP.NET 2.0. For example, ASP.NET 2.0 pages based on a master pageare 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 niceto make some statements about the new compilation model and its underlyingimplementation. What is stated in this article applies to ASP.NET 1.x, but is alsolargely true for ASP.NET 2.0. The schema of the aforementioned XML file changesin ASP.NET 2.0, and new file formats are introduced, but the overall model offile parsing and class creation remains unchanged.

 

Detect Page Changes

As mentioned earlier, the dynamically compiled assembly iscached and used to serve any future request for the page. However, changes madeto an .aspx file will automatically invalidate the assembly, which will berecompiled to serve the next request. The link between the assembly and thesource .aspx file is kept in the XML file we mentioned a bit earlier. Let srecall it:

 

 hash="fffffeda266fd5f7">    Default.aspx" />

 

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

 

Based on the Win32 file notification change system, thisASP.NET feature enables developers to quickly build applications with a minimumof process overhead. Users, in fact, can just hit save to cause code changesto immediately take effect within the application. In addition to thisdevelopment-oriented benefit, deployment of applications is greatly enhanced bythis feature, as you can simply deploy a new version of the page thatoverwrites the old one.

 

When a page is changed, it s recompiled as a singleassembly (or as part of an existing assembly), and reloaded. ASP.NET ensuresthat 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 bythe old assembly. The two assemblies are given different names (because theyare randomly generated) and therefore can happily live side by side in the samefolder, as well as be loaded in the same AppDomain.

 

Refresh Assemblies

When a new assembly is created for a page as the effect ofan update, ASP.NET verifies whether the old assembly can be deleted. If theassembly contains only that page class, ASP.NET attempts to delete theassembly. Often, though, it finds the file loaded and locked, and the deletionfails. 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 cannotbe deleted until they are released.) Renaming an assembly in use is no big dealin this case because the image of the executable is already loaded in memoryand there will be no need to reload it later. The file, in fact, is destinedfor deletion. Notice that .DELETE files are cleaned up when the directory isnext accessed in sweep mode, so to speak. The directory, in fact, is not scavengedeach time it is accessed, but only when the application is restarted or anapplication file (global.asax or web.config) changes.

 

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

 

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

 

Nosample code accompanies this article. The download message appears erroneouslyin the March issue of asp.netPRO.

 

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

 

 

 

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like