ASPNET Web Forms Internals
ASPNET Web Forms Internals
ASP.NET pages are dynamically compiled on demand when first required in the
context of a Web application. Dynamic compilation is not specific to ASP.NET
pages (.aspx files); it also occurs with Web Services (.asmx files), Web user con-
trols (.ascx files), HTTP handlers (.ashx files), and ASP.NET application files
such as the global.asax file. But what does it mean exactly that an ASP.NET
page is compiled? How does the ASP.NET runtime turn the source code of an
.aspx file into a .NET Framework compilable class? And what becomes of the
dynamically created assembly when the associated source file gets updated?
And finally, what happens once a compiled assembly has been associated with
the requested .aspx URL?
Don’t be too surprised to find all these questions crowding your mind at
this time. Their presence indicates you’re on the right track and ready to learn
more about the underpinnings of the Web Forms programming model.
In this chapter, we simply aim to unveil all the mysteries fluttering around
the dynamic compilation of ASP.NET pages. We’ll do this by considering the
actions performed on the Web server, and which modules perform them, when
a request arrives for an .aspx page.
server-side elaboration are passed on to the registered module. For example, ASP
pages are processed by an ISAPI extension named asp.dll. In general, when the
resource is associated with executable code, IIS hands the request to that execut-
able for further processing. Files with an .aspx extension are assigned to an ISAPI
extension named aspnet_isapi.dll, as shown in Figure 2-1.
F02xx01.bmp
Figure 2-1 The IIS application mappings for resources with an .aspx
extension.
Just like any other ISAPI extension, aspnet_isapi.dll is hosted by the IIS 5.0
process—the executable named inetinfo.exe. Resource mappings are stored in
the IIS metabase. Upon installation, ASP.NET modifies the IIS metabase to make
sure that aspnet_isapi.dll can handle all the resources identified by the exten-
sions listed in Table 2-1.
IIS AppDomain
ASP.NET
Browser aspnet_isapi.dll Worker HttpRuntime Application
HTTP named process
pipe
HTTP pipeline
inetinfo.exe aspnet_wp.exe
F02xx02.bmp
Figure 2-2 IIS receives page requests and forwards them to the
ASP.NET runtime.
Browser
HTTP
aspnet_isapi.dll
IIS
inetinfo.exe
named pipe
...
...
Application Application
CPU
HTML HTML
F02xx03.bmp
Figure 2-3 The ASP.NET runtime and the various AppDomains.
Process Recycling
The behavior and performance of the ASP.NET worker process is constantly
monitored to catch any form of decay as soon as possible. Parameters used to
evaluate the performance include the number of requests served and queued,
the total life of the process, and the percentage of physical memory (60% by
default) it can use.
The <processModel> element of the machine.config file defines threshold
values for all these parameters. The aspnet_isapi.dll checks the overall state of
the current worker process before forwarding any request to it. If the process
breaks one of these measures of good performance, a new worker process is
started to serve the next request. The old process continues running as long as
C02619034.fm Page 66 Friday, May 9, 2003 11:31 AM
there are requests pending in its own queue. After that, when it ceases to be
invoked, it goes into idle mode and is then shut down.
This automatic scavenging mechanism is known as process recycling and
is one of the aspects that improve the overall robustness and efficiency of the
ASP.NET platform. In this way, in fact, memory leaks and run-time anomalies
are promptly detected and overcome.
Note In IIS 6.0, you’ll find many design and implementation features
of ASP.NET that are enhanced and extended to all resources. Histori-
cally, Microsoft was originally developing IIS 6.0 and ASP.NET
together. Microsoft split them into separate projects when a decision
was made to ship an initial version of ASP.NET prior to shipping IIS
with a new version of Windows. ASP.NET clearly needed to support
older versions of IIS, so a parallel IIS 5.0 model for ASP.NET was also
built. In that sense, the ASP.NET model for IIS 5.0 matured much more
quickly and inspired a lot of features in the newest IIS 6.0 model. As a
significantly different product, IIS 6.0 takes the essence of the
ASP.NET innovations and re-architects them in a wider and more gen-
eral context. As a result, specific features of ASP.NET (for example,
output caching and process recycling) become features of the whole
Web server infrastructure with IIS 6.0. Those features are available to
all Web applications hosted by IIS 6.0, including ASP.NET applica-
tions. ASP.NET is designed to detect the version of IIS and adjust its
way of working.
C02619034.fm Page 67 Friday, May 9, 2003 11:31 AM
Parameter Description
IIS-Process-ID The process ID number of the parent IIS process.
This-Process-Unique-ID A unique process ID used to identify the worker pro-
cess in a Web garden configuration.
Number-of-Sync-Pipes Number of pipes to listen to for information.
RPC_C_AUTHN_LEVEL_XXX Indicates the required level of authentication for DCOM
security. Default is Connect.
RPC_C_IMP_LEVEL_XXX Indicates the authentication level required for COM
security. Default is Impersonate.
CPU-Mask Bit mask indicating which CPUs are available for
ASP.NET processes if the run time is configured to
work as a Web garden.
Max-Worker-Threads Maximum number of worker threads per CPU in the
thread pool.
Max-IO-Threads Maximum number of IO threads per CPU in the thread
pool.
Default values for the arguments in Table 2-2 can be set by editing the
attributes of the <processModel> section in the machine.config file. (I’ll cover
the machine.config file in more detail in Chapter 12.)
These parameters instruct the process how to perform tasks that need to
happen before the CLR is loaded. Setting COM security is just one such task,
and that’s why authentication-level values need to be passed to the ASP.NET
worker process. What does ASP.NET have to do with COM security? Well, the
CLR is actually exposed as a COM object. (Note that the CLR itself is not made
of COM code, but the interface to the CLR is a COM object.)
Other parameters are the information needed to hook up the named pipes
between the ISAPI extension and the worker process. The names for the pipes
are generated randomly and have to be communicated. The worker process
retrieves the names of the pipes by using the parent process ID (that is, the IIS
process) and the number of pipes created.
C02619034.fm Page 68 Friday, May 9, 2003 11:31 AM
(continued)
C02619034.fm Page 70 Friday, May 9, 2003 11:31 AM
Browser
HTTP
Kernel-mode
Application pool Application pool
...
request queue request queue
WAS initializes
http.sys
WAS reads
Workers
metabase
get requests Manages the
from the lifetime and the IIS 6.0 metabase
applications recycling of
queue worker processes
F02xx04.bmp
Figure 2-4 How Web applications are processed in IIS 6.0.
In summary, in the IIS 6.0 process model, ASP.NET runs even faster
because no interprocess communication between inetinfo.exe (the IIS
executable) and aspnet_wp.exe is required. The HTTP request arrives
directly at the worker process that hosts the CLR. Furthermore, the
ASP.NET worker process is not a special process but simply a copy of the
IIS worker process. This fact shifts to IIS the burden of process recycling
and health checks.
C02619034.fm Page 71 Friday, May 9, 2003 11:31 AM
the URL. The object used to find or create a new Web application is HttpAppli-
cationFactory—an internal-use object responsible for returning a valid object
capable of handling the request.
HTTP modules represent the managed counterpart of ISAPI filters and will be
covered in greater detail in Chapter 23.
The HttpApplication object determines the type of object that represents the
resource being requested—typically, an ASP.NET page. It then uses a handler fac-
tory object to either instantiate the type from an existing assembly or dynamically
create the assembly and then an instance of the type. A handler factory object is
a class that implements the IHttpHandlerFactory interface and is responsible for
returning an instance of a managed class that can handle the HTTP request—an
HTTP handler. An ASP.NET page is simply a handler object—that is, an instance
of a class that implements the IHttpHandler interface.
The page handler factory is responsible for either finding the assembly
that contains the page class or dynamically creating an ad hoc assembly. The
System.Web namespace defines a few handler factory classes. These are listed in
Table 2-3.
Bear in mind that handler factory objects do not compile the requested
resource each time it is invoked. The compiled code is stored in a directory on
the Web server and used until the corresponding resource file is modified.
So the page handler factory creates an instance of an object that represents
the particular page requested. This object inherits from the System.Web.UI.Page
class, which in turn implements the IHttpHandler interface. The page object is
built as an instance of a dynamically generated class based on the source code
embedded in the .aspx file. The page object is returned to the application fac-
tory, which passes that back to the HttpRuntime object. The final step accom-
plished by the ASP.NET runtime is calling the ProcessRequest method on the
page object. This call causes the page to execute the user-defined code and
generate the HTML text for the browser.
Figure 2-5 illustrates the overall HTTP pipeline architecture.
C02619034.fm Page 75 Friday, May 9, 2003 11:31 AM
URL is default.aspx
Request
HttpContext Page object
Response
..
HttpApplicationFactory
Server
Select the application object
to serve the request
HttpModule HttpApplication
from a (dynamically
created) assembly
HttpModule
PageHandlerFactory
Page-specific object
ASP.default_aspx
F02xx05.bmp
Figure 2-5 The HTTP pipeline processing for a page.
has one child directory for each application ever 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 sub-
folder.
Page-specific assemblies are cached in a subdirectory placed a couple lev-
els down the virtual directory folder. The names of these child directories are
fairly hard to make sense of. 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 (in boldface) have fake
but realistic names.
\Framework
\v1.1.4322
\Temporary ASP.NET Files
\MyWebApp
\3678b103
\e60405c7
So much for the location of the dynamic assembly. So how does the
ASP.NET runtime determine the assembly name for a particular .aspx page? The
assembly 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
The XML file is created when the page is compiled. This is the typical con-
tent of this XML file:
<preserve assem="c5gaxkyh” type="ASP.Default_aspx”
hash="fffffeda266fd5f7">
<filedep name="C:\Inetpub\wwwroot\MyWebApp\Default.aspx” />
</preserve>
I’ll say more about the schema of the file in a moment. For now, it will suf-
fice to look at the assem attribute. The attribute value is just the name of the
assembly (without extension) created to execute the default.aspx page. Figure
2-6 provides a view of the folder.
C02619034.fm Page 77 Friday, May 9, 2003 11:31 AM
F02xx06.bmp
Figure 2-6 Temporary ASP.NET Files: a view of interiors.
The name attribute of the <filedep> node contains just the full path of the
file associated with the assembly whose name is stored in the assem attribute of
the <preserve> node. The type attribute, on the other hand, 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.
C02619034.fm Page 78 Friday, May 9, 2003 11:31 AM
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 immedi-
ately take effect within the application. In addition to this development-ori-
ented 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 (because they are randomly generated) names
and therefore can happily live side by side in the same folder as well as be
loaded in the same AppDomain. Because that was so much fun, let’s drill down
a little more into this topic.
F02xx07.bmp
Figure 2-7 Runtimeinfo.aspx shows ASP.NET runtime information.
C02619034.fm Page 80 Friday, May 9, 2003 11:31 AM
Batch Compilation
Compiling an ASP.NET page takes a while. So even though you pay this price
only once, you might encounter situations in which you decide it’s best to hap-
pily avoid that. Unfortunately, as of version 1.1, ASP.NET lacks a tool (or a built-
in mechanism) to scan the entire tree of a Web application and do a precompi-
lation of all pages. However, you can always request each page of a site before
the site goes live or, better yet, have an ad hoc application do it.
In effect, since version 1.0, ASP.NET has supported batch compilation, but
this support takes place only at run time. ASP.NET attempts to batch into a sin-
gle compiled assembly as many pages as possible without exceeding the con-
figured maximum batch size. Furthermore, batch compilation groups pages by
language, and it doesn’t group in the same assembly pages that reside in differ-
ent directories.
Just as with many other aspects of ASP.NET, batch compilation is highly
configurable and is a critical factor for overall site performance. Fine-tuning the
related parameters in the <compilation> section of the machine.config file is
important and should save you from having and loading 1000 different assem-
blies for as many pages or from having a single huge assembly with 1000
classes inside. Notice, though, that the problem here is not only with the size
and the number of the assemblies but also with the time needed to recompile
the assemblies in case of updates.
As mentioned earlier, when the page runs with the Debug attribute set to
true, the ASP.NET runtime does not delete the source code used to create the
assembly. Let’s have a quick look at the key parts of the source code generated.
(Complete sources are included in this book’s sample code.)
<script runat="server">
private void Page_Load(object sender, EventArgs e) {
TheSourceFile.Text = HttpRuntime.CodegenDir;
}
private void MakeUpper(object sender, EventArgs e) {
string buf = TheString.Value;
TheResult.InnerText = buf.ToUpper();
}
</script>
<html>
<head><title>Pro ASP.NET (Ch 02)</title></head>
<body>
<h1>Sample Page</h1>
<form runat="server">
<asp:Label runat="server” id="TheSourceFile” /><hr>
<input runat="server” type="text” id="TheString” />
<input runat="server” type="submit” id="TheButton”
value="Uppercase...” onserverclick="MakeUpper” /><br>
<span id="TheResult” runat="server"></span>
</form>
</body>
</html>
C02619034.fm Page 82 Friday, May 9, 2003 11:31 AM
The following listing shows the source code that ASP.NET generates to
process the preceding page. The text in boldface type indicates code extracted
from the .aspx file:
namespace ASP
{
using System;
§
using ASP;
using System.IO;
public Default_aspx()
{
ArrayList dependencies;
if (__initialized == false)
{
dependencies = new ArrayList();
dependencies.Add(
“c:\\inetpub\\wwwroot\\vdir\\Default.aspx”);
__fileDependencies = dependencies;
__initialized = true;
}
this.Server.ScriptTimeout = 30000000;
}
Important As you can see, portions of the source code in the .aspx
file are used to generate a new class in the specified language. Just
because the inline code in a page will be glued together in a class
doesn’t mean you can use multiple languages to develop an ASP.NET
page. The .NET platform is language-neutral but, unfortunately, .NET
compilers are not capable of cross-language compilation!
In addition, for ASP.NET pages, the language declared in the
@Page directive must match the language of the inline code. The Lan-
guage attribute, in fact, is used to determine the language in which the
class is to be created. Finally, the source code is generated using the
classes of the language’s Code Document Object Model (CodeDOM).
CodeDOM can be used to create and retrieve instances of code gen-
erators and code compilers. Code generators can be used to generate
code in a particular language, and code compilers can be used to
compile code into assemblies. Not all .NET languages provide such
classes, and this is why not all languages can be used to develop
ASP.NET applications. For example, the CodeDOM for J# has been
added only in version 1.1 of the .NET Framework, but there is a J#
redistributable that adds this functionality to version 1.0.
All the controls in the page marked with the runat attribute are rendered
as protected properties of the type that corresponds to the tag. Those controls
are instantiated and initialized in the various __BuildControlXXX methods. The
initialization is done using the attributes specified in the .aspx page. The build
method for the form adds child-parsed objects to the HtmlForm instance. This
means that all the parent-child relationships between the controls within the
form are registered. The __BuildControlTree method ensures that all controls in
the whole page are correctly registered with the page object.
C02619034.fm Page 85 Friday, May 9, 2003 11:31 AM
All the members defined in the <script> block are copied verbatim as
members of the new class with the same level of visibility you declared. The
base class for the dynamically generated source is Page unless the code-behind
approach is used. In that case, the base class is just the code-behind class. We’ll
return to this later in “The Code-Behind Technique” section.
By proceeding this way, you will enable the page to get a slight perfor-
mance boost by not having to do the extra work of matching names and events.
Visual Studio .NET disables the AutoEventWireup attribute.
Page-Related Events
To handle a page-related event, an ASP.NET page can either hook up the event
(for example, Load) or, in a derived class, override the corresponding
method—for example, OnLoad. The second approach provides for greater flex-
ibility because you can decide whether and when to call the base method,
which, in the end, fires the event to the user-code. Let’s review in detail the var-
ious phases in the page life cycle:
Both the form and the controls must be marked with the runat attribute; other-
wise, they will be considered as plain text to be output verbatim. A server-side
form is an instance of the HtmlForm class. The HtmlForm class does not expose
any property equivalent to the Action property of the HTML <form> tag. The rea-
son is that an ASP.NET page always posts to itself. Unlike the Action property,
other common form properties such as Method and Target are fully supported.
Note Valid ASP.NET pages are also those that have no server-side
forms and those that run HTML forms—a <form> tag without the runat
attribute. In an ASP.NET page, you can also have both HTML and
server forms. In no case, though, can you have more than one <form>
tag with the runat attribute set to server. HTML forms work as usual
and let you post to any page in the application. The drawback is that in
this case, no state will be automatically restored. In other words, the
ASP.NET Web Forms model works only if you use exactly one server
<form> element.
<html>
<body>
<form runat="server” id="TheForm">
<asp:textbox runat="server” id="TheString” />
<asp:button runat="server” text="Convert...” onclick="MakeUpper” /><hr>
<asp:label runat="server” id="TheResult” />
</form>
</body>
</html>
As shown in Figure 2-8, the page lets the user type some text in an input
field and then posts all the data to the server for further processing. If you point
your browser to this page, the actual HTML code being displayed is the follow-
ing. The text in boldface is the page’s view state:
C02619034.fm Page 90 Friday, May 9, 2003 11:31 AM
<html>
<body>
<form name="TheForm” method="post” action="MyForm.aspx” id="TheForm">
<input type="hidden”
name="__VIEWSTATE”
value="dDwtMTM3NjQ2NjY2NTs7PrH3U/xuqPTNI63IlLw5THHvPFUf” />
<input name="TheString” type="text” id="TheString” />
<input type="submit” name="_ctl0” value="Convert...” /><hr>
<span id="TheResult"></span>
</form>
</body>
</html>
F02xx08.bmp
Figure 2-8 The sample ASP.NET page in action.
The server-side <form> tag is rendered as a plain old HTML form in which
the Name attribute matches the ID property of the HtmlForm control, the
Method attribute defaults to POST and Action is automatically set with the URL
of the same page. The form contains as many HTML elements as there are
server controls in the .aspx source. The form also includes any static text or
HTML tags declared in the page layout. The view state is rendered as a hidden
field named __VIEWSTATE and filled with Base64-encoded text.
Postback Events
A postback event is a page request that results from a client action. Typically,
when the user clicks on a submit button, like the Convert button in Figure 2-8,
a postback event occurs. In this case, the HTML form posts to the same .aspx
page and, as normally happens with HTTP POST commands, the contents of
the various input fields in the form are packed in the body of the request.
On the server, the code in charge of processing the request first gets an
instance of the class that represents the .aspx page and then goes through the
steps described earlier in the “Page-Related Events” section.
State Restoration
As the first step of the page processing, the HTTP runtime builds the tree of
controls for the page and sets the IsPostBack property. IsPostBack is a Boolean
property that equals true if the page is being processed after a post from the
same page—a postback event.
After initialization, the HTTP runtime instructs the page to deserialize the
view-state string and transform it into an instance of the StateBag class. State-
Bag implements a dictionary that is populated with the name/value pairs stored
and encoded in the view-state string. Once the StateBag object has been com-
pletely set up, user code can start playing with its contents. From a user-code
perspective, however, this timing means it can’t do anything before the OnLoad
event fires. Before OnLoad is fired, though, another step needs to be accom-
plished.
At this point, the various controls active on the page have been restored to
the state stored in the view state. Unless the view state has been sapiently and
maliciously corrupted on the client (an extremely remote possibility, if not com-
pletely impossible), the state of the objects is the state that existed when that
page was rendered to the client. It is not yet the state the user set with client-
side input actions such as checking radio buttons or typing text. The next step
updates the state of the various controls to reflect any client action. This final
step ends the state restoration phase, and the page is now ready to process user
code. Fire the OnLoad event.
The HTML syntax dictates that the ID of the button clicked be inserted in
the post data. On the server, the ASP.NET page digs out this information in its
attempt to locate an element whose name matches one of its server controls. If
a match is found, ASP.NET verifies that the control implements IPostBack-
EventHandler. This is enough to guarantee that the button clicked on the server
was a submit button. Generally, it means the control has some code to execute
upon postback. At this point, the page raises the postback event on the control
and has the control execute some code. To raise the postback event on the con-
trol, the ASP.NET page invokes the control’s RaisePostBackEvent method—one
of the members of the IPostBackEventHandler interface. For a submit button,
this causes the invocation of the method associated with the onclick property.
If some script code can post the page back to the server, the author of the
script is responsible for letting ASP.NET know about the sender of the event. To
understand how to accomplish this, let’s see what happens when you use a
Web control such as the LinkButton—a button rendered as a hyperlink:
<asp:linkbutton runat="server” id="TheLink"
text="Convert...” onclick="MakeUpper” />
The HTML code for the page changes slightly. A JavaScript function and a
couple of extra hidden fields are added. The two hidden fields are
__EVENTTARGET and __EVENTARGUMENT. They are empty when the page is
rendered to the browser and contain event data when the page is posted back.
In particular, __EVENTTARGET contains the name of the control that caused the
postback. The __EVENTARGUMENT field contains any argument that might be
useful to carry out the call. A short piece of JavaScript code sets the two fields
and submits the form:
<!--
<script language="javascript">
function __doPostBack(eventTarget, eventArgument) {
var theform;
if (window.navigator.appName.toLowerCase().indexOf(“netscape”) > -1) {
theform = document.forms["TheForm"];
}
else {
theform = document.TheForm;
}
theform.__EVENTTARGET.value = eventTarget.split(“$”).join(“:”);
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
But what’s the link between the __doPostBack function and the LinkBut-
ton object? Let’s look at the HTML generated for the LinkButton:
<a href="javascript:__doPostBack(‘TheLink’,’’)">Convert...</a>
C02619034.fm Page 94 Friday, May 9, 2003 11:31 AM
Object
IComponent
Control
IDisposable
IParserAccessor
TemplateControl
INamingContainer
Page
IHttpHandler
F02xx09.bmp
Figure 2-9 The hierarchy of classes from which Page inherits.
From the parent Control class, Page inherits the base behavior for interact-
ing with all other components in the CLR—the IComponent interface—and for
releasing unmanaged resources (the IDisposable interface). In addition, the
IParserAccessor interface groups the methods that ASP.NET controls must
implement to recognize parsed child elements. The IParserAccessor interface
plays a key role in the implementation of the control tree in the dynamically
generated source code for requested ASP.NET pages. (Actually, the IParserAc-
cessor interface consists of the single AddParsedSubObject method we saw
largely used in the code shown in “Reviewing the Class Source Code” section.)
namespace in which all child controls are guaranteed to have unique names in
the overall tree of controls. (This is also a very important feature for iterative
data-bound controls, such as DataGrid, and for user controls.)
The Page class also implements the methods of the IHttpHandler inter-
face, thus qualifying as the handler of a particular type of HTTP requests—
those for .aspx files. The key element of the IHttpHandler interface is the Pro-
cessRequest method, which is the method the ASP.NET runtime calls to start the
page processing that will actually serve the request.
Property Description
Server Instance of the HttpServerUtility class, provides helper
methods for processing Web requests. Functionally
equivalent to the ASP intrinsic Server object.
Session Instance of the HttpSessionState class, manages user-spe-
cific data. Functionally equivalent to the ASP intrinsic
Session object.
Trace Instance of the TraceContext class, performs tracing on
the page.
User An IPrincipal object that represents the user making the
request.
Property Description
Controls Returns the collection of all the child controls contained
in the current page
ErrorPage Gets or sets the error page to which the requesting
browser is redirected in case of an unhandled page
exception
IsPostBack Indicates whether the page is being loaded in response
to a client postback or whether it is being loaded for the
first time
IsValid Indicates whether page validation succeeded
NamingContainer Returns null
Page Returns the current Page object
Parent Returns null
TemplateSourceDirectory Gets the virtual directory of the page
Validators Returns the collection of all validation controls contained
in the page
ViewStateUserKey String property used to assign an identifier to the view
state variable for individual users. This trick is a line of
defense against one-click attacks. The property is not
available with ASP.NET 1.0.
C02619034.fm Page 98 Friday, May 9, 2003 11:31 AM
In the context of an ASP.NET application, the Page object is the root of the
hierarchy. For this reason, inherited properties such as NamingContainer and
Parent always return null. The Page property, on the other hand, returns an
instance of the same object (this in C# and Me in Visual Basic .NET).
A special note deserves the ViewStateUserKey property that has been
added with version 1.1 of the .NET Framework. A common use for the user key
would be to stuff user-specific information that will then be used to hash the
contents of the view state along with other information. (See Chapter 14.) A typ-
ical value for the ViewStateUserKey property is the name of the authenticated
user or the user’s session ID. This contrivance reinforces the security level for
the view-state information and further lowers the likelihood of attacks. If you
employ a user-specific key, an attacker couldn’t construct a valid view state for
your user account unless he could also authenticate as you. That way, you have
another barrier against on-click attacks. This technique, though, might not be
really effective for Web sites that allow anonymous access unless you have
some other unique tracking device running.
Note that if you plan to set the ViewStateUserKey property, you must do
that during the Page_Init event. If you attempt to do it later (for example, when
Page_Load fires), an exception will be thrown.
The three ID properties (ID, ClientID, and UniqueID) always return the
empty string from a Page object. They make sense only for server controls.
Method Description
DataBind Binds all the data-bound controls contained in the
page to their data sources. The DataBind method
doesn’t generate code itself but prepares the
ground for the forthcoming rendering.
RegisterRequiresPostBack Registers the specified control with the page so
that the control will receive a post-back handling
notice. In other words, the page will call the Load-
PostData method of registered controls. LoadPost-
Data requires the implementation of the
IPostBackDataHandler interface.
RegisterRequiresRaiseEvent Registers the specified control to handle an
incoming postback event. The control must imple-
ment the IPostBackEventHandler interface.
RenderControl Outputs the HTML text for the page, including
tracing information if tracing is enabled.
VerifyRenderingInServerForm Controls call this method when they render to
ensure they are included in the body of a server
form. The method does not return a value, but it
throws an exception in case of error.
Both methods differ from ParseControl in that the latter never causes com-
pilation but simply parses the string and infers control information. The infor-
mation is then used to create and initialize a new instance of the control class.
As mentioned, the runat attribute is unnecessary in this context. In ASP.NET,
the runat attribute is key, but in practice, it has no other role than marking the
surrounding markup text for parsing and instantiation. It does not contain infor-
mation useful to instantiate a control, and for this reason can be omitted from
the strings you pass directly to ParseControl.
Table 2-9 enumerates all the methods in the Page class that have to do
with HTML and script code to be inserted in the client page.
Method Description
GetPostBackClientEvent Calls into GetPostBackEventReference.
GetPostBackClientHyperlink Appends javascript: to the beginning of the return
string received from GetPostBackEventReference.
javascript:__doPostBack(‘CtlID’,’’)
GetPostBackEventReference Returns the prototype of the client-side script func-
tion that causes, when invoked, a postback. It takes a
Control and an argument, and it returns a string like
this:
__doPostBack(‘CtlID’,’’)
IsClientScriptBlockRegistered Determines whether the specified client script is reg-
istered with the page.
IsStartupScriptRegistered Determines whether the specified client startup script
is registered with the page.
RegisterArrayDeclaration Use this method to add an ECMAScript array to the
client page. This method accepts the name of the
array and a string that will be used verbatim as the
body of the array. For example, if you call the
method with arguments such as “theArray” and “‘a’,
‘b’", you get the following JavaScript code:
var theArray = new Array(‘a’, ‘b’);
(continued)
C02619034.fm Page 102 Friday, May 9, 2003 11:31 AM
Method Description
RegisterClientScriptBlock An ASP.NET page uses this method to emit client-side
script blocks in the client page just after the opening
tag of the HTML <form> element.
RegisterHiddenField Use this method to automatically register a hidden
field on the page.
RegisterOnSubmitStatement Use this method to emit client script code that han-
dles the client OnSubmit event. The script should be
a JavaScript function call to client code registered
elsewhere.
RegisterStartupScript An ASP.NET page uses this method to emit client-side
script blocks in the client page just before closing the
HTML <form> element.
Many methods listed in Table 2-9 let you emit script in the client page—
either JavaScript or VBScript. When you use any of these methods, you actually
tell the page to insert that script code when the page is rendered. So when any
of these methods execute, the script-related information is simply cached in
internal structures and used later when the page object generates its HTML text.
The same pattern applies to hidden fields and ECMAScript arrays.
Event Description
AbortTransaction Occurs for ASP.NET pages marked to participate in an automatic
transaction when a transaction aborts.
CommitTransaction Occurs for ASP.NET pages marked to participate in an automatic
transaction when a transaction commits.
DataBinding Occurs when the DataBind method is called on the page to
bind all the child controls to their respective data sources.
Disposed Occurs when the page is released from memory, which is the
last stage of the page life cycle.
Error Occurs when an unhandled exception is thrown.
Init Occurs when the page is initialized, which is the first step in the
page life cycle.
Load Occurs when the page loads up.
PreRender Occurs when the page is about to render.
Unload Occurs when the page is unloaded from memory but not yet
disposed.
For the code-behind feature to work, the Inherits attribute is always nec-
essary, as it is the only supported way to inform the ASP.NET runtime to derive
ASP.Page_aspx from a different and more specific page class. (The code-behind
class itself must necessarily derive from Page or from a class that, in turn, inher-
its from Page.)
You can also deploy a code-behind class as plain source code. In this
case, you use Inherits to indicate the name of the class and the Src attribute to
specify the URL from which the class can be loaded and compiled, as shown in
the following line of code:
<%@ Page Language="C#” Inherits="MyBasePageClass” Src="MyBasePage.cs” %>
Tip Code-behind classes and inline code can happily cohabit within
the same Web Forms page. Interestingly, this fact alone opens up the
possibility of using multiple languages to develop the page. In fact, you
could use one language to develop the code-behind class and then
link it to the page using the Inherits attribute. You’re responsible for
compiling the class and deploying the class through an assembly.
Next, you’d use the other language to set the Language attribute and
develop inline code for the page. Finally, you should note that this pro-
gramming style is not supported by Visual Studio .NET. If you like it,
use Web Matrix or other third-party commercial products.
If you decide to use the Src attribute, you have to deploy the source code
of the class, thus making the logic of your solution available on the Web server.
On the other hand, this approach simplifies the maintenance of the Web site
because you could modify the file (or better yet, a copy of it) on the Web server.
Otherwise, you have to modify the source, rebuild the project yourself, and
then deploy it again to the Web server.
Moving to Code-Behind
A practical advantage of the code-behind approach is that it allows two pro-
grammers or programming teams to simultaneously work on the development
of a Web page. After the structure of a page is defined in terms of the number,
names, and types of constituent controls, HTML designers can work on the
appearance of the page, determining the position, colors, and fonts of the con-
trols; they can even determine the necessity for client-side scripting. At the
same time, programmers can develop the code and event handlers required for
the page to work properly and fulfill users’ expectations.
Any existing ASP.NET page can migrate to code-behind with very few
changes. The first step is writing a class that inherits, either directly or indirectly,
from Page. Next, you add to the class, as a protected or public member, any
control declared in the page layout that has the runat attribute set to server.
Also, convert any global variable or procedure in the <script> section of the
original ASP.NET page into a new member of the class with the scope and vis-
ibility you want.
Be sure to choose the right control type that will lie behind an ASP.NET or
HTML tag. For example, an <asp:textbox runat="server"> tag maps to a TextBox
control, whereas an equivalent <input type=text runat="server"> tag maps to
HtmlInputText.
Caution Because the actual page class being processed will inherit
from the code-behind class, make sure you don’t declare any control
members as private. In this case, in fact, the actual derived class won’t
be able to access the control because of the protection level. Use the
public modifier—or better yet, the protected modifier—instead.
Once you’ve accomplished this, you’re almost done. A subtle design issue
you might want to consider is the following. Typically, your Web page will have
controls that fire server-side events—for example, a submit button:
<asp:button runat="server” id="TheButton” text="Submit” />
To make the control usable, you need to bind it to some code. You can do
this in two ways. You could either add an onclick attribute to the tag or pro-
grammatically define an event handler in the code-behind class. In the former
case, you end up having the following code:
<asp:button runat="server” id="TheButton” text="Submit”
onclick="OnSubmitDocument” />
C02619034.fm Page 107 Friday, May 9, 2003 11:31 AM
There’s nothing bad about this code, at least in terms of overall function-
ality. However, it makes the ideal of neat separation between code and layout
a bit less neat. The page layout, in fact, contains a reference to a piece of code.
Among other things, this means HTML designers must remember to change the
attribute should they decide to rename the handler.
From a design standpoint, a much better approach is to go the program-
matic route and register the event handler with code. In C#, you would accom-
plish this with the following code:
override protected void OnInit(EventArgs e)
{
this.TheButton.Click += new System.EventHandler(this.OnSubmitDocument);
}
Page Inheritance
It’s hard to say whether code-behind inspired the separation between code and
layout or whether things happened the other way around—that is, code-behind
was invented as a way to implement code and layout separation. Rather than
concern ourselves with how it all developed, let’s look at code-behind from
another, more architectural, perspective.
At the end of the day, code-behind is just a fancy name for the ASP.NET
feature that lets you explicitly set the base class for the dynamically created
page class. And I think this feature alone is much more important to developers
than the separation between code and layout. To achieve code separation, you
don’t need to know about the architecture of code-behind—just use Visual Stu-
dio .NET and you’re done.
The capability of changing the base class of dynamic pages opens a new
world of possibilities that can be described as the Web counterpart of the visual
form inheritance we have in Windows Forms applications. For Windows Forms,
visual inheritance indicates the .NET Framework ability to let you create new
forms by inheriting from previously created forms. The .NET Framework pro-
vides you with a base Form class that you extend in user projects by adding
child controls. Each application form is definitely an instance of some user-
defined, Form-based class.
You normally inherit your custom forms always from the base class
Form, but a smarter approach is possible. For example, to make all your
applications share the same look and feel, you could create a form class
inheriting from Form, add some features and controls, and compile the result-
ant class into a new assembly. Suppose you call this form class AcmeForm.
Next, whenever you need to create a new Windows Forms application with
C02619034.fm Page 108 Friday, May 9, 2003 11:31 AM
the same characteristics, you simply derive the main window from AcmeForm
and get a ready-to-use and feature-rich form window. Although it’s impressive
to show, this feature involves nothing that qualifies as rocket science; it’s just
the offspring of class inheritance. As such, it can be applied to ASP.NET pages
as well. Code-behind and the Inherits attribute of the @Page directive are the
underpinnings of this important feature.
<body onload="SetInputFocus()">
<form name="__theForm">
<input type="text” value="Joe” name="fname">
<input type="text” value="Users” name="lname">
</form>
</body>
</html>
Although it’s easy to understand and implement, the solution is still boring
to code (or even to cut and paste) for each page of the application. The code-
behind technique comes to the rescue. The idea is that you derive a new class
from Page and make sure it contains the needed script code and runs it upon
startup.
C02619034.fm Page 109 Friday, May 9, 2003 11:31 AM
namespace ProAspNet.CS.Ch02
{
public class Page : System.Web.UI.Page
{
public Page()
{
}
// Internals
private const string FUNCTION_NAME = “__setFocus";
private const string SCRIPT_NAME = “__inputFocusHandler";
// Gets/Sets the name of the HTML control with the input focus
public string Focus;
sb.Append(formName);
sb.Append(“][ctl].focus();”);
sb.Append(“\n }”);
sb.Append(“\n}\n”);
In the PreRender event, the page registers a startup script. If the server
form is named theForm and the Focus property is set to user, the following
script will be generated:
<script language=javascript>
function __setFocus(ctl) {
if (document.forms["theForm"][ctl] != null) {
document.forms["theForm"][ctl].focus();
}
}
__setFocus(‘user’);
</script>
The __setFocus JavaScript function is first declared and then invoked with
a particular argument. The script is registered using the RegisterStartupScript
method. A startup script is placed exactly before closing the <form> tag and
executed when the browser has finished with the rest of the form—in this
sense, it is called a startup script and is functionally equivalent to setting the
onload attribute of the page body.
<script runat="server">
public void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
this.Focus = “user";
}
</script>
<html>
<head><title>Pro ASP.NET (Ch 02)</title></head>
<body>
<h2>Log on to the System</h2>
<form runat="server">
<b>UserName</b><br>
<asp:textbox runat="server” id="user” /><br>
<b>Password</b><br>
<asp:textbox runat="server” id="pswd” /><br>
<asp:button id="go” runat="server” text="Connect” />
</form>
</body>
</html>
The IsPostBack property is set to true if the page is being displayed after
a postback. If you don’t check against IsPostBack, the input focus will be set to
the control named user each time the page is accessed; otherwise, it will be set
only the first time the page is accessed in the session.
Note that the base page could also be changed for all the applications running
on a server machine. In this case, you must edit the machine.config file, locate
the <pages> section, and add the pageBaseType attribute. By default, the
attribute is initialized implicitly and is not listed in the configuration file. To edit
both web.config and machine.config, you need administrator privileges.
Note In the next version of ASP.NET, the WebControl class and the
Page class will provide a nearly identical feature through a method
named SetFocus. The method takes the ID of a control and caches it
internally. When the page is rendered to HTML, ad hoc script code will
be generated to set the focus to the control.
ASP.NET are orthogonal to true visual inheritance. The contents of the master,
in fact, are merged into the derived page rather than serving as a base class for
the derived page.
Conclusion
Resources
■ HTTP Architecture (http://www.develop.com/conferences
/conferencedotnet/materials/A4.pdf)
■ HTTP Pipeline (http://www.develop.com/summercamp
/conferencedotnet/materials/W2.pdf)
■ Securely Implement Request Processing, Filtering, and Content Redi-
rection with HTTP Pipelines in ASP.NET (http://msdn.microsoft.com
/msdnmag/issues/02/09/HTTPPipelines)
■ Viewstate Optimization Strategies in ASP.NET (http://www.
webreference.com/programming/asp/viewstate)
■ KB306005 Repair IIS Mapping After You Remove and Reinstall IIS
■ KB315158 ASP.NET Does Not Work with the Default ASPNET
Account on a Domain Controller