Chapter 5. Anatomy of an ASP.NET Page

The wise are instructed by reason; ordinary minds by experience; the stupid, by necessity; and brutes by instinct.

Cicero

ASP.NET pages are dynamically compiled on demand when first requested in the context of a Web application. Dynamic compilation is not specific to ASP.NET pages alone (.aspx files); it also occurs with services (.svc and asmx files), Web user controls (.ascx files), HTTP handlers (.ashx files), and a few more ASP.NET application files such as the global.asax file. A pipeline of run-time modules takes care of the incoming HTTP packet and makes it evolve from a simple protocol-specific payload up to the rank of a server-side ASP.NET object—whether it’s an instance of a class derived from the system’s Page class or something else.

The ASP.NET HTTP runtime processes the page object and causes it to generate the markup to insert in the response. The generation of the response is marked by several events handled by user code and collectively known as the page life cycle.

In this chapter, we’ll review how an HTTP request for an .aspx resource is mapped to a page object, the programming interface of the Page class, and how to control the generation of the markup by handling events of the page life cycle.

Note

By default in release mode, application pages are compiled in batch mode, meaning that ASP.NET attempts to stuff as many uncompiled pages as possible into a single assembly. The attributes maxBatchSize and maxBatchGeneratedFileSize in the <compilation> section let you limit the number of pages packaged in a single assembly and the overall size of the assembly. By default, you will have no more than 1000 pages per batched compilation and no assembly larger than 1 MB. In general, you don’t want users to wait too long when a large number of pages are compiled the first time. At the same time, you don’t want to load a huge assembly in memory to serve only a small page, or to start compilation for each and every page. The maxBatchSize and maxBatchGeneratedFileSize attributes help you find a good balance between first-hit delay and memory usage.

Invoking a Page

Let’s start by examining in detail how the .aspx page is converted into a class and then compiled into an assembly. Generating an assembly for a particular .aspx resource is a two-step process. First, the source code of the resource file is parsed and a corresponding class is created that inherits either from Page or another class that, in turn, inherits from Page. Second, the dynamically generated class is compiled into an assembly and cached in an ASP.NET-specific temporary directory.

The compiled page remains in use as long as no changes occur to the linked .aspx source file or the whole application is restarted. Any changes to the linked .aspx file invalidate the current page-specific assembly and force the HTTP runtime to create a new assembly on the next request for the page.

Note

Editing files such as web.config and global.asax causes the whole application to restart. In this case, all the pages will be recompiled as soon as each page is requested. The same happens if a new assembly is copied or replaced in the application’s Bin folder.

The Runtime Machinery

Most of the requests that hit Internet Information Services (IIS) are forwarded to a particular run-time module for actual processing. The only exception to this model is made for static resources (for example, images) that IIS can quickly serve on its own. A module that can handle Web resources within IIS is known as an ISAPI extension and can be made of managed or unmanaged code. The worker process that serves the Web application in charge of the request loads the pinpointed module and commands it through a contracted programming interface.

For example, old-fashioned ASP pages are processed by an ISAPI extension named asp.dll whereas files with an .aspx extension—classic Web Forms pages—are assigned to an ISAPI extension named aspnet_isapi.dll, as shown in Figure 5-1. Extension-less requests like those managed by an ASP.NET MVC application are intercepted at the gate and redirected to completely distinct runtime machinery. (At least this is what happens under IIS 7 in integrated mode. In older configurations, you still need to register a specific extension for the requests to be correctly handled by IIS.)

Setting the handler for resources with an .aspx extension.

Figure 5-1. Setting the handler for resources with an .aspx extension.

Resource Mappings

IIS stores the list of recognized resources in the IIS metabase. Depending on the version of IIS you are using, the metabase might be a hidden component or a plain configuration file that an administrator can freely edit by hand. Regardless of the internal implementation, the IIS manager tool provides a user interface to edit the content of the metabase.

Upon installation, ASP.NET modifies the IIS metabase to make sure that aspnet_isapi.dll can handle some typical ASP.NET resources. Table 5-1 lists some of these resources.

Table 5-1. IIS Application Mappings for aspnet_isapi.dll

Extension

Resource Type

.asax

ASP.NET application files. Note, though, that any .asax file other than global.asax is ignored. The mapping is there only to ensure that global.asax can’t be requested directly.

.ascx

ASP.NET user control files.

.ashx

HTTP handlers—namely, managed modules that interact with the low-level request and response services of IIS.

.asmx

Files that represent the endpoint of old-fashioned .NET Web services.

.aspx

Files that represent ASP.NET pages.

.axd

Extension that identifies internal HTTP handlers used to implement system features such as application-level tracing (trace.axd) or script injection (webresource.axd).

.svc

Files that represent the endpoint of a Windows Communication Foundation (WCF) service.

In addition, the aspnet_isapi.dll extension handles other typical Microsoft Visual Studio extensions such as .cs, .csproj, .vb, .vbproj, .config, and .resx.

As mentioned in Chapter 2, the exact behavior of the ASP.NET ISAPI extension depends on the process model selected for the application—integrated pipeline (the default in IIS 7 and superior) or classic pipeline. Regardless of the model, at the end of the processing pipeline the originally requested URL that refers to an .aspx resource is mapped to, and served through, an instance of a class that represents an ASP.NET Web Forms page. The base class is the System.Web.UI.Page class.

Representing the Requested Page

The aforementioned Page class is only the base class. The actual class being used by the IIS worker process is a dynamically created derived class. So the ASP.NET HTTP runtime environment first determines the name of the class that will be used to serve the request. A particular naming convention links the URL of the page to the name of the class. If the requested page is, say, default.aspx, the associated class turns out to be ASP.default_aspx. The transformation rule applies a fixed ASP namespace and replaces any dot (.) with an underscore (_). If the URL contains a directory name, any slashes are also replaced with an underscore.

If no class exists with the specified name in any of the assemblies currently loaded in the AppDomain, the HTTP runtime orders that the class be created and compiled on the fly. This step is often referred to as the dynamic compilation of ASP.NET pages.

The source code for the new class is created by parsing the source code of the .aspx resource, and it’s temporarily saved in the ASP.NET temporary folder. The parser attempts to create a class with an initializer method able to create instances of any referenced server controls found in the ASPX markup. A referenced server control results from tags explicitly decorated with the runat=server attribute and from contiguous literals, including blanks and carriage returns. For example, consider the following short piece of markup:

<html>
<body>
<asp:button runat="server" ID="Button1" text="Click" />
</body>
</html>

When parsed, it sparks three distinct server control instances: two literal controls and a Button control. The first literal comprehends the text “<html><body>” plus any blanks and carriage returns the editor has put in. The second literal includes “</body></html>”.

Next, the Page-derived class is compiled and loaded in memory to serve the request. When a new request for the same page arrives, the class is ready and no compile step will ever take place. (The class will be re-created and recompiled only if the source code of the .aspx source changes at some point.)

The ASP.default_aspx class inherits from Page or, more likely, from a class that in turn inherits from Page. More precisely, the base class for ASP.default_aspx will be a combination of the code-behind, partial class you created through Visual Studio and a second partial class dynamically arranged by the ASP.NET HTTP runtime. The second, implicit partial class contains the declaration of protected properties for any explicitly referenced server controls. This second partial class is the key that allows you to write the following code successfully:

// No member named Button1 has ever been explicitly declared in any code-behind
// class. It is silently added at compile time through a partial class.
Button1.Text = ...;

Partial classes are a hot feature of .NET compilers. When partially declared, a class has its source code split over multiple source files, each of which appears to contain an ordinary class definition from beginning to end. The keyword partial, though, informs the compiler that the class declaration being processed is incomplete. To get full and complete source code, the compiler must look into other files specified on the command line.

Partial Classes in ASP.NET Projects

Partial classes are a compiler feature originally designed to overcome the brittleness of tool-generated code back in Visual Studio 2003 projects. Ideal for team development, partial classes simplify coding and avoid manual file synchronization in all situations in which many authors work on distinct segments of the class logical class.

Generally, partial classes are a source-level, assembly-limited, non-object-oriented way to extend the behavior of a class. A number of advantages are derived from intensive use of partial classes. As mentioned, you can have multiple teams at work on the same component at the same time. In addition, you have a neat and elegant way to add functionality to a class incrementally. In the end, this is just what the ASP.NET runtime does.

The ASPX markup defines server controls that will be handled by the code in the code-behind class. For this model to work, the code-behind class needs to incorporate references to these server controls as internal members—typically, protected members. In Visual Studio, the code-behind class is a partial class that just lacks members’ declaration. Missing declarations are incrementally added at run time via a second partial class created by the ASP.NET HTTP runtime. The compiler of choice (C#, Microsoft Visual Basic .NET, or whatever) will then merge the two partial classes to create the real parent of the dynamically created page class.

Processing the Request

So to serve a request for a page named default.aspx, the ASP.NET runtime gets or creates a reference to a class named ASP.default_aspx. Next, the HTTP runtime environment invokes the class through the methods of a well-known interface—IHttpHandler. The root Page class implements this interface, which includes a couple of members: the ProcessRequest method and the Boolean IsReusable property. After the HTTP runtime has obtained an instance of the class that represents the requested resource, invoking the ProcessRequest method—a public method—gives birth to the process that culminates in the generation of the final response for the browser. As mentioned, the steps and events that execute and trigger out of the call to ProcessRequest are collectively known as the page life cycle.

Although serving pages is the ultimate goal of the ASP.NET runtime, the way in which the resultant markup code is generated is much more sophisticated than in other platforms and involves many objects. The IIS worker process passes any incoming HTTP requests to the so-called HTTP pipeline. The HTTP pipeline is a fully extensible chain of managed objects that works according to the classic concept of a pipeline. All these objects form what is often referred to as the ASP.NET HTTP runtime environment.

This ASP.NET-specific pipeline is integrated with the IIS pipeline in place for any requests when the Web application is configured to work in IIS 7 Integrated mode. Otherwise, IIS and ASP.NET use distinct pipelines—an unmanaged pipeline for IIS and a managed pipeline for ASP.NET.

A page request passes through a pipeline of objects that process the original HTTP payload and, at the end of the chain, produce some markup code for the browser. The entry point in this pipeline is the HttpRuntime class.

The HttpRuntime Class

The ASP.NET worker process activates the HTTP pipeline in the beginning by creating a new instance of the HttpRuntime class and then calling its ProcessRequest method for each incoming request. For the sake of clarity, note that despite the name, HttpRuntime.ProcessRequest has nothing to do with the IHttpHandler interface.

The HttpRuntime class contains a lot of private and internal methods and only three public static methods: Close, ProcessRequest, and UnloadAppDomain, as detailed in Table 5-2.

Table 5-2. Public Methods in the HttpRuntime Class

Method

Description

Close

Removes all items from the ASP.NET cache, and terminates the Web application. This method should be used only when your code implements its own hosting environment. There is no need to call this method in the course of normal ASP.NET request processing.

ProcessRequest

Drives all ASP.NET Web processing execution.

UnloadAppDomain

Terminates the current ASP.NET application. The application restarts the next time a request is received for it.

Note that all the methods shown in Table 5-2 have limited applicability in user applications. In particular, you’re not supposed to use ProcessRequest in your own code, whereas Close is useful only if you’re hosting ASP.NET in a custom application. Of the three methods in Table 5-2, only UnloadAppDomain can be considered for use if, under certain run-time conditions, you realize you need to restart the application. (See the sidebar What Causes Application Restarts? later in this chapter.)

Upon creation, the HttpRuntime object initializes a number of internal objects that will help carry out the page request. Helper objects include the cache manager and the file system monitor used to detect changes in the files that form the application. When the ProcessRequest method is called, the HttpRuntime object starts working to serve a page to the browser. It creates a new empty context for the request and initializes a specialized text writer object in which the markup code will be accumulated. A context is given by an instance of the HttpContext class, which encapsulates all HTTP-specific information about the request.

After that, the HttpRuntime object uses the context information to either locate or create a Web application object capable of handling the request. A Web application is searched using the virtual directory information contained in the URL. The object used to find or create a new Web application is HttpApplicationFactory—an internal-use object responsible for returning a valid object capable of handling the request.

Before we get to discover more about the various components of the HTTP pipeline, a look at Figure 5-2 is in order.

The HTTP pipeline processing for a page.

Figure 5-2. The HTTP pipeline processing for a page.

The Application Factory

During the lifetime of the application, the HttpApplicationFactory object maintains a pool of HttpApplication objects to serve incoming HTTP requests. When invoked, the application factory object verifies that an AppDomain exists for the virtual folder the request targets. If the application is already running, the factory picks an HttpApplication out of the pool of available objects and passes it the request. A new HttpApplication object is created if an existing object is not available.

If the virtual folder has not yet been called for the first time, a new HttpApplication object for the virtual folder is created in a new AppDomain. In this case, the creation of an HttpApplication object entails the compilation of the global.asax application file, if one is present, and the creation of the assembly that represents the actual page requested. This event is actually equivalent to the start of the application. An HttpApplication object is used to process a single page request at a time; multiple objects are used to serve simultaneous requests.

The HttpApplication Object

HttpApplication is the base class that represents a running ASP.NET application. A derived HTTP application class is dynamically generated by parsing the contents of the global.asax file, if any is present. If global.asax is available, the application class is built and named after it: ASP.global_asax. Otherwise, the base HttpApplication class is used.

An instance of an HttpApplication-derived class is responsible for managing the entire lifetime of the request it is assigned to. The same instance can be reused only after the request has been completed. The HttpApplication maintains a list of HTTP module objects that can filter and even modify the content of the request. Registered modules are called during various moments of the elaboration as the request passes through the pipeline.

The HttpApplication object determines the type of object that represents the resource being requested—typically, an ASP.NET page, a Web service, or perhaps a user control. HttpApplication then uses the proper handler factory to get an object that represents the requested resource. The factory either instantiates the class for the requested resource from an existing assembly or dynamically creates the assembly and then an instance of the class. 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.

Let’s see what happens when the resource requested is a page.

The Page Factory

When the HttpApplication object in charge of the request has figured out the proper handler, it creates an instance of the handler factory object. For a request that targets a page, the factory is a class named PageHandlerFactory. To find the appropriate handler, HttpApplication uses the information in the <httpHandlers> section of the configuration file as a complement to the information stored in the IIS handler mappings list, as shown in Figure 5-3.

The HTTP pipeline processing for a page.

Figure 5-3. The HTTP pipeline processing for a page.

Bear in mind that handler factory objects do not compile the requested resource each time it is invoked. The compiled code is stored in an ASP.NET temporary 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. As mentioned, the actual object inherits from the System.Web.UI.Page class, which in turn implements the IHttpHandler interface. The page object is returned to the application factory, which passes that back to the HttpRuntime object. The final step accomplished by the ASP.NET runtime is calling the IHttpHandler’s ProcessRequest method on the page object. This call causes the page to execute the user-defined code and generate the markup for the browser.

In Chapter 17, we’ll return to the initialization of an ASP.NET application, the contents of global.asax, and the information stuffed into the HTTP context—a container object, created by the HttpRuntime class, that is populated, passed along the pipeline, and finally bound to the page handler.

The Processing Directives of a Page

Processing directives configure the run-time environment that will execute the page. In ASP.NET, directives can be located anywhere in the page, although it’s a good and common practice to place them at the beginning of the file. In addition, the name of a directive is case insensitive and the values of directive attributes don’t need to be quoted. The most important and most frequently used directive in ASP.NET is @Page. The complete list of ASP.NET directives is shown in Table 5-3.

Table 5-3. Directives Supported by ASP.NET Pages

Directive

Description

@ Assembly

Links an assembly to the current page or user control.

@ Control

Defines control-specific attributes that guide the behavior of the control compiler.

@ Implements

Indicates that the page, or the user control, implements a specified .NET Framework interface.

@ Import

Indicates a namespace to import into a page or user control.

@ Master

Identifies an ASP.NET master page. (See Chapter 8.)

@ MasterType

Provides a way to create a strongly typed reference to the ASP.NET master page when the master page is accessed from the Master property. (See Chapter 8.)

@ OutputCache

Controls the output caching policies of a page or user control. (See Chapter 18.)

@ Page

Defines page-specific attributes that guide the behavior of the page compiler and the language parser that will preprocess the page.

@ PreviousPageType

Provides a way to get strong typing against the previous page, as accessed through the PreviousPage property.

@ Reference

Links a page or user control to the current page or user control.

@ Register

Creates a custom tag in the page or the control. The new tag (prefix and name) is associated with the namespace and the code of a user-defined control.

With the exception of @Page, @PreviousPageType, @Master, @MasterType, and @Control, all directives can be used both within a page and a control declaration. @Page and @Control are mutually exclusive. @Page can be used only in .aspx files, while the @Control directive can be used only in user control .ascx files. @Master, in turn, is used to define a very special type of page—the master page.

The syntax of a processing directive is unique and common to all supported types of directives. Multiple attributes must be separated with blanks, and no blank can be placed around the equal sign (=) that assigns a value to an attribute, as the following line of code demonstrates:

<%@ Directive_Name attribute="value" [attribute="value"...] %>

Each directive has its own closed set of typed attributes. Assigning a value of the wrong type to an attribute, or using a wrong attribute with a directive, results in a compilation error.

Important

The content of directive attributes is always rendered as plain text. However, attributes are expected to contain values that can be rendered to a particular .NET Framework type, specific to the attribute. When the ASP.NET page is parsed, all the directive attributes are extracted and stored in a dictionary. The names and number of attributes must match the expected schema for the directive. The string that expresses the value of an attribute is valid as long as it can be converted into the expected type. For example, if the attribute is designed to take a Boolean value, true and false are its only feasible values.

The @Page Directive

The @Page directive can be used only in .aspx pages and generates a compile error if used with other types of ASP.NET files such as controls and Web services. Each .aspx file is allowed to include at most one @Page directive. Although not strictly necessary from the syntax point of view, the directive is realistically required by all pages of some complexity.

@Page features over 40 attributes that can be logically grouped in three categories: compilation (defined in Table 5-4), overall page behavior (defined in Table 5-5), and page output (defined in Table 5-6). Each ASP.NET page is compiled upon first request, and the HTML actually served to the browser is generated by the methods of the dynamically generated class. The attributes listed in Table 5-4 let you fine-tune parameters for the compiler and choose the language to use.

Table 5-4. @Page Attributes for Page Compilation

Attribute

Description

ClassName

Specifies the name of the class that will be dynamically compiled when the page is requested. It must be a class name without namespace information.

CodeFile

Indicates the path to the code-behind class for the current page. The source class file must be deployed to the Web server.

CodeBehind

Attribute consumed by Visual Studio, indicates the path to the code-behind class for the current page. The source class file will be compiled to a deployable assembly.

CodeFileBaseClass

Specifies the type name of a base class for a page and its associated code-behind class. The attribute is optional, but when it is used the CodeFile attribute must also be present.

CompilationMode

Indicates whether the page should be compiled at run time.

CompilerOptions

A sequence of compiler command-line switches used to compile the page.

Debug

A Boolean value that indicates whether the page should be compiled with debug symbols.

Explicit

A Boolean value that determines whether the page is compiled with the Visual Basic Option Explicit mode set to On. Option Explicit forces the programmer to explicitly declare all variables. The attribute is ignored if the page language is not Visual Basic .NET.

Inherits

Defines the base class for the page to inherit. It can be any class derived from the Page class.

Language

Indicates the language to use when compiling inline code blocks (<% … %>) and all the code that appears in the page <script> section. Supported languages include Visual Basic .NET, C#, JScript .NET, and J#. If not otherwise specified, the language defaults to Visual Basic .NET.

LinePragmas

Indicates whether the run time should generate line pragmas in the source code to mark specific locations in the file for the sake of debugging tools.

MasterPageFile

Indicates the master page for the current page.

Src

Indicates the source file that contains the implementation of the base class specified with Inherits. The attribute is not used by Visual Studio and other Rapid Application Development (RAD) designers.

Strict

A Boolean value that determines whether the page is compiled with the Visual Basic Option Strict mode set to On. When this attribute is enabled, Option Strict permits only type-safe conversions and prohibits implicit conversions in which loss of data is possible. (In this case, the behavior is identical to that of C#.) The attribute is ignored if the page language is not Visual Basic .NET.

Trace

A Boolean value that indicates whether tracing is enabled. If tracing is enabled, extra information is appended to the page’s output. The default is false.

TraceMode

Indicates how trace messages are to be displayed for the page when tracing is enabled. Feasible values are SortByTime and SortByCategory. The default, when tracing is enabled, is SortByTime.

WarningLevel

Indicates the compiler warning level at which you want the compiler to abort compilation for the page. Possible values are 0 through 4.

Attributes listed in Table 5-5 allow you to control to some extent the overall behavior of the page and the supported range of features. For example, you can set a custom error page, disable session state, and control the transactional behavior of the page.

Note

The schema of attributes supported by @Page is not as strict as for other directives. In particular, any public properties defined on the page class can be listed as an attribute, and initialized, in a @Page directive.

Table 5-5. @Page Attributes for Page Behavior

Attribute

Description

AspCompat

A Boolean attribute that, when set to true, allows the page to be executed on a single-threaded apartment (STA) thread. The setting allows the page to call COM+ 1.0 components and components developed with Microsoft Visual Basic 6.0 that require access to the unmanaged ASP built-in objects. (I’ll return to this topic in Chapter 16.)

Async

If this attribute is set to true, the generated page class derives from IHttpAsyncHandler rather than having IHttpHandler add some built-in asynchronous capabilities to the page.

AsyncTimeOut

Defines the timeout in seconds used when processing asynchronous tasks. The default is 45 seconds.

AutoEventWireup

A Boolean attribute that indicates whether page events are automatically enabled. It’s set to true by default. Pages developed with Visual Studio .NET have this attribute set to false, and page events for these pages are individually tied to handlers.

Buffer

A Boolean attribute that determines whether HTTP response buffering is enabled. It’s set to true by default.

Description

Provides a text description of the page. The ASP.NET page parser ignores the attribute, which subsequently has only a documentation purpose.

EnableEventValidation

A Boolean value that indicates whether the page will emit a hidden field to cache available values for input fields that support event data validation. It’s set to true by default.

EnableSessionState

Defines how the page should treat session data. If this attribute is set to true, the session state can be read and written to. If it’s set to false, session data is not available to the application. Finally, if this attribute is set to ReadOnly, the session state can be read but not changed.

EnableViewState

A Boolean value that indicates whether the page view state is maintained across page requests. The view state is the page call context—a collection of values that retain the state of the page and are carried back and forth. View state is enabled by default. (I’ll cover this topic in Chapter 17.)

EnableTheming

A Boolean value that indicates whether the page will support themes for embedded controls. It’s set to true by default.

EnableViewStateMac

A Boolean value that indicates ASP.NET should calculate a machine-specific authentication code and append it to the view state of the page (in addition to Base64 encoding). The Mac in the attribute name stands for machine authentication check. When the attribute is true, upon postbacks ASP.NET will check the authentication code of the view state to make sure that it hasn’t been tampered with on the client.

ErrorPage

Defines the target URL to which users will be automatically redirected in case of unhandled page exceptions.

MaintainScrollPositionOnPostback

A Boolean value that indicates whether to return the user to the same position in the client browser after postback.

SmartNavigation

A Boolean value that indicates whether the page supports the Microsoft Internet Explorer 5 or later smart navigation feature. Smart navigation allows a page to be refreshed without losing scroll position and element focus.

Theme, StylesheetTheme

Indicates the name of the theme (or style-sheet theme) selected for the page.

Transaction

Indicates whether the page supports or requires transactions. Feasible values are Disabled, NotSupported, Supported, Required, and RequiresNew. Transaction support is disabled by default.

ValidateRequest

A Boolean value that indicates whether request validation should occur. If this attribute is set to true, ASP.NET checks all input data against a hard-coded list of potentially dangerous values. This functionality helps reduce the risk of cross-site scripting attacks for pages. The value is true by default.

Attributes listed in Table 5-6 allow you to control the format of the output being generated for the page. For example, you can set the content type of the page or localize the output to the extent possible.

Table 5-6. @Page Directives for Page Output

Attribute

Description

ClientTarget

Indicates the target browser for which ASP.NET server controls should render content.

ClientIDMode

Specifies the algorithm to use to generate client ID values for server controls. This attribute requires ASP.NET 4.

CodePage

Indicates the code page value for the response. Set this attribute only if you created the page using a code page other than the default code page of the Web server on which the page will run. In this case, set the attribute to the code page of your development machine. A code page is a character set that includes numbers, punctuation marks, and other glyphs. Code pages differ on a per-language basis.

ContentType

Defines the content type of the response as a standard MIME type. Supports any valid HTTP content type string.

Culture

Indicates the culture setting for the page. Culture information includes the writing and sorting system, calendar, and date and currency formats. The attribute must be set to a non-neutral culture name, which means it must contain both language and country/region information. For example, en-US is a valid value, unlike en alone, which is considered country/region neutral.

LCID

A 32-bit value that defines the locale identifier for the page. By default, ASP.NET uses the locale of the Web server.

MetaDescription

Sets the “description” meta element for the page. The value set through the @Page directive overrides any similar values you might have specified as literal text in the markup. This attribute requires ASP.NET 4.

MetaKeywords

Sets the “keywords” meta element for the page. The value set through the @Page directive overrides any similar values you might have specified as literal text in the markup. This attribute requires ASP.NET 4.

ResponseEncoding

Indicates the character encoding of the page. The value is used to set the CharSet attribute on the content type HTTP header. Internally, ASP.NET handles all strings as Unicode.

UICulture

Specifies the default culture name used by Resource Manager to look up culture-specific resources at run time.

ViewStateEncryptionMode

Determines how and if the view state is encrypted. Feasible values are Auto, Always, or Never. The default is Auto, meaning that view state will be encrypted only if an individual control requests that.

ViewStateMode

Determines the value for the page’s ViewStateMode property that influences the way in which the page treats the view state of child controls. (More details are available in Chapter 17.) This attribute requires ASP.NET 4.

As you can see, many attributes discussed in Table 5-6 are concern with page localization. Building multilanguage and international applications is a task that ASP.NET, and the .NET Framework in general, greatly simplify.

The @Assembly Directive

The @Assembly directive adds an assembly to a collection of assembly names that are used during the compilation of the ASP.NET page so that classes and interfaces in the assembly are available for early binding to the code. You use the @Assembly directive when you want to reference a given assembly only from a specific page.

Some assemblies are linked by default for any ASP.NET application. The complete list can be found in the root web.config file of the Web server machine. The list is pretty long in ASP.NET 4, but it no longer includes the System.Web.Mobile assembly that was there for older versions of ASP.NET. The mobile assembly is now deprecated, but if you’re trying to upgrade an existing application to ASP.NET 4 that uses the assembly, you are required to add the assembly explicitly via an @Assembly directive or via a custom <compilation> section in the application.

Table 5-7 lists some of the assemblies that are automatically provided to the compiler for an ASP.NET 4 application.

Table 5-7. Assemblies Linked by Default in ASP.NET 4

Assembly File Name

Description

mscorlib

Provides the core functionality of the .NET Framework, including types, AppDomains, and run-time services

System.dll

Provides another bunch of system services, including regular expressions, compilation, native methods, file I/O, and networking

System.Configuration.dll

Defines classes to read and write configuration data.

System.Core.dll

Provides some other core functionality of the .NET Framework, including LINQ-to-Objects, the time-zone API, and some security and diagnostic classes

System.Data.dll

Defines data container and data access classes, including the whole ADO.NET framework

System.Data.DataSetExtensions.dll

Defines additional functions built over the ADO.NET DataSet object

System.Drawing.dll

Implements the GDI+ features

System.EnterpriseServices.dll

Provides the classes that allow for serviced components and COM+ interaction

System.Web.dll

Indicates the assembly implements the core ASP.NET services, controls, and classes

System.Web.ApplicationServices.dll

Provides classes that enable you to access ASP.NET authentication, roles, and profile functions via a bunch of built-in WCF services

System.Web.DynamicData.dll

Provides classes behind the ASP.NET Dynamic Data framework

System.Web.Entity.dll

Contains the code for the EntityDataSource component that supports Entity Framework

System.Web.Extensions.dll

Contains the code for AJAX extensions to ASP.NET

System.Web.Services.dll

Contains the core code that makes Web services run

System.Xml.dll

Implements the .NET Framework XML features

System.Xml.Linq.dll

Contains the code for the LINQ-to-XML parser

Note that you can modify, extend, or restrict the list of default assemblies by editing the global settings in the root web.config file under

%Windows%Microsoft.NETFrameworkv4.0.30319Config

If you do so, changes will apply to all ASP.NET applications run on that Web server. Alternatively, you can modify the assembly list on a per-application basis by editing the <assemblies> section under <compilation> in the application’s specific web.config file. Note also that the <compilation> section should be used only for global assembly cache (GAC) resident assemblies, not for the private assemblies that you deploy to the Bin folder.

By default, the <compilation> section in the root web.config file contains the following entry:

<add assembly="*" />

It means that any assembly found in the binary path of the application should be treated as if it were registered through the @Assembly directive. To prevent all assemblies found in the Bin directory from being linked to the page, remove the entry from the root configuration file. To link a needed assembly to the page, use the following syntax:

<%@ Assembly Name="AssemblyName" %>
<%@ Assembly Src="assembly_code.cs" %>

The @Assembly directive supports two mutually exclusive attributes: Name and Src. Name indicates the name of the assembly to link to the page. The name cannot include the path or the extension. Src indicates the path to a source file to dynamically compile and link against the page. The @Assembly directive can appear multiple times in the body of the page. In fact, you need a new directive for each assembly to link. Name and Src cannot be used in the same @Assembly directive, but multiple directives defined in the same page can use either.

Note

In terms of performance, the difference between Name and Src is minimal, although Name points to an existing and ready-to-load assembly. The source file referenced by Src is compiled only the first time it is requested. The ASP.NET runtime maps a source file with a dynamically compiled assembly and keeps using the compiled code until the original file undergoes changes. This means that after the first application-level call, the impact on the page performance is identical whether you use Name or Src.

Any assemblies you register through the @Assembly directive are used by the compiler at compile time, which allows for early binding. After the compilation of the requested ASP.NET file is complete, the assembly is loaded into the application domain, thus allowing late binding. In the end, any assemblies listed through the directive (implicitly through the root configuration or explicitly through the application configuration) is loaded into the AppDomain and referenced on demand.

Important

Removing an assembly from the Visual Studio project doesn’t help much to keep the AppDomain lean and mean. To ensure you load all the assemblies you want and only the ones you want, you should insert the following code in your configuration file:

<assemblies>
  <clear />
  <add assembly="..." />
  ...
  <add assembly="*" />
</assemblies>

The <clear /> tag removes all default configurations; the subsequent tags add just the assemblies your application needs. As you can verify for yourself, the default list will likely load assemblies you don’t need.

In debug mode, you can track the list of assemblies actually loaded in the AppDomain for the site using the following code:

var assemblies1 = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
var assemblies2 = AppDomain.CurrentDomain.GetAssemblies();

The size of the two arrays can vary quite a bit. The former counts just the dynamically referenced assemblies at the current stage of execution. The latter counts the number of assemblies physically loaded in the AppDomain (which can’t be unloaded unless you recycle the application).

The @Import Directive

The @Import directive links the specified namespace to the page so that all the types defined can be accessed from the page without specifying the fully qualified name. For example, to create a new instance of the ADO.NET DataSet class, you either import the System.Data namespace or specify the fully qualified class name whenever you need it, as in the following code:

System.Data.DataSet ds = new System.Data.DataSet();

After you’ve imported the System.Data namespace into the page, you can use more natural coding, as shown here:

DataSet ds = new DataSet();

The syntax of the @Import directive is rather self-explanatory:

<%@ Import namespace="value" %>

@Import can be used as many times as needed in the body of the page. The @Import directive is the ASP.NET counterpart of the C# using statement and the Visual Basic .NET Imports statement. Looking back at unmanaged C/C++, we could say the directive plays a role nearly identical to the #include directive. For example, to be able to connect to a Microsoft SQL Server database and grab some disconnected data, you need to import the following two namespaces:

<%@ Import namespace="System.Data" %>
<%@ Import namespace="System.Data.SqlClient" %>

You need the System.Data namespace to work with the DataSet and DataTable classes, and you need the System.Data.SqlClient namespace to prepare and issue the command. In this case, you don’t need to link against additional assemblies because the System.Data.dll assembly is linked by default.

Note

@Import helps the compiler only to resolve class names; it doesn’t automatically link required assemblies. Using the @Import directive allows you to use shorter class names, but as long as the assembly that contains the class code is not properly referenced, the compiler will generate a type error. In this case, using the fully qualified class name is of no help because the compiler lacks the type definition. You might have noticed that, more often than not, assembly and namespace names coincide. The latest version of Visual Studio (as well as some commercial products such as JetBrains ReSharper) is able to detect when you lack a reference and offers to import the namespace and reference the assembly with a single click. This is pure tooling activity—namespaces and assemblies are totally different beasts.

The @Implements Directive

The directive indicates that the current page implements the specified .NET Framework interface. An interface is a set of signatures for a logically related group of functions. An interface is a sort of contract that shows the component’s commitment to expose that group of functions. Unlike abstract classes, an interface doesn’t provide code or executable functionality. When you implement an interface in an ASP.NET page, you declare any required methods and properties within the <script> section. The syntax of the @Implements directive is as follows:

<%@ Implements interface="InterfaceName" %>

The @Implements directive can appear multiple times in the page if the page has to implement multiple interfaces. Note that if you decide to put all the page logic in a separate class file, you can’t use the directive to implement interfaces. Instead, you implement the interface in the code-behind class.

The @Reference Directive

The @Reference directive is used to establish a dynamic link between the current page and the specified page or user control. This feature has significant implications for the way you set up cross-page communication. It also lets you create strongly typed instances of user controls. Let’s review the syntax.

The directive can appear multiple times in the page. The directive features two mutually exclusive attributes: Page and Control. Both attributes are expected to contain a path to a source file:

<%@ Reference page="source_page" %>
<%@ Reference control="source_user_control" %>

The Page attribute points to an .aspx source file, whereas the Control attribute contains the path of an .ascx user control. In both cases, the referenced source file will be dynamically compiled into an assembly, thus making the classes defined in the source programmatically available to the referencing page. When running, an ASP.NET page is an instance of a .NET Framework class with a specific interface made of methods and properties. When the referencing page executes, a referenced page becomes a class that represents the .aspx source file and can be instantiated and programmed at will. For the directive to work, the referenced page must belong to the same domain as the calling page. Cross-site calls are not allowed, and both the Page and Control attributes expect to receive a relative virtual path.

Note

Cross-page posting can be considered as an alternate approach to using the @Reference directive. Cross-page posting is an ASP.NET feature through which you force an ASP.NET button control to post the content of its parent form to a given target page. I’ll demonstrate cross-page posting in Chapter 9.

The Page Class

In the .NET Framework, the Page class provides the basic behavior for all objects that an ASP.NET application builds by starting from .aspx files. Defined in the System.Web.UI namespace, the class derives from TemplateControl and implements the IHttpHandler interface:

public class Page : TemplateControl, IHttpHandler
{
   ...
}

In particular, TemplateControl is the abstract class that provides both ASP.NET pages and user controls with a base set of functionality. At the upper level of the hierarchy, you find the Control class. It defines the properties, methods, and events shared by all ASP.NET server-side elements—pages, controls, and user controls.

Derived from a class—TemplateControl—that implements INamingContainer, the Page class also serves as the naming container for all its constituent controls. In the .NET Framework, the naming container for a control is the first parent control that implements the INamingContainer interface. For any class that implements the naming container interface, ASP.NET creates a new virtual namespace in which all child controls are guaranteed to have unique names in the overall tree of controls. (This is 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 interface, thus qualifying it as the handler of a particular type of HTTP requests—those for .aspx files. The key element of the IHttpHandler interface is the ProcessRequest method, which is the method the ASP.NET runtime calls to start the page processing that will actually serve the request.

Note

INamingContainer is a marker interface that has no methods. Its presence alone, though, forces the ASP.NET runtime to create an additional namespace for naming the child controls of the page (or the control) that implements it. The Page class is the naming container of all the page’s controls, with the clear exception of those controls that implement the INamingContainer interface themselves or are children of controls that implement the interface.

Properties of the Page Class

The properties of the Page class can be classified in three distinct groups: intrinsic objects, worker properties, and page-specific properties. The tables in the following sections enumerate and describe them.

Intrinsic Objects

Table 5-8 lists all properties that return a helper object that is intrinsic to the page. In other words, objects listed here are all essential parts of the infrastructure that allows for the page execution.

Table 5-8. ASP.NET Intrinsic Objects in the Page Class

Property

Description

Application

Instance of the HttpApplicationState class; represents the state of the application. It is functionally equivalent to the ASP intrinsic Application object.

Cache

Instance of the Cache class; implements the cache for an ASP.NET application. More efficient and powerful than Application, it supports item priority and expiration.

Profile

Instance of the ProfileCommon class; represents the user-specific set of data associated with the request.

Request

Instance of the HttpRequest class; represents the current HTTP request.

Response

Instance of the HttpResponse class; sends HTTP response data to the client.

RouteData

Instance of the RouteData class; groups information about the selected route (if any) and its values and tokens. (Routing in Web Forms is covered in Chapter 4, “xxx.”) The object is supported only in ASP.NET 4.

Server

Instance of the HttpServerUtility class; provides helper methods for processing Web requests.

Session

Instance of the HttpSessionState class; manages user-specific data.

Trace

Instance of the TraceContext class; performs tracing on the page.

User

An IPrincipal object that represents the user making the request.

I’ll cover Request, Response, and Server in Chapter 16; Application and Session are covered in Chapter 17; Cache will be the subject of Chapter 19. Finally, User and security will be the subject of Chapter 19.

Worker Properties

Table 5-9 details page properties that are both informative and provide the foundation for functional capabilities. You can hardly write code in the page without most of these properties.

Table 5-9. Worker Properties of the Page Class

Property

Description

AutoPostBackControl

Gets a reference to the control within the page that caused the postback event.

ClientScript

Gets a ClientScriptManager object that contains the client script used on the page.

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.

Form

Returns the current HtmlForm object for the page.

Header

Returns a reference to the object that represents the page’s header. The object implements IPageHeader.

IsAsync

Indicates whether the page is being invoked through an asynchronous handler.

IsCallback

Indicates whether the page is being loaded in response to a client script callback.

IsCrossPagePostBack

Indicates whether the page is being loaded in response to a postback made from within another page.

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.

Master

Instance of the MasterPage class; represents the master page that determines the appearance of the current page.

MasterPageFile

Gets and sets the master file for the current page.

NamingContainer

Returns null.

Page

Returns the current Page object.

PageAdapter

Returns the adapter object for the current Page object.

Parent

Returns null.

PreviousPage

Returns the reference to the caller page in case of a cross-page postback.

TemplateSourceDirectory

Gets the virtual directory of the page.

Validators

Returns the collection of all validation controls contained in the page.

ViewStateUserKey

String property that represents a user-specific identifier used to hash the view-state contents. This trick is a line of defense against one-click attacks.

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).

The ViewStateUserKey property deserves a special mention. A common use for the user key is to stuff user-specific information that is then used to hash the contents of the view state along with other information. A typical 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 can’t construct a valid view state for your user account unless the attacker can also authenticate as you. With this configuration, you have another barrier against one-click attacks. This technique, though, might not be 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.

Context Properties

Table 5-10 lists properties that represent visual and nonvisual attributes of the page, such as the URL’s query string, the client target, the title, and the applied style sheet.

Table 5-10. Page-Specific Properties of the Page Class

Property

Description

ClientID

Always returns the empty string.

ClientIDMode

Determines the algorithm to use to generate the ID of HTML elements being output as part of a control’s markup. This property requires ASP.NET 4.

ClientQueryString

Gets the query string portion of the requested URL.

ClientTarget

Set to the empty string by default; allows you to specify the type of browser the HTML should comply with. Setting this property disables automatic detection of browser capabilities.

EnableViewState

Indicates whether the page has to manage view-state data. You can also enable or disable the view-state feature through the EnableViewState attribute of the @Page directive.

EnableViewStateMac

Indicates whether ASP.NET should calculate a machine-specific authentication code and append it to the page view state.

EnableTheming

Indicates whether the page supports themes.

ID

Always returns the empty string.

MetaDescription

Gets and sets the content of the description meta tag. This property requires ASP.NET 4.

MetaKeywords

Gets and sets the content of the keywords meta tag. This property requires ASP.NET 4.

MaintainScrollPositionOnPostback

Indicates whether to return the user to the same position in the client browser after postback.

SmartNavigation

Indicates whether smart navigation is enabled. Smart navigation exploits a bunch of browser-specific capabilities to enhance the user’s experience with the page.

StyleSheetTheme

Gets or sets the name of the style sheet applied to this page.

Theme

Gets and sets the theme for the page. Note that themes can be programmatically set only in the PreInit event.

Title

Gets or sets the title for the page.

TraceEnabled

Toggles page tracing on and off.

TraceModeValue

Gets or sets the trace mode.

UniqueID

Always returns the empty string.

ViewStateEncryptionMode

Indicates if and how the view state should be encrypted.

ViewStateMode

Enables the view state for an individual control even if the view state is disabled for the page. This property requires ASP.NET 4.

Visible

Indicates whether ASP.NET has to render the page. If you set Visible to false, ASP.NET doesn’t generate any HTML code for the page. When Visible is false, only the text explicitly written using Response.Write hits the client.

The three ID properties (ID, ClientID, and UniqueID) always return the empty string from a Page object. They make sense only for server controls.

Methods of the Page Class

The whole range of Page methods can be classified in a few categories based on the tasks each method accomplishes. A few methods are involved with the generation of the markup for the page; others are helper methods to build the page and manage the constituent controls. Finally, a third group collects all the methods related to client-side scripting.

Rendering Methods

Table 5-11 details the methods that are directly or indirectly involved with the generation of the markup code.

Table 5-11. Methods for Markup Generation

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.

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 that 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.

In an ASP.NET page, no control can be placed outside a <form> tag with the runat attribute set to server. The VerifyRenderingInServerForm method is used by Web and HTML controls to ensure that they are rendered correctly. In theory, custom controls should call this method during the rendering phase. In many situations, the custom control embeds or derives an existing Web or HTML control that will make the check itself.

Not directly exposed by the Page class, but strictly related to it, is the GetWebResourceUrl method on the ClientScriptManager class. (You get a reference to the current client script manager through the ClientScript property on Page.) When you develop a custom control, you often need to embed static resources such as images or client script files. You can make these files be separate downloads; however, even though it’s effective, the solution looks poor and inelegant. Visual Studio allows you to embed resources in the control assembly, but how would you retrieve these resources programmatically and bind them to the control? For example, to bind an assembly-stored image to an <IMG> tag, you need a URL for the image. The GetWebResourceUrl method returns a URL for the specified resource. The URL refers to a new Web Resource service (webresource.axd) that retrieves and returns the requested resource from an assembly.

// Bind the <IMG> tag to the given GIF image in the control's assembly
img.ImageUrl = Page.GetWebResourceUrl(typeof(TheControl), GifName));

GetWebResourceUrl requires a Type object, which will be used to locate the assembly that contains the resource. The assembly is identified with the assembly that contains the definition of the specified type in the current AppDomain. If you’re writing a custom control, the type will likely be the control’s type. As its second argument, the GetWebResourceUrl method requires the name of the embedded resource. The returned URL takes the following form:

WebResource.axd?a=assembly&r=resourceName&t=timestamp

The timestamp value is the current timestamp of the assembly, and it is added to make the browser download resources again if the assembly is modified.

Controls-Related Methods

Table 5-12 details a bunch of helper methods on the Page class architected to let you manage and validate child controls and resolve URLs.

Table 5-12. Helper Methods of the Page Object

Method

Description

DesignerInitialize

Initializes the instance of the Page class at design time, when the page is being hosted by RAD designers such as Visual Studio.

FindControl

Takes a control’s ID and searches for it in the page’s naming container. The search doesn’t dig out child controls that are naming containers themselves.

GetTypeHashCode

Retrieves the hash code generated by ASP.xxx_aspx page objects at run time. In the base Page class, the method implementation simply returns 0; significant numbers are returned by classes used for actual pages.

GetValidators

Returns a collection of control validators for a specified validation group.

HasControls

Determines whether the page contains any child controls.

LoadControl

Compiles and loads a user control from an .ascx file, and returns a Control object. If the user control supports caching, the object returned is PartialCachingControl.

LoadTemplate

Compiles and loads a user control from an .ascx file, and returns it wrapped in an instance of an internal class that implements the ITemplate interface. The internal class is named SimpleTemplate.

MapPath

Retrieves the physical, fully qualified path that an absolute or relative virtual path maps to.

ParseControl

Parses a well-formed input string, and returns an instance of the control that corresponds to the specified markup text. If the string contains more controls, only the first is taken into account. The runat attribute can be omitted. The method returns an object of type Control and must be cast to a more specific type.

RegisterRequiresControlState

Registers a control as one that requires control state.

RegisterRequiresPostBack

Registers the specified control to receive a postback handling notice, even if its ID doesn’t match any ID in the collection of posted data. The control must implement the IPostBackDataHandler interface.

RegisterRequiresRaiseEvent

Registers the specified control to handle an incoming postback event. The control must implement the IPostBackEventHandler interface.

RegisterViewStateHandler

Mostly for internal use, the method sets an internal flag that causes the page view state to be persisted. If this method is not called in the prerendering phase, no view state will ever be written. Typically, only the HtmlForm server control for the page calls this method. There’s no need to call it from within user applications.

ResolveUrl

Resolves a relative URL into an absolute URL based on the value of the TemplateSourceDirectory property.

Validate

Instructs any validation controls included in the page to validate their assigned information. If defined in the page, the method honors ASP.NET validation groups.

The methods LoadControl and LoadTemplate share a common code infrastructure but return different objects, as the following pseudocode shows:

public Control LoadControl(string virtualPath)
{
    Control ascx = GetCompiledUserControlType(virtualPath);
    ascx.InitializeAsUserControl();
    return ascx;
}
public ITemplate LoadTemplate(string virtualPath)
{
    Control ascx = GetCompiledUserControlType(virtualPath);
    return new SimpleTemplate(ascx);
}

Both methods differ from the ParseControl method in that the latter never causes compilation but simply parses the string and infers control information. The information 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 information useful to instantiate a control, and for this reason it can be omitted from the strings you pass directly to ParseControl.

Script-Related Methods

Table 5-13 enumerates all the methods in the Page class related to HTML and script code to be inserted in the client page.

Table 5-13. Script-Related Methods

Method

Description

GetCallbackEventReference

Obtains a reference to a client-side function that, when invoked, initiates a client callback to server-side events.

GetPostBackClientEvent

Calls into GetCallbackEventReference.

GetPostBackClientHyperlink

Appends javascript: to the beginning of the return string received from GetPostBackEventReference. For example:

javascript:__doPostBack(‘CtllD’,’’)

GetPostBackEventReference

Returns the prototype of the client-side script function 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 registered with the page. It’s marked as obsolete.

IsStartupScriptRegistered

Determines whether the specified client startup script is registered with the page. It’s marked as obsolete.

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’);

It’s marked as obsolete.

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. It’s marked as obsolete.

RegisterHiddenField

Use this method to automatically register a hidden field on the page. It’s marked as obsolete.

RegisterOnSubmitStatement

Use this method to emit client script code that handles the client OnSubmit event. The script should be a JavaScript function call to client code registered elsewhere. It’s marked as obsolete.

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. It’s marked as obsolete.

SetFocus

Sets the browser focus to the specified control.

As you can see, some methods in Table 5-13, which are defined and usable in ASP.NET 1.x, are marked as obsolete. In ASP.NET 4 applications, you should avoid calling them and resort to methods with the same name exposed out of the ClientScript property.

// Avoid this in ASP.NET 4
Page.RegisterArrayDeclaration(...);

// Use this in ASP.NET 4
Page.ClientScript.RegisterArrayDeclaration(...);

The ClientScript property returns an instance of the ClientScriptManager class and represents the central console for registering script code to be programmatically emitted within the page.

Methods listed in Table 5-13 let you emit JavaScript code in the client page. 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.

Events of the Page Class

The Page class fires a few events that are notified during the page life cycle. As Table 5-14 shows, some events are orthogonal to the typical life cycle of a page (initialization, postback, and rendering phases) and are fired as extra-page situations evolve. Let’s briefly review the events and then attack the topic with an in-depth discussion of the page life cycle.

Table 5-14. Events a Page Can Fire

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

InitComplete

Occurs when all child controls and the page have been initialized

Load

Occurs when the page loads up, after being initialized

LoadComplete

Occurs when the loading of the page is completed and server events have been raised

PreInit

Occurs just before the initialization phase of the page begins

PreLoad

Occurs just before the loading phase of the page begins

PreRender

Occurs when the page is about to render

PreRenderComplete

Occurs just before the pre-rendering phase begins

SaveStateComplete

Occurs when the view state of the page has been saved to the persistence medium

Unload

Occurs when the page is unloaded from memory but not yet disposed of

The Eventing Model

When a page is requested, its class and the server controls it contains are responsible for executing the request and rendering HTML back to the client. The communication between the client and the server is stateless and disconnected because it’s based on the HTTP protocol. Real-world applications, though, need some state to be maintained between successive calls made to the same page. With ASP, and with other server-side development platforms such as Java Server Pages and PHP, the programmer is entirely responsible for persisting the state. In contrast, ASP.NET provides a built-in infrastructure that saves and restores the state of a page in a transparent manner. In this way, and in spite of the underlying stateless protocol, the client experience appears to be that of a continuously executing process. It’s just an illusion, though.

Introducing the View State

The illusion of continuity is created by the view state feature of ASP.NET pages and is based on some assumptions about how the page is designed and works. Also, server-side Web controls play a remarkable role. In brief, before rendering its contents to HTML, the page encodes and stuffs into a persistence medium (typically, a hidden field) all the state information that the page itself and its constituent controls want to save. When the page posts back, the state information is deserialized from the hidden field and used to initialize instances of the server controls declared in the page layout.

The view state is specific to each instance of the page because it is embedded in the HTML. The net effect of this is that controls are initialized with the same values they had the last time the view state was created—that is, the last time the page was rendered to the client. Furthermore, an additional step in the page life cycle merges the persisted state with any updates introduced by client-side actions. When the page executes after a postback, it finds a stateful and up-to-date context just as it is working over a continuous point-to-point connection.

Two basic assumptions are made. The first assumption is that the page always posts to itself and carries its state back and forth. The second assumption is that the server-side controls have to be declared with the runat=server attribute to spring to life when the page posts back.

The Single Form Model

ASP.NET pages are built to support exactly one server-side <form> tag. The form must include all the controls you want to interact with on the server. Both the form and the controls must be marked with the runat attribute; otherwise, they will be considered 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 reason 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.

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. We’ll return to this topic in Chapter 9.

Asynchronous Pages

ASP.NET pages are served by an HTTP handler like an instance of the Page class. Each request takes up a thread in the ASP.NET thread pool and releases it only when the request completes. What if a frequently requested page starts an external and particularly lengthy task? The risk is that the ASP.NET process is idle but has no free threads in the pool to serve incoming requests for other pages. This happens mostly because HTTP handlers, including page classes, work synchronously. To alleviate this issue, ASP.NET has supported asynchronous handlers since version 1.0 through the IHTTPAsyncHandler interface. Starting with ASP.NET 2.0, creating asynchronous pages was made easier thanks to specific support from the framework.

Two aspects characterize an asynchronous ASP.NET page: a tailor-made attribute on the @Page directive, and one or more tasks registered for asynchronous execution. The asynchronous task can be registered in either of two ways. You can define a Begin/End pair of asynchronous handlers for the PreRenderComplete event or create a PageAsyncTask object to represent an asynchronous task. This is generally done in the Page_Load event, but any time is fine provided that it happens before the PreRender event fires.

In both cases, the asynchronous task is started automatically when the page has progressed to a well-known point. Let’s dig out more details.

Note

An ASP.NET asynchronous page is still a class that derives from Page. There are no special base classes to inherit for building asynchronous pages.

The Async Attribute

The new Async attribute on the @Page directive accepts a Boolean value to enable or disable asynchronous processing. The default value is false.

<%@ Page Async="true" ... %>

The Async attribute is merely a message for the page parser. When used, the page parser implements the IHttpAsyncHandler interface in the dynamically generated class for the .aspx resource. The Async attribute enables the page to register asynchronous handlers for the PreRenderComplete event. No additional code is executed at run time as a result of the attribute.

Let’s consider a request for a TestAsync.aspx page marked with the Async directive attribute. The dynamically created class, named ASP.TestAsync_aspx, is declared as follows:

public class TestAsync_aspx : TestAsync, IHttpHandler, IHttpAsyncHandler
{
   ...
}

TestAsync is the code file class and inherits from Page or a class that in turn inherits from Page. IHttpAsyncHandler is the canonical interface that has been used for serving resources asynchronously since ASP.NET 1.0.

The AddOnPreRenderCompleteAsync Method

The AddOnPreRenderCompleteAsync method adds an asynchronous event handler for the page’s PreRenderComplete event. An asynchronous event handler consists of a Begin/End pair of event handler methods, as shown here:

AddOnPreRenderCompleteAsync (
    new BeginEventHandler(BeginTask),
    new EndEventHandler(EndTask)
);

The call can be simplified as follows:

AddOnPreRenderCompleteAsync(BeginTask, EndTask);

BeginEventHandler and EndEventHandler are delegates defined as follows:

IAsyncResult BeginEventHandler(
    object sender,
    EventArgs e,
    AsyncCallback cb,
    object state)
void EndEventHandler(
    IAsyncResult ar)

In the code file, you place a call to AddOnPreRenderCompleteAsync as soon as you can, and always earlier than the PreRender event can occur. A good place is usually the Page_Load event. Next, you define the two asynchronous event handlers.

The Begin handler is responsible for starting any operation you fear can block the underlying thread for too long. The handler is expected to return an IAsyncResult object to describe the state of the asynchronous task. When the lengthy task has completed, the End handler finalizes the original request and updates the page’s user interface and controls. Note that you don’t necessarily have to create your own object that implements the IAsyncResult interface. In most cases, in fact, to start lengthy operations you just use built-in classes that already implement the asynchronous pattern and provide IAsyncResult ready-made objects.

The page progresses up to entering the PreRenderComplete stage. You have a pair of asynchronous event handlers defined here. The page executes the Begin event, starts the lengthy operation, and is then suspended until the operation terminates. When the work has been completed, the HTTP runtime processes the request again. This time, though, the request processing begins at a later stage than usual. In particular, it begins exactly where it left off—that is, from the PreRenderComplete stage. The End event executes, and the page finally completes the rest of its life cycle, including view-state storage, markup generation, and unloading.

Important

The Begin and End event handlers are called at different times and generally on different pooled threads. In between the two methods calls, the lengthy operation takes place. From the ASP.NET runtime perspective, the Begin and End events are similar to serving distinct requests for the same page. It’s as if an asynchronous request is split in two distinct steps: a Begin step and End step. Each request is always served by a pooled thread. Typically, the Begin and End steps are served by threads picked up from the ASP.NET thread pool. The lengthy operation, instead, is not managed by ASP.NET directly and doesn’t involve any of the pooled threads. The lengthy operation is typically served by a thread selected from the operating system completion thread pool.

The Significance of PreRenderComplete

So an asynchronous page executes up until the PreRenderComplete stage is reached and then blocks while waiting for the requested operation to complete asynchronously. When the operation is finally accomplished, the page execution resumes from the PreRenderComplete stage. A good question to ask would be the following: “Why PreRenderComplete?” What makes PreRenderComplete such a special event?

By design, in ASP.NET there’s a single unwind point for asynchronous operations (also familiarly known as the async point). This point is located between the PreRender and PreRenderComplete events. When the page receives the PreRender event, the async point hasn’t been reached yet. When the page receives PreRenderComplete, the async point has passed.

Building a Sample Asynchronous Page

Let’s roll a first asynchronous test page to download and process some RSS feeds. The page markup is quite simple indeed:

<%@ Page Async="true" Language="C#" AutoEventWireup="true"
         CodeFile="TestAsync.aspx.cs" Inherits="TestAsync" %>
<html>
<body>
    <form id="form1" runat="server">
        <% = RssData %>
    </form>
</body>
</html>

The code file is shown next, and it attempts to download the RSS feed from my personal blog:

public partial class TestAsync : System.Web.UI.Page
{
    const String RSSFEED = "http://weblogs.asp.net/despos/rss.aspx";
    private WebRequest req;

    public String RssData { get; set; }

    void Page_Load (Object sender, EventArgs e)
    {
        AddOnPreRenderCompleteAsync(BeginTask, EndTask);
    }

    IAsyncResult BeginTask(Object sender,
                           EventArgs e, AsyncCallback cb, Object state)
    {
        // Trace
        Trace.Warn("Begin async: Thread=" +
                    Thread.CurrentThread.ManagedThreadId.ToString());

        // Prepare to make a Web request for the RSS feed
        req = WebRequest.Create(RSSFEED);

        // Begin the operation and return an IAsyncResult object
        return req.BeginGetResponse(cb, state);
    }

    void EndTask(IAsyncResult ar)
    {
        // This code will be called on a(nother) pooled thread

        using (var response = req.EndGetResponse(ar))
        {
            String text;
            using (var reader = new StreamReader(response.GetResponseStream()))
            {
                text = reader.ReadToEnd();
            }

            // Process the RSS data
            rssData = ProcessFeed(text);
        }

        // Trace
        Trace.Warn("End async: Thread=" +
                    Thread.CurrentThread.ManagedThreadId.ToString());

        // The page is updated using an ASP-style code block in the ASPX
        // source that displays the contents of the rssData variable
    }

    String ProcessFeed(String feed)
    {
        // Build the page output from the XML input
        ...
    }
}

As you can see, such an asynchronous page differs from a standard one only for the aforementioned elements—the Async directive attribute and the pair of asynchronous event handlers. Figure 5-4 shows the sample page in action.

A sample asynchronous page downloading links from a blog.

Figure 5-4. A sample asynchronous page downloading links from a blog.

It would also be interesting to take a look at the messages traced by the page. Figure 5-5 provides visual clues of it. The Begin and End stages are served by different threads and take place at different times.

Note the time elapsed between the Exit BeginTask and Enter EndTask stages. It is much longer than intervals between any other two consecutive operations. It’s in that interval that the lengthy operation—in this case, downloading and processing the RSS feed—took place. The interval also includes the time spent to pick up another thread from the pool to serve the second part of the original request.

The traced request details clearly show the two steps needed to process a request asynchronously.

Figure 5-5. The traced request details clearly show the two steps needed to process a request asynchronously.

The RegisterAsyncTask Method

The AddOnPreRenderCompleteAsync method is not the only tool you have to register an asynchronous task. The RegisterAsyncTask method is, in most cases, an even better solution. RegisterAsyncTask is a void method and accepts a PageAsyncTask object. As the name suggests, the PageAsyncTask class represents a task to execute asynchronously.

The following code shows how to rework the sample page that reads some RSS feed and make it use the RegisterAsyncTask method:

void Page_Load (object sender, EventArgs e)
{
    PageAsyncTask task = new PageAsyncTask(
        new BeginEventHandler(BeginTask),
        new EndEventHandler(EndTask),
        null,
        null);
    RegisterAsyncTask(task);
}

The constructor accepts up to five parameters, as shown in the following code:

public PageAsyncTask(
     BeginEventHandler beginHandler,
     EndEventHandler endHandler,
     EndEventHandler timeoutHandler,
     object state,
     bool executeInParallel)

The beginHandler and endHandler parameters have the same prototype as the corresponding handlers you use for the AddOnPreRenderCompleteAsync method. Compared to the AddOnPreRenderCompleteAsync method, PageAsyncTask lets you specify a timeout function and an optional flag to enable multiple registered tasks to execute in parallel.

The timeout delegate indicates the method that will get called if the task is not completed within the asynchronous timeout interval. By default, an asynchronous task times out if it’s not completed within 45 seconds. You can indicate a different timeout in either the configuration file or the @Page directive. Here’s what you need if you opt for the web.config file:

<system.web>
    <pages asyncTimeout="30" />
</system.web>

The @Page directive contains an integer AsyncTimeout attribute that you set to the desired number of seconds.

Just as with the AddOnPreRenderCompleteAsync method, you can pass some state to the delegates performing the task. The state parameter can be any object.

The execution of all tasks registered is automatically started by the Page class code just before the async point is reached. However, by placing a call to the ExecuteRegisteredAsyncTasks method on the Page class, you can take control of this aspect.

Choosing the Right Approach

When should you use AddOnPreRenderCompleteAsync, and when is RegisterAsyncTask a better option? Functionally speaking, the two approaches are nearly identical. In both cases, the execution of the request is split in two parts: before and after the async point. So where’s the difference?

The first difference is logical. RegisterAsyncTask is an API designed to run tasks asynchronously from within a page—and not just asynchronous pages with Async=true. AddOnPreRenderCompleteAsync is an API specifically designed for asynchronous pages. That said, a couple of further differences exist.

One is that RegisterAsyncTask executes the End handler on a thread with a richer context than AddOnPreRenderCompleteAsync. The thread context includes impersonation and HTTP context information that is missing in the thread serving the End handler of a classic asynchronous page. In addition, RegisterAsyncTask allows you to set a timeout to ensure that any task doesn’t run for more than a given number of seconds.

The other difference is that RegisterAsyncTask makes the implementation of multiple calls to remote sources significantly easier. You can have parallel execution by simply setting a Boolean flag, and you don’t need to create and manage your own IAsyncResult object.

The bottom line is that you can use either approach for a single task, but you should opt for RegisterAsyncTask when you have multiple tasks to execute simultaneously.

Async-Compliant Operations

Which required operations force, or at least strongly suggest, the adoption of an asynchronous page? Any operation can be roughly labeled in either of two ways: CPU bound or I/O bound. CPU bound indicates an operation whose completion time is mostly determined by the speed of the processor and amount of available memory. I/O bound indicates the opposite situation, where the CPU mostly waits for other devices to terminate.

The need for asynchronous processing arises when an excessive amount of time is spent getting data in and out of the computer in relation to the time spent processing it. In such situations, the CPU is idle or underused and spends most of its time waiting for something to happen. In particular, I/O-bound operations in the context of ASP.NET applications are even more harmful because serving threads are blocked too, and the pool of serving threads is a finite and critical resource. You get real performance advantages if you use the asynchronous model on I/O-bound operations.

Typical examples of I/O-bound operations are all operations that require access to some sort of remote resource or interaction with external hardware devices. Operations on non-local databases and non-local Web service calls are the most common I/O-bound operations for which you should seriously consider building asynchronous pages.

Important

Asynchronous operations exist to speed up lengthy operations, but the benefits they provide are entirely enjoyed on the server side. There’s no benefit for the end user in adopting asynchronous solutions. The “time to first byte” doesn’t change for the user in a synchronous or asynchronous scenario. Using AJAX solutions would give you at least the means to (easily) display temporary messages to provide information about the progress. However, if it’s not coded asynchronously on the server, any lengthy operation that goes via AJAX is more harmful for the system than a slow-but-asynchronous classic Web Forms page.

The Page Life Cycle

A page instance is created on every request from the client, and its execution causes itself and its contained controls to iterate through their life-cycle stages. Page execution begins when the HTTP runtime invokes ProcessRequest, which kicks off the page and control life cycles. The life cycle consists of a sequence of stages and steps. Some of these stages can be controlled through user-code events; some require a method override. Some other stages—or more exactly, substages—are just not public, are out of the developer’s control, and are mentioned here mostly for completeness.

The page life cycle is articulated in three main stages: setup, postback, and finalization. Each stage might have one or more substages and is composed of one or more steps and points where events are raised. The life cycle as described here includes all possible paths. Note that there are modifications to the process depending upon cross-page posts, script callbacks, and postbacks.

Page Setup

When the HTTP runtime instantiates the page class to serve the current request, the page constructor builds a tree of controls. The tree of controls ties into the actual class that the page parser created after looking at the ASPX source. Note that when the request processing begins, all child controls and page intrinsics—such as HTTP context, request objects, and response objects—are set.

The very first step in the page lifetime is determining why the run time is processing the page request. There are various possible reasons: a normal request, postback, cross-page postback, or callback. The page object configures its internal state based on the actual reason, and it prepares the collection of posted values (if any) based on the method of the request—either GET or POST. After this first step, the page is ready to fire events to the user code.

The PreInit Event

This event is the entry point in the page life cycle. When the event fires, no master page or theme has been associated with the page as yet. Furthermore, the page scroll position has been restored, posted data is available, and all page controls have been instantiated and default to the properties values defined in the ASPX source. (Note that at this time controls have no ID, unless it is explicitly set in the .aspx source.) Changing the master page or the theme programmatically is possible only at this time. This event is available only on the page. IsCallback, IsCrossPagePostback, and IsPostback are set at this time.

The Init Event

The master page, if one exists, and the theme have been set and can’t be changed anymore. The page processor—that is, the ProcessRequest method on the Page class—proceeds and iterates over all child controls to give them a chance to initialize their state in a context-sensitive way. All child controls have their OnInit method invoked recursively. For each control in the control collection, the naming container and a specific ID are set, if not assigned in the source.

The Init event reaches child controls first and the page later. At this stage, the page and controls typically begin loading some parts of their state. At this time, the view state is not restored yet.

The InitComplete Event

Introduced with ASP.NET 2.0, this page-only event signals the end of the initialization substage. For a page, only one operation takes place in between the Init and InitComplete events: tracking of view-state changes is turned on. Tracking view state is the operation that ultimately enables controls to really persist in the storage medium any values that are programmatically added to the ViewState collection. Simply put, for controls not tracking their view state, any values added to their ViewState are lost across postbacks.

All controls turn on view-state tracking immediately after raising their Init event, and the page is no exception. (After all, isn’t the page just a control?)

Important

In light of the previous statement, note that any value written to the ViewState collection before InitComplete won’t be available on the next postback.

View-State Restoration

If the page is being processed because of a postback—that is, if the IsPostBack property is true—the contents of the __VIEWSTATE hidden field is restored. The __VIEWSTATE hidden field is where the view state of all controls is persisted at the end of a request. The overall view state of the page is a sort of call context and contains the state of each constituent control the last time the page was served to the browser.

At this stage, each control is given a chance to update its current state to make it identical to what it was on last request. There’s no event to wire up to handle the view-state restoration. If something needs be customized here, you have to resort to overriding the LoadViewState method, defined as protected and virtual on the Control class.

Processing Posted Data

All the client data packed in the HTTP request—that is, the contents of all input fields defined with the <form> tag—are processed at this time. Posted data usually takes the following form:

TextBox1=text&DropDownList1=selectedItem&Button1=Submit

It’s an &-separated string of name/value pairs. These values are loaded into an internal-use collection. The page processor attempts to find a match between names in the posted collection and ID of controls in the page. Whenever a match is found, the processor checks whether the server control implements the IPostBackDataHandler interface. If it does, the methods of the interface are invoked to give the control a chance to refresh its state in light of the posted data. In particular, the page processor invokes the LoadPostData method on the interface. If the method returns true—that is, the state has been updated—the control is added to a separate collection to receive further attention later.

If a posted name doesn’t match any server controls, it is left over and temporarily parked in a separate collection, ready for a second try later.

Note

As mentioned, during the processing of posted data, posted names are matched against the ID of controls in the page. Which ID? Is it the ClientID property, or rather, is it the UniqueID property? Posted names are matched against the unique ID of page controls. Client IDs are irrelevant in this instance because they are not posted back to the server.

The PreLoad Event

The PreLoad event merely indicates that the page has terminated the system-level initialization phase and is going to enter the phase that gives user code in the page a chance to further configure the page for execution and rendering. This event is raised only for pages.

The Load Event

The Load event is raised for the page first and then recursively for all child controls. At this time, controls in the page tree are created and their state fully reflects both the previous state and any data posted from the client. The page is ready to execute any initialization code related to the logic and behavior of the page. At this time, access to control properties and view state is absolutely safe.

Handling Dynamically Created Controls

When all controls in the page have been given a chance to complete their initialization before display, the page processor makes a second try on posted values that haven’t been matched to existing controls. The behavior described earlier in the Processing Posted Data section is repeated on the name/value pairs that were left over previously. This apparently weird approach addresses a specific scenario—the use of dynamically created controls.

Imagine adding a control to the page tree dynamically—for example, in response to a certain user action. As mentioned, the page is rebuilt from scratch after each postback, so any information about the dynamically created control is lost. On the other hand, when the page’s form is submitted, the dynamic control there is filled with legal and valid information that is regularly posted. By design, there can’t be any server control to match the ID of the dynamic control the first time posted data is processed. However, the ASP.NET framework recognizes that some controls could be created in the Load event. For this reason, it makes sense to give it a second try to see whether a match is possible after the user code has run for a while.

If the dynamic control has been re-created in the Load event, a match is now possible and the control can refresh its state with posted data.

Handling the Postback

The postback mechanism is the heart of ASP.NET programming. It consists of posting form data to the same page using the view state to restore the call context—that is, the same state of controls existing when the posting page was last generated on the server.

After the page has been initialized and posted values have been taken into account, it’s about time that some server-side events occur. There are two main types of events. The first type of event signals that certain controls had the state changed over the postback. The second type of event executes server code in response to the client action that caused the post.

Detecting Control State Changes

The whole ASP.NET machinery works around an implicit assumption: there must be a one-to-one correspondence between some HTML input tags that operate in the browser and some other ASP.NET controls that live and thrive in the Web server. The canonical example of this correspondence is between <input type=“text”> and TextBox controls. To be more technically precise, the link is given by a common ID name. When the user types some new text into an input element and then posts it, the corresponding TextBox control—that is, a server control with the same ID as the input tag—is called to handle the posted value. I described this step in the Processing Posted Data section earlier in the chapter.

For all controls that had the LoadPostData method return true, it’s now time to execute the second method of the IPostBackDataHandler interface: the RaisePostDataChangedEvent method. The method signals the control to notify the ASP.NET application that the state of the control has changed. The implementation of the method is up to each control. However, most controls do the same thing: raise a server event and give page authors a way to kick in and execute code to handle the situation. For example, if the Text property of a TextBox changes over a postback, the TextBox raises the TextChanged event to the host page.

Executing the Server-Side Postback Event

Any page postback starts with some client action that intends to trigger a server-side action. For example, clicking a client button posts the current contents of the displayed form to the server, thus requiring some action and a new, refreshed page output. The client button control—typically, a hyperlink or a submit button—is associated with a server control that implements the IPostBackEventHandler interface.

The page processor looks at the posted data and determines the control that caused the postback. If this control implements the IPostBackEventHandler interface, the processor invokes the RaisePostBackEvent method. The implementation of this method is left to the control and can vary quite a bit, at least in theory. In practice, though, any posting control raises a server event letting page authors write code in response to the postback. For example, the Button control raises the onclick event.

There are two ways a page can post back to the server—by using a submit button (that is, <input type=“submit”>) or through script. A submit HTML button is generated through the Button server control. The LinkButton control, along with a few other postback controls, inserts some script code in the client page to bind an HTML event (for example, onclick) to the form’s submit method in the browser’s HTML object model. We’ll return to this topic in the next chapter.

Note

The UseSubmitBehavior property exists on the Button class to let page developers control the client behavior of the corresponding HTML element as far as form submission is concerned. By default, a Button control behaves like a submit button. By setting UseSubmitBehavior to false, you change the output to <input type=“button”>, but at the same time the onclick property of the client element is bound to predefined script code that just posts back. In the end, the output of a Button control remains a piece of markup that ultimately posts back; through UseSubmitBehavior, you can gain some more control over that.

The LoadComplete Event

The page-only LoadComplete event signals the end of the page-preparation phase. Note that no child controls will ever receive this event. After firing LoadComplete, the page enters its rendering stage.

Page Finalization

After handling the postback event, the page is ready for generating the output for the browser. The rendering stage is divided in two parts: pre-rendering and markup generation. The pre-rendering substage is in turn characterized by two events for pre-processing and post-processing.

The PreRender Event

By handling this event, pages and controls can perform any updates before the output is rendered. The PreRender event fires for the page first and then recursively for all controls. Note that at this time the page ensures that all child controls are created. This step is important, especially for composite controls.

The PreRenderComplete Event

Because the PreRender event is recursively fired for all child controls, there’s no way for the page author to know when the pre-rendering phase has been completed. For this reason, ASP.NET supports an extra event raised only for the page. This event is PreRenderComplete.

The SaveStateComplete Event

The next step before each control is rendered out to generate the markup for the page is saving the current state of the page to the view-state storage medium. Note that every action taken after this point that modifies the state could affect the rendering, but it is not persisted and won’t be retrieved on the next postback. Saving the page state is a recursive process in which the page processor walks its way through the whole page tree, calling the SaveViewState method on constituent controls and the page itself. SaveViewState is a protected and virtual (that is, overridable) method that is responsible for persisting the content of the ViewState dictionary for the current control. (We’ll come back to the ViewState dictionary in Chapter 19.)

ASP.NET server controls can provide a second type of state, known as a “control state.” A control state is a sort of private view state that is not subject to the application’s control. In other words, the control state of a control can’t be programmatically disabled, as is the case with the view state. The control state is persisted at this time, too. Control state is another state storage mechanism whose contents are maintained across page postbacks much like the view state, but the purpose of the control state is to maintain necessary information for a control to function properly. That is, state behavior property data for a control should be kept in the control state, while user interface property data (such as the control’s contents) should be kept in the view state.

The SaveStateComplete event occurs when the state of controls on the page have been completely saved to the persistence medium.

Note

The view state of the page and all individual controls is accumulated in a unique memory structure and then persisted to storage medium. By default, the persistence medium is a hidden field named __VIEWSTATE. Serialization to, and deserialization from, the persistence medium is handled through a couple of overridable methods on the Page class: SavePageStateToPersistenceMedium and LoadPageStateFromPersistenceMedium. For example, by overriding these two methods you can persist the page state in a server-side database or in the session state, dramatically reducing the size of the page served to the user. Hold on, though. This option is not free of issues, and we’ll talk more about it in Chapter 19.

Generating the Markup

The generation of the markup for the browser is obtained by calling each constituent control to render its own markup, which will be accumulated in a buffer. Several overridable methods allow control developers to intervene in various steps during the markup generation—begin tag, body, and end tag. No user event is associated with the rendering phase.

The Unload Event

The rendering phase is followed by a recursive call that raises the Unload event for each control, and finally for the page itself. The Unload event exists to perform any final cleanup before the page object is released. Typical operations are closing files and database connections.

Note that the unload notification arrives when the page or the control is being unloaded but has not been disposed of yet. Overriding the Dispose method of the Page class—or more simply, handling the page’s Disposed event—provides the last possibility for the actual page to perform final clean up before it is released from memory. The page processor frees the page object by calling the method Dispose. This occurs immediately after the recursive call to the handlers of the Unload event has completed.

Summary

ASP.NET is a complex technology built on top of a substantially thick—and, fortunately, solid and stable—Web infrastructure. To provide highly improved performance and a richer programming toolset, ASP.NET builds a desktop-like abstraction model, but it still has to rely on HTTP and HTML to hit the target and meet end-user expectations.

It is exactly this thick abstraction layer that has been responsible for the success of Web Forms for years, but it’s being questioned these days as ASP.NET MVC gains acceptance and prime-time use. A thick abstraction layer makes programming quicker and easier, but it necessarily takes some control away from developers. This is not necessarily a problem, but its impact depends on the particular scenario you are considering.

There are two relevant aspects in the ASP.NET Web Forms model: the process model and the page object model. Each request of a URL that ends with .aspx is assigned to an application object working within the CLR hosted by the worker process. The request results in a dynamically compiled class that is then instantiated and put to work. The Page class is the base class for all ASP.NET pages. An instance of this class runs behind any URL that ends with .aspx. In most cases, you won’t just build your ASP.NET pages from the Page class directly, but you’ll rely on derived classes that contain event handlers and helper methods, at the very minimum. These classes are known as code-behind classes.

The class that represents the page in action implements the ASP.NET eventing model based on two pillars: the single form model (page reentrancy) and server controls. The page life cycle, fully described in this chapter, details the various stages (and related substages) a page passes through on the way to generate the markup for the browser. A deep understanding of the page life cycle and eventing model is key to diagnosing possible problems and implementing advanced features quickly and efficiently.

In this chapter, I mentioned controls several times. Server controls are components that get input from the user, process the input, and output a response as HTML. In the next chapter, we’ll explore the internal architecture of server controls and other working aspects of Web Forms pages.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.101.72