Daily .Net Feeds - ASP.Net 2.0 - Advanced - Day 8

Hi Everyone,

Welcome back!!!

The feed is delayed a lot today but today will be our last session on dynamic page compilation. To revisit all concepts we learnt and understand how the dynamic compilation process works end to end, we will consider an example. Since all that we discuss today is in the context of this example the feed for today will be a long one but unfortunately I can't imagine splitting :-).

So, let say, we have a page called Test.aspx in a folder named Compilation. The page contains a text box to grab some user-typed text and a button to trigger a server-side operation that reverses the text. The results are shown in a label control, while another label control displays the current time through a data-bound expression. These features represent two of the most common operations we find on web form pages - postbacks and declarative data binding. The markup and the code for the page follow:

<%@ Page Language="C#" CodeFile="Test.aspx.cs" Inherits="Test" %>

<html>

<head id="Head1" runat="server">

<title>Sample page</title>

</head>

<body>

<form id="form1" runat="server">

<div>

<h1>Enter some text and click.</h1>

<h2><asp:Label ID="Today" runat="server"

Text='<%# DateTime.Now %>' /></h2>

<asp:TextBox ID="TextBox1" runat="server" Text="Type here" />

<asp:Button ID="Button1" runat="server" Text="Reverse"

OnClick="ReverseText" />

<hr />

<asp:Label ID="Label1" runat="server" />

</div>

</form>

</body>

</html>

using System;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Text;

public partial class Test : System.Web.UI.Page

{

protected void Page_Load(object sender, EventArgs e)

{

DataBind();

}

protected void ReverseText(object sender, EventArgs e)

{

string reverseText = Reverse(TextBox1.Text);

Label1.Text = reverseText;

}

private string Reverse(string text)

{

if (text.Length == 1)

return text;

else

{

char[] rg = text.ToCharArray();

Array.Reverse(rg);

return new string(rg);

}

}

}

Let's examine in more detail how the test.aspx page is converted into a class and compiled into an assembly. The source code of the page class to render test.aspx is created in the temporary folder and deleted immediately after compiling. It will persist if we have debug=true attribute in the @Page directive or in web.config file. Execute the page, and once the page shows up in the browser, open the "Compilation" folder in the browser under the ASP.NET temporary folder. Below, the first figure shows the path to the folder where temporary files for the page are created and the second figure lists the temporary files created to serve test.aspx. Note that some of these files are deleted after use on a production machine that serves non debug pages.

Following table provides more details about each of these files. Note that xgpc4gcp is a randomly generated prefix. You will get a different name each time you recompile the page.

File name

Description

hash.web

Contains the hash value for the folder, which will be used to calculate hash values for individual resources.

App_Web_xxx.dll

The dynamic assembly created for the pages in the folder named test.aspx. The xxx placeholder indicates the hash value that makes the test.aspx page unique even if multiple pages with the same name exist in different subdirectories.

Test.aspx.xxx.compiled

XML file that contains information about the dependencies that test.aspx has with external files. It also links test.aspx to the randomly generated assembly name.

xgpc4cgb.x.cs

Source code for the C# class code created after parsing test.aspx and all its dependencies. These files will be Visual Basic .NET class files if Visual Basic .NET is the language of the page. The x placeholder indicates a 0-based index used to distinguish relevant constituent files. This file is deleted unless the debug attribute is turned on in the page.

xgpc4cgb.cmdline

Text file that contains the command line used to compile the preceding class file. This file is deleted unless the debug attribute is turned on in the page.

xgpc4cgp.err

Text file that contains any output the compiler sends to the standard error stream. This file is deleted unless the debug attribute is turned on in the page.

xgpc4cgb.out

Text file that contains any output text generated by the compiler. This file is deleted unless the debug attribute is turned on in the page.

Let's take a look at the source code of the xgpc4cgb.x.cs files. For test.aspx, we have three distinct constituent source files. The x placeholder, therefore, varies from 0 through 2, in this case, as discussed in following table:

File

Class

Contents

xgpc4cgb.0.cs

(partial) Test, ASP.test_aspx

It contains two classes. The first is the partial class Test that completes the code file written by the author to back up test.aspx. The second is the page handler class used to serve the test.aspx resource.

xgpc4cgb.1.cs

(partial) Test

The code file class created by the page author as test.aspx.cs.

xgpc4cgb.2.cs

ASP.FastObjectFactory

Internal-use factory class created to avoid reflection and maximize speed when instantiating the page handler class.

The contents in the partial class defined as xxx.1.cs is the code of the code file class associated with the ASP.NET page. This partial class—named Test as in the Inherits attribute of the @Page directive—is completed with a dynamically generated partial class that adds members for each server control. Here's what the second partial class looks like:

public partial class Test : IRequiresSessionState

{

protected Label Today;

protected TextBox TextBox1;

protected Button Button1;

protected Label Label1;

protected HtmlForm form1;

protected DefaultProfile Profile

{

get { return (DefaultProfile)Context.Profile; }

}

protected HttpApplication ApplicationInstance

{

get { return (HttpApplication)Context.ApplicationInstance; }

}

}

The FastObjectFactory class is also worth a quick mention. It merely contains a static method that instantiates the page handler class in a strong-typed, early-bound manner. Because the compiled page type is known at run time, .NET reflection would be the only option left to instantiate a class. This trick allows you to gain a little bit of performance on a very frequently executed operation. Here's the typical source code:

namespace ASP

{

internal class FastObjectFactory

{

private FastObjectFactory() { }

static object Create_ASP_Test_aspx()

{

return new ASP.Test_aspx();

}

}

}

Warning: All above code is shown for educational purposes only and in a slightly edited format that makes it read well. If you plan to use this information for building real-world applications, make sure that you verify and cross-check any line of code to ensure that it works in your context, too. Note also that this is ASP.NET internal code; as such, it might change in future builds without being noted.

Structure of the Page:

The page class created to service requests for test.aspx inherits from the class specified through the Inherits attribute in the markup file - Test in this case. As mentioned, the Test class is a partial class defined in test.aspx.cs and derives, directly or indirectly, from Page. A second, ASP.NET-generated partial class will complete the definition of Test. The page handler for test.aspx has a fixed name – ASP.Test_aspx. Let's explore its internals as well:

namespace ASP

{

public class Test_aspx : Test

{

private static bool __initialized;

private static object __fileDependencies;

public Test_aspx()

{

string[] dependencies;

AppRelativeVirtualPath = "~/Test.aspx";

if (__initialized == false)

{

dependencies = new string[2];

dependencies[0] = "~/Test.aspx";

dependencies[1] = "~/Test.aspx.cs";

__fileDependencies = GetWrappedFileDependencies(dependencies);

__initialized = true;

}

Server.ScriptTimeout = 30000000;

}

protected override void FrameworkInitialize()

{

base.FrameworkInitialize();

__BuildControlTree(this);

AddWrappedFileDependencies(__fileDependencies);

Request.ValidateInput();

}

public override int GetTypeHashCode()

{

// This static number is generated dynamically when the source

// code of this page is generated dynamically

return 850224717;

}

// ...

}

}

The class has two static members to indicate the initialization state of the class and the list of its file dependencies. These two members are initialized in the class constructor. Next, the ASP.Test_aspx overrides two methods on the base Page class, as detailed in following table:

Method

Description

FrameworkInitialize

Governs the creation of the page's control tree.

GetTypeHashCode

Returns the hash code for the page that uniquely identifies the page's control hierarchy. The method should not be confused with GetHashCode that all .NET Framework classes inherit from System.Object, although both methods pursue similar goals—returning values to identify the contents of objects. GetTypeHashCode is more specific than GetHashCode and bases its value on the page's control tree. The number is generated on the fly when the source code of the page is generated.

There are two key things going on in the override of FrameworkInitialize. First, the __BuildControlTree method is invoked to fill the Controls collection of the page class, thus determining the control tree of the page. The __BuildControlTree method is autogenerated by ASP.NET. We'll take a look at it in a moment. Second, ValidateInput is invoked on the Request object to ensure that no potentially dangerous input is being processed by the page. ValidateInput is controlled by the ValidateRequest attribute in the @Page directive and simply applies some standard regular expressions to all inbound data. You should never rely on ValidateInput alone to secure your application.

The Control Tree:

The __BuildControlTree is the entry point in the code that physically builds a class from the ASPX markup. It uses an IParserAccessor reference to the current page object to find all sub objects in the specified graph and add them to the Controls collection of the page being processed.

private void __BuildControlTree(Test_aspx __ctrl)

{

// Get the parser interface from the page

IParserAccessor __parser = (IParserAccessor) __ctrl);

// Build the <head> node and add it to the tree

HtmlHead __ctrl1 = __BuildControl__control2();

__parser.AddParsedSubObject(__ctrl1);

// Build a subtree for a literal expression

__parser.AddParsedSubObject(new LiteralControl("<body>"));

// Build the <form> node and add it to tree

HtmlForm __ctrl2 = __BuildControlform1();

__parser.AddParsedSubObject(__ctrl2);

// Build a subtree for a literal expression

__parser.AddParsedSubObject(new LiteralControl("</body></html>"));

}

The contents of the <head> tag are processed to obtain a subtree of controls, and the same process occurs with the <form> tag and all of its contents. Any literal expression, such as <body>, is processed as a control as well. Any consecutive text found between two server controls is converted into a literal control and added to the tree.

Note: In light of this behavior, you should avoid using carriage return/linefeed pairs between server controls. In fact, the sequence /r/n is added to the generated tree as a literal control. The sequence has no impact on the HTML rendering—it simply makes the source more easily readable—but it charges a slight performance penalty at run time.

Each server control originates a procedure like the following one, which builds the TextBox1 text box control:

private TextBox __BuildControlTextBox1()

{

TextBox __ctrl = new TextBox();

this.TextBox1 = __ctrl;

__ctrl.ApplyStyleSheetSkin(this);

__ctrl.ID = "TextBox1";

__ctrl.Text = "Type here";

return __ctrl;

}

The preceding code is the result of the following markup:

<asp:TextBox ID="TextBox2" runat="server" Text="Type here" />

The build method for the <form> tag calls build methods for all contained controls. Build methods have fixed names: __BuildControlname, where name stands for the ID of the control. It will be controlN for unnamed controls.

Event Handlers and Data-Binding Expressions:

What if the control has an event handler or a data-binding expression? Let's first consider the case of a button with the Click event handler. The code is nearly identical to the preceding code snippet, except of course that a Button class is used instead of a TextBox. In addition, you'll find the following:

__ctrl.Click += new EventHandler(this.ReverseText);

For data-binding expressions <%# ... %>, the code generated is similar except that the DataBinding event is used.

__ctrl.DataBinding += new EventHandler(__DataBindingToday);

The code associated with the handler depends on the nature of the bound control and the code being bound. In this relatively simple case, it looks like the following fragment:

public void __DataBindingToday(object sender, EventArgs e)

{

Label target = (Label)sender;

target.Text = Convert.ToString(DateTime.Now,

CultureInfo.CurrentCulture);

}

Note: The $-expressions introduced in ASP.NET 2.0 are processed in a different way, as they are not bound to the data-binding process. We'll delve deep into $-expressions in some oncoming session.

That's it for today. Thanks for joining!!! So we wrapped up with dynamic page compilation today and we will look at pre-compilation tomorrow. See you tomorrow.

Thanks,

Sukesh Khare

Coordinator Daily