Web Forms Internals

Overview

Few things are harder to put up with than the annoyance of a good example.
-Mark Twain

ASP.NET pages are dynamically compiled on demand when first required in the context of a Web application. Dynamic compilation is not specific to ASP.NET pages (.aspx files); it also occurs with Web Services (.asmx files), Web user controls (.ascx files), HTTP handlers (.ashx files), and ASP.NET application files such as the global.asax file. But what does it mean exactly that an ASP.NET page is compiled? How does the ASP.NET runtime turn the source code of an .aspx file into a .NET Framework compilable class? And what becomes of the dynamically created assembly when the associated source file gets updated? And finally, what happens once a compiled assembly has been associated with the requested .aspx URL?

Don't be too surprised to find all these questions crowding your mind at this time. Their presence indicates you're on the right track and ready to learn more about the underpinnings of the Web Forms programming model.


Executing ASP NET Pages

The expression compiled page is at once precise as well as vague and generic. It is precise because it tells you exactly what happens when a URL with an .aspx extension is requested. It is vague because it doesn't specify which module launches and controls the compiler and what actual input the compiler receives on the command line. Finally, it is generic because it omits a fair number of details.

In this chapter, we simply aim to unveil all the mysteries fluttering around the dynamic compilation of ASP.NET pages. We'll do this by considering the actions performed on the Web server, and which modules perform them, when a request arrives for an .aspx page.

  Note

Much of the content of this chapter is based on the behavior of the ASP.NET runtime version 1.0 and version 1.1 with Internet Information Services (IIS) 5.0 serving as the Web server. Some key differences apply when using Windows Server 2003 and IIS 6.0. Any significant differences that affect the ASP.NET way of working are noted. Throughout this chapter, IIS is always IIS 5.0 unless another version is explicitly mentioned.

The first part of this chapter discusses under-the-hood details that might not interest you, as they aren't strictly concerned with the development of ASP.NET applications. Reading this chapter in its entirety is not essential to understanding fundamental techniques of ASP.NET programming. So, if you want, you can jump directly to the "The Event Model" section, which is the section in which we discuss what happens once an ASP.NET page has been requested and starts being processed.

The IIS Resource Mappings

All resources you can access on an IIS-based Web server are grouped by their file extension. Any incoming request is then assigned to a particular run-time module for actual processing. Modules that can handle Web resources within the context of IIS are ISAPI extensions—that is, plain-old Win32 DLLs that expose, much like an interface, a bunch of API functions with predefined names and prototypes. IIS and ISAPI extensions use these DLL entries as a sort of private communication protocol. When IIS needs an ISAPI extension to accomplish a certain task, it simply loads the DLL and calls the appropriate function with valid arguments. Although the ISAPI documentation doesn't mention an ISAPI extension as an interface, it is just that—a module that implements a well-known programming interface.

When the request for a resource arrives, IIS first verifies the type of the resource. Static resources such as images, text files, HTML pages, and scriptless ASP pages are resolved directly by IIS without the involvement of external modules. IIS accesses the file on the local Web server and flushes its contents to the output console so that the requesting browser can get it. Resources that require server-side elaboration are passed on to the registered module. For example, ASP pages are processed by an ISAPI extension named asp.dll. In general, when the resource is associated with executable code, IIS hands the request to that executable for further processing. Files with an .aspx extension are assigned to an ISAPI extension named aspnet_isapi.dll, as shown in Figure 2-1.

click to expand
Figure 2-1: The IIS application mappings for resources with an .aspx extension.

Just like any other ISAPI extension, aspnet_isapi.dll is hosted by the IIS 5.0 process—the executable named inetinfo.exe. Resource mappings are stored in the IIS metabase. Upon installation, ASP.NET modifies the IIS metabase to make sure that aspnet_isapi.dll can handle all the resources identified by the extensions listed in Table 2-1.

Table 2-1: IIS Application Mappings for aspnet_isapi.dll

Extension

Resource Type

.asax

ASP.NET application files. The typical example is global.asax.

.ascx

Web user control files used to embed pagelets in ASP.NET pages.

.ashx

HTTP handlers, namely managed modules that interact with the low-level request and response services of IIS. (See Chapter 23.)

.asmx

Files that implement XML Web services.

.aspx

Files that represent ASP.NET pages.

.axd

Extension that identifies the ASP.NET trace-viewer application (trace.axd). When invoked in a virtual folder, the trace viewer displays trace information for every page in the application. (See Chapter 4.)

.rem

Fake resource used to qualify the Uniform Resource Identifier (URI) of a .NET Remoting object hosted by IIS.

.soap

Same as .rem.

In addition, the aspnet_isapi.dll extension handles other typical Microsoft Visual Studio .NET extensions such as .cs, .csproj, .vb, .vbproj, .licx, .config, .resx, .webinfo, and .vsdisco. Other extensions added with Visual Studio .NET 2003 for J# projects are .java, .jsl, .resources, .vjsproj.

The ASP.NET ISAPI extension doesn't process the .aspx file but acts as a dispatcher. It collects all the information available about the invoked URL and the underlying resource, and it routes the request toward another distinct process—the ASP.NET worker process.

The ASP NET Worker Process

The ASP.NET worker process represents the ASP.NET runtime environment. It consists of a Win32 unmanaged executable named aspnet_wp.exe, which hosts the .NET common language runtime (CLR). This process is the executable you need to attach to in order to debug ASP.NET applications. The ASP.NET worker process activates the HTTP pipeline that will actually process the page request. The HTTP pipeline is a collection of .NET Framework classes that take care of compiling the page assembly and instantiating the related page class.

The connection between aspnet_isapi.dll and aspnet_wp.exe is established through a named pipe—a Win32 mechanism for transferring data over process boundaries. As the name suggests, a named pipe works like a pipe: you enter data in one end, and the same data comes out the other end. Pipes can be established both to connect local processes and processes running on remote machines. Figure 2-2 illustrates the ASP.NET layer built on top of IIS.

click to expand
Figure 2-2: IIS receives page requests and forwards them to the ASP.NET runtime.

How the ASP.NET Runtime Works

A single copy of the worker process runs all the time and hosts all the active Web applications. The only exception to this situation is when you have a Web server with multiple CPUs. In this case, you can configure the ASP.NET runtime so that multiple worker processes run, one for each available CPU. A model in which multiple processes run on multiple CPUs in a single server machine is known as a Web garden and is controlled by attributes on the section in the machine.config file. (I'll cover ASP.NET configuration files in Chapter 12.)

When a single worker process is used by all CPUs and controls all Web applications, it doesn't necessarily mean that no process isolation is achieved. Each Web application is, in fact, identified with its virtual directory and belongs to a distinct application domain, commonly referred to as an AppDomain. A new AppDomain is created within the ASP.NET worker process whenever a client addresses a virtual directory for the first time. After creating the new AppDomain, the ASP.NET runtime loads all the needed assemblies and passes control to the HTTP pipeline to actually service the request.

If a client requests a page from an already running Web application, the ASP.NET runtime simply forwards the request to the existing AppDomain associated with that virtual directory. All the assemblies needed to process the page are now ready to use because they were compiled upon the first call. Figure 2-3 provides a more general view of the ASP.NET runtime.

click to expand
Figure 2-3: The ASP.NET runtime and the various AppDomains.

  Tip

To configure the ASP.NET runtime to work as a Web garden—that is, to have more worker processes running on multiple CPUs in the same physical server—open the machine.config file and locate the section. Next, you set the webGarden attribute to true (because it is false by default) and the cpuMask attribute to a bit mask in which each 1 identifies an available and affined CPU. If ASP.NET is running with IIS 6.0, you must use the IIS Manager to configure Web gardens. In this case, the settings in machine.config are ignored. If webGarden is false, the cpuMask setting is ignored and only one process is scheduled regardless of how many CPUs you have.

Note that the documentation available with version 1.0 of the .NET Framework is a bit confusing on this point. Documentation in version 1.1 is clear and correct.

Processes, AppDomains, and Threads

In .NET, executable code must be loaded into the CLR to be managed while running. To manage the application's code, the CLR must first obtain a pointer to an AppDomain. AppDomains are separate units of processing that the CLR recognizes in a running process. All .NET processes run at least one AppDomain—known as the default AppDomain—that gets created during the CLR initialization. An application can have additional AppDomains. Each AppDomain is independently configured and given personal settings, such as security settings, reference paths, and configuration files.

AppDomains are separated and isolated from one another in a way that resembles process separation in Win32. The CLR enforces isolation by preventing direct calls between objects living in different AppDomains. From the CPU perspective, AppDomains are much more lightweight than Win32 processes. The certainty that AppDomains run type-safe code allows the CLR to provide a level of isolation that's as strong as the process boundaries but more cost effective. Type-safe code cannot cause memory faults, which in Win32 were one of the reasons to have a physical separation between process-memory contexts. An AppDomain is a logical process and, as such, is more lightweight than a true process.

Managed code running in an AppDomain is carried out by a particular thread. However, threads and AppDomains are orthogonal entities in the sense that you can have several threads active during the execution of the AppDomain code, but a single thread is in no way tied to run only within the context of a given AppDomain.

The .NET Remoting API is as a tailor-made set of system services for accessing an object in an external AppDomain.

Process Recycling

The behavior and performance of the ASP.NET worker process is constantly monitored to catch any form of decay as soon as possible. Parameters used to evaluate the performance include the number of requests served and queued, the total life of the process, and the percentage of physical memory (60% by default) it can use.

The element of the machine.config file defines threshold values for all these parameters. The aspnet_isapi.dll checks the overall state of the current worker process before forwarding any request to it. If the process breaks one of these measures of good performance, a new worker process is started to serve the next request. The old process continues running as long as there are requests pending in its own queue. After that, when it ceases to be invoked, it goes into idle mode and is then shut down.

This automatic scavenging mechanism is known as process recycling and is one of the aspects that improve the overall robustness and efficiency of the ASP.NET platform. In this way, in fact, memory leaks and run-time anomalies are promptly detected and overcome.

Process Recycling in IIS 6.0

Process recycling is also a built-in feature of IIS 6.0 that all types of Web applications, including ASP.NET and ASP applications, automatically take advantage of. More often than not and in spite of the best efforts to properly build them, Web applications leak memory, are poorly coded, or have other run-time problems. For this reason, administrators will periodically encounter situations that necessitate rebooting or restarting a Web server.

Up to the release of IIS 6.0, restarting a Web site required interrupting the entire Web server. In IIS 6.0, all user code is handled by worker processes, which are completely isolated from the core Web server. Worker processes are periodically recycled according to the number of requests they served, the memory occupied, and the time elapsed since activation. Worker processes are also automatically shut down if they appear to hang or respond too slowly. An ad hoc module in IIS 6.0 takes care of replacing faulty processes with fresh new ones.

  Note

In IIS 6.0, you'll find many design and implementation features of ASP.NET that are enhanced and extended to all resources. Historically, Microsoft was originally developing IIS 6.0 and ASP.NET together. Microsoft split them into separate projects when a decision was made to ship an initial version of ASP.NET prior to shipping IIS with a new version of Windows. ASP.NET clearly needed to support older versions of IIS, so a parallel IIS 5.0 model for ASP.NET was also built. In that sense, the ASP.NET model for IIS 5.0 matured much more quickly and inspired a lot of features in the newest IIS 6.0 model. As a significantly different product, IIS 6.0 takes the essence of the ASP.NET innovations and re-architects them in a wider and more general context. As a result, specific features of ASP.NET (for example, output caching and process recycling) become features of the whole Web server infrastructure with IIS 6.0. Those features are available to all Web applications hosted by IIS 6.0, including ASP.NET applications. ASP.NET is designed to detect the version of IIS and adjust its way of working.

Configuring the ASP.NET Worker Process

The aspnet_isapi module controls the behavior of the ASP.NET worker process through a few parameters. Table 2-2 details the information that gets passed between the ASP.NET ISAPI extension and the ASP.NET worker process.

Table 2-2: Parameters of the ASP.NET Process

Parameter

Description

IIS-Process-ID

The process ID number of the parent IIS process.

This-Process-Unique-ID

A unique process ID used to identify the worker process in a Web garden configuration.

Number-of-Sync-Pipes

Number of pipes to listen to for information.

RPC_C_AUTHN_LEVEL_ XXX

Indicates the required level of authentication for DCOM security. Default is Connect.

RPC_C_IMP_LEVEL_XX X

Indicates the authentication level required for COM security. Default is Impersonate.

CPU-Mask

Bit mask indicating which CPUs are available for ASP.NET processes if the run time is configured to work as a Web garden.

Max-Worker-Threads

Maximum number of worker threads per CPU in the thread pool.

Max-IO-Threads

Maximum number of IO threads per CPU in the thread pool.

Default values for the arguments in Table 2-2 can be set by editing the attributes of the section in the machine.config file. (I'll cover the machine.config file in more detail in Chapter 12.)

These parameters instruct the process how to perform tasks that need to happen before the CLR is loaded. Setting COM security is just one such task, and that's why authentication-level values need to be passed to the ASP.NET worker process. What does ASP.NET have to do with COM security? Well, the CLR is actually exposed as a COM object. (Note that the CLR itself is not made of COM code, but the interface to the CLR is a COM object.)

Other parameters are the information needed to hook up the named pipes between the ISAPI extension and the worker process. The names for the pipes are generated randomly and have to be communicated. The worker process retrieves the names of the pipes by using the parent process ID (that is, the IIS process) and the number of pipes created.

  Note

All the system information needed to set up the ASP.NET worker process (that is, the contents of the machine.config file) is read by the aspnet_isapi.dll unmanaged code prior to spawning any instance of the worker process.

About the Web Garden Model

The This-Process-Unique-ID parameter is associated with Web garden support. When multiple worker processes are used in a Web garden scenario, the aspnet_isapi.dll needs to know which process it's dealing with. Any HTTP request posted to the pipe must address a precise target process, and this information must be written into the packet sent through the pipe. The typical way of identifying a process is by means of its process ID.

Unfortunately, though, aspnet_isapi.dll can't know the actual ID of the worker process being spawned because the ID won't be determined until the kernel is done with the CreateProcess API function. The following pseudocode demonstrates that the [process_id] argument of aspnet_wp.exe can't be the process ID of the same process being created!

// aspnet_isapi.dll uses this code to create a worker process
CreateProcess("aspnet_wp.exe", "[iis_id] [process_id] ...", ...);

For this reason, aspnet_isapi.dll generates a unique but fake process ID and uses that ID to uniquely identify each worker process running on a multiprocessor machine configured as a Web garden. In this way, the call we just saw is rewritten as follows:

// [This-Process-Unique-ID] is a unique GUID 
// generated by aspnet_isapi.dll
CreateProcess("aspnet_wp.exe", "[iis_id] [This-Process-Unique-ID] ...", ...);

The worker process caches the This-Process-Unique-ID argument and uses it to recognize which named-pipe messages it has to serve.

ASP.NET and the IIS 6.0 Process Model

IIS 6.0, which ships as a part of Windows Server 2003, implements its HTTP listener as a kernel-level module. As a result, all incoming requests are first managed by such a driver—http.sys—and in kernel mode. No third-party code ever interacts with the listener, and no user-mode crashes will ever affect the stability of IIS. The http.sys driver listens for requests and posts them to the request queue of the appropriate application pool. An application pool is a blanket term that identifies a worker process and a virtual directory. A module, known as the Web Administration Service (WAS), reads from the IIS metabase and instructs the http.sys driver to create as many request queues as there are application pools registered in the metabase.

So when a request arrives, the driver looks at the URL and queues the request to the corresponding application pool. The WAS is responsible for creating and administering the worker processes for the various pools. The IIS worker process is an executable named w3wp.exe, whose main purpose is extracting HTTP requests from the kernel-mode queue. The worker process hosts a core application handler DLL to actually process the request and load ISAPI extensions and filters as appropriate.

Looking at the diagram of ASP.NET applications in Figure 2-4, you can see the IIS 6.0 process model eliminates the need for aspnet_wp.exe. The w3wp.exe loads the aspnet_isapi.dll, and in turn, the ISAPI extension loads the CLR in the worker process and launches the pipeline. With IIS 6.0, ASP.NET is managed by IIS and no longer concerns itself with things like process recycling, Web gardening, and isolation from the Web server.

click to expand
Figure 2-4: How Web applications are processed in IIS 6.0.

In summary, in the IIS 6.0 process model, ASP.NET runs even faster because no interprocess communication between inetinfo.exe (the IIS executable) and aspnet_wp.exe is required. The HTTP request arrives directly at the worker process that hosts the CLR. Furthermore, the ASP.NET worker process is not a special process but simply a copy of the IIS worker process. This fact shifts to IIS the burden of process recycling and health checks.

In IIS 6.0, ASP.NET ignores the contents of the section from the machine.config file. Only thread and deadlock settings are read from that section of the machine.config. Everything else goes through the metabase and can be configured only by using the IIS Manager. (Other configuration information continues being read from .config files.)

The ASP NET HTTP Pipeline

The ASP.NET worker process is responsible for running the Web application that lives behind the requested URL. It passes any incoming HTTP requests to the so-called HTTP pipeline—that is, the fully extensible chain of managed objects that works according to the classic concept of a pipeline. Unlike ASP pages, ASP.NET pages are not simply parsed and served to the user. While serving pages is the ultimate goal of ASP.NET, the way in which the resultant HTML code is generated is much more sophisticated than in ASP and involves many more objects.

A page request passes through a pipeline of objects that process the HTTP content and, at the end of the chain, produce some HTML code for the browser. The entry point in this pipeline is the HttpRuntime class. The ASP.NET runtime activates the HTTP pipeline by creating a new instance of the HttpRuntime class and then calling the method ProcessRequest.

The HttpRuntime Object

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 context for the request and initializes a specialized text writer object in which the HTML code will be accumulated. A context is given by an instance of the HttpContext class, which encapsulates all HTTP-specific information about the request. The text writer is an instance of the HttpWriter class and is the object that actually buffers text sent out through the Response object.

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.

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, 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 any is present, and the creation of the assembly that represents the actual page requested. An HttpApplication object is used to process a single page request at a time; multiple objects are used to serve simultaneous requests for the same page.

  Note

ASP.NET global.asax files are dynamically compiled the first time any page or Web service is requested in the virtual directory. This happens even before the target page or Web service is compiled. ASP.NET pages and Web services within that Web application are subsequently linked to the resulting global.asax compiled class when they are in turn compiled.

The HttpApplication Object

HttpApplication is a global.asax-derived object that the ASP.NET worker process uses to handle HTTP requests that hit a particular virtual directory. A particular HttpApplication instance is responsible for managing the entire lifetime of the request it is assigned to, and the instance of HttpApplication can be reused only after the request has been completed. The HttpApplication class defines the methods, properties, and events common to all application objects—.aspx pages, user controls, Web services, and HTTP handlers—within an ASP.NET application.

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. HTTP modules represent the managed counterpart of ISAPI filters and will be covered in greater detail in Chapter 23.

The HttpApplication object determines the type of object that represents the resource being requested—typically, an ASP.NET page. It then uses a handler factory object to either instantiate the type from an existing assembly or dynamically create the assembly and then an instance of the type. A handler factory object is a class that implements the IHttpHandlerFactory interface and is responsible for returning an instance of a managed class that can handle the HTTP request—an HTTP handler. An ASP.NET page is simply a handler object—that is, an instance of a class that implements the IHttpHandler interface.

  Caution

Although the name sounds vaguely evocative of the intrinsic ASP Application object, the ASP.NET HttpApplication has nothing to do with it. The ASP Application object is fully supported in ASP.NET, but it maps to an object of type HttpApplicationState. However, the HttpApplication object has a property named Application, which returns just the ASP.NET counterpart of the ASP intrinsic application-state object.

The Handler Factory

The HttpApplication determines the type of object that must handle the request, and it delegates the type-specific handler factory to create an instance of that type. Let's see what happens when the resource requested is a page.

Once 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 an undocumented class named PageHandlerFactory.

  Note

The HttpApplication object determines the proper handler for the request and creates an instance of that class. To find the appropriate handler, it uses the information in the section of the machine.config file. The section lists all the currently registered handlers for the application.

The page handler factory is responsible for either finding the assembly that contains the page class or dynamically creating an ad hoc assembly. The System.Web namespace defines a few handler factory classes. These are listed in Table 2-3.

Table 2-3: Handler Factory Classes in the .NET Framework

Handler Factory

Type

Description

HttpRemotingHandlerFactory

*.rem;

*.soap

Instantiates the object that will take care of a .NET Remoting request routed through IIS. Instantiates an object of type HttpRemotingHandler.

PageHandlerFactory

*.aspx

Compiles and instantiates the type that represents the page. The source code for the class is built while parsing the source code of the .aspx file. Instantiates an object of a type that derives from Page.

SimpleHandlerFactory

*.ashx

Compiles and instantiates the specified HTTP handler from the source code of the .ashx file. Instantiates an object that implements the IHttpHandler interface.

WebServiceHandlerFactory

*.asmx

Compiles the source code of a Web service, and translates the SOAP payload into a method invocation. Instantiates an object of the type specified in the Web service file.

Bear in mind that handler factory objects do not compile the requested resource each time it is invoked. The compiled code is stored in a directory on the Web server and used until the corresponding resource file is modified.

So the page handler factory creates an instance of an object that represents the particular page requested. This object inherits from the System.Web.UI.Page class, which in turn implements the IHttpHandler interface. The page object is built as an instance of a dynamically generated class based on the source code embedded in the .aspx file. The page object is returned to the application factory, which passes that back to the HttpRuntime object. The final step accomplished by the ASP.NET runtime is calling the ProcessRequest method on the page object. This call causes the page to execute the user-defined code and generate the HTML text for the browser.

Figure 2-5 illustrates the overall HTTP pipeline architecture.

click to expand
Figure 2-5: The HTTP pipeline processing for a page.

The ASP NET Page Factory Object

Let's examine in detail how the .aspx page is converted into a class and compiled into an assembly. Generating an assembly for a particular .aspx resource is a two-step process. First, the source code for the class is created by merging the content of the

Pro ASP.NET (Ch 02)

Sample Page


 

The following listing shows the source code that ASP.NET generates to process the preceding page. The text in boldface type indicates code extracted from the .aspx file:

namespace ASP 
{
 using System;
 
 using ASP;
using System.IO;

 public class Default_aspx : Page, IRequiresSessionState 
 {
 private static int __autoHandlers;
protected Label TheSourceFile;
protected HtmlInputText TheString;
protected HtmlInputButton TheButton;
protected HtmlGenericControl TheResult;
protected HtmlForm TheAppForm;
 private static bool __initialized = false;
 private static ArrayList __fileDependencies;
 
private void Page_Load(object sender, EventArgs e)
{
TheSourceFile.Text = HttpRuntime.CodegenDir;
}
private void MakeUpper(object sender, EventArgs e)
{
string buf = TheString.Value;
TheResult.InnerText = buf.ToUpper();
}

 public Default_aspx() 
 {
 ArrayList dependencies;
 if (__initialized == false)
 {
 dependencies = new ArrayList();
 dependencies.Add(
 "c:\inetpub\wwwroot\vdir\Default.aspx");
 __fileDependencies = dependencies;
 __initialized = true;
 }
 this.Server.ScriptTimeout = 30000000;
 }
 
 protected override int AutoHandlers {
 get {return __autoHandlers;}
 set {__autoHandlers = value;}
 }
 
 protected Global_asax ApplicationInstance {
 get {return (Global_asax)(this.Context.ApplicationInstance));}
 }
 
 public override string TemplateSourceDirectory {
 get {return "/vdir";}
 }
 
private Control __BuildControlTheSourceFile() {
Label __ctrl = new Label();
this.TheSourceFile = __ctrl;
__ctrl.ID = "TheSourceFile";
return __ctrl;
}

private Control __BuildControlTheString() {
// initialize the TheString control
}

private Control __BuildControlTheButton() {
// initialize the TheButton control
}

private Control __BuildControlTheResult() {
// initialize the TheResult control
}

private Control __BuildControlTheAppForm() {
HtmlForm __ctrl = new HtmlForm();
this.TheAppForm = __ctrl;
__ctrl.ID = "TheAppForm";
IParserAccessor __parser = (IParserAccessor) __ctrl;
this.__BuildControlTheSourceFile();
__parser.AddParsedSubObject(this.TheSourceFile);
__parser.AddParsedSubObject(new LiteralControl("

")); this.__BuildControlTheString(); __parser.AddParsedSubObject(this.TheString); this.__BuildControlTheButton(); __parser.AddParsedSubObject(this.TheButton); __parser.AddParsedSubObject(new LiteralControl("
"));
this.__BuildControlTheResult(); __parser.AddParsedSubObject(this.TheResult); return __ctrl; } private void __BuildControlTree(Control __ctrl) { IParserAccessor __parser = (IParserAccessor)__ctrl; __parser.AddParsedSubObject( new LiteralControl("…")); this.__BuildControlTheAppForm(); __parser.AddParsedSubObject(this.TheAppForm); __parser.AddParsedSubObject(new LiteralControl("…")); } protected override void FrameworkInitialize() { this.__BuildControlTree(this); this.FileDependencies = __fileDependencies; this.EnableViewStateMac = true; this.Request.ValidateInput(); } public override int GetTypeHashCode() { return 2003216705; } } }

  Important

As you can see, portions of the source code in the .aspx file are used to generate a new class in the specified language. Just because the inline code in a page will be glued together in a class doesn't mean you can use multiple languages to develop an ASP.NET page. The .NET platform is language-neutral but, unfortunately, .NET compilers are not capable of cross-language compilation!

In addition, for ASP.NET pages, the language declared in the @Page directive must match the language of the inline code. The Language attribute, in fact, is used to determine the language in which the class is to be created. Finally, the source code is generated using the classes of the language's Code Document Object Model (CodeDOM). CodeDOM can be used to create and retrieve instances of code generators and code compilers. Code generators can be used to generate code in a particular language, and code compilers can be used to compile code into assemblies. Not all .NET languages provide such classes, and this is why not all languages can be used to develop ASP.NET applications. For example, the CodeDOM for J# has been added only in version 1.1 of the .NET Framework, but there is a J# redistributable that adds this functionality to version 1.0.

All the controls in the page marked with the runat attribute are rendered as protected properties of the type that corresponds to the tag. Those controls are instantiated and initialized in the various __BuildControlXXX methods. The initialization is done using the attributes specified in the .aspx page. The build method for the form adds child-parsed objects to the HtmlForm instance. This means that all the parent-child relationships between the controls within the form are registered. The __BuildControlTree method ensures that all controls in the whole page are correctly registered with the page object.

All the members defined in the


As shown in Figure 2-8, the page lets the user type some text in an input field and then posts all the data to the server for further processing. If you point your browser to this page, the actual HTML code being displayed is the following. The text in boldface is the page's view state:

click to expand
Figure 2-8: The sample ASP.NET page in action.


 

name="__VIEWSTATE" value="dDwtMTM3NjQ2NjY2NTs7PrH3U/xuqPTNI63IlLw5THHvPFUf" />


  Note

A question I often get at conferences and classes (but rarely a publicly asked question) concerns the use of hidden fields in ASP.NET. The question is typically, "Should I really use hidden fields also in ASP.NET?" Hidden fields have a bad reputation among ASP developers because they appear to be a quick fix and a sort of dirty trick. In some way, developers tend to think that they use hidden fields because they're unable to find a better solution. A similar feeling was common among Windows SDK programmers regarding the use of global variables or temporary files.

Developers seem to fear using hidden fields in the dazzling new object-oriented world of ASP.NET. Well, nothing really prohibits the use of hidden fields in ASP.NET applications and using them is in no way shameful, as long as HTTP remains the underlying protocol. The real point is even more positive.

Using hidden fields in ASP.NET allows you to create more useful Web pages—for example, the view-state mechanism. In light of this, hidden fields are especially recommended when you need to pass information to be consumed through client-side scripts.

The server-side

tag is rendered as a plain old HTML form in which the Name attribute matches the ID property of the HtmlForm control, the Method attribute defaults to POST and Action is automatically set with the URL of the same page. The form contains as many HTML elements as there are server controls in the .aspx source. The form also includes any static text or HTML tags declared in the page layout. The view state is rendered as a hidden field named __VIEWSTATE and filled with Base64-encoded text.

The View State of the Page

The contents of the view state are determined by the contents of the ViewState property of the page and its controls. The ViewState property is a collection of name/value pairs that both pages and controls fill with data they determine should be preserved across page requests. The various collections are merged together to form a unique object graph, which is then encoded as a Base64 string. Seen from the client side, the __VIEWSTATE hidden field is just a more or less overwhelming sequence of characters. The more state information you save in the ViewState property, the bigger your page becomes. Other security and performance concerns apply to the page view state, but we'll cover them in greater detail in Chapter 14. Note that for a real-world page, the size of the view state can easily be more than 10 KB of data, which is an extra burden of 10 KB transmitted over the network and simply ignored on the client.

  Note

The implementation of the view state in ASP.NET raises hidden fields from the rank of a dirty trick to the higher and more noble role of an internal system feature. Today, lots of ASP developers use hidden fields to persist small pieces of page-specific information. This information is, in most cases, written as plain text and serialized with home-made schemes. The view state implementation revamps that old trick and, more, exposes persisted information in an extremely handy, object-based fashion.

Some developers are afraid of having their own state information at the mercy of the client. That this is the actual situation cannot be denied; but that this automatically qualifies as a security hole has yet to be proven. However, if you were among the many who used to cache plain text in hidden fields, the ASP.NET view state represents a quantum leap! In addition, there are some extra security enforcements that can be applied to the view state. We'll review those in Chapter 14.

Postback Events

A postback event is a page request that results from a client action. Typically, when the user clicks on a submit button, like the Convert button in Figure 2-8, a postback event occurs. In this case, the HTML form posts to the same .aspx page and, as normally happens with HTTP POST commands, the contents of the various input fields in the form are packed in the body of the request.

On the server, the code in charge of processing the request first gets an instance of the class that represents the .aspx page and then goes through the steps described earlier in the "Page-Related Events" section.

State Restoration

As the first step of the page processing, the HTTP runtime builds the tree of controls for the page and sets the IsPostBack property. IsPostBack is a Boolean property that equals true if the page is being processed after a post from the same page—a postback event.

After initialization, the HTTP runtime instructs the page to deserialize the view-state string and transform it into an instance of the StateBag class. StateBag implements a dictionary that is populated with the name/value pairs stored and encoded in the view-state string. Once the StateBag object has been completely set up, user code can start playing with its contents. From a user-code perspective, however, this timing means it can't do anything before the OnLoad event fires. Before OnLoad is fired, though, another step needs to be accomplished.

At this point, the various controls active on the page have been restored to the state stored in the view state. Unless the view state has been sapiently and maliciously corrupted on the client (an extremely remote possibility, if not completely impossible), the state of the objects is the state that existed when that page was rendered to the client. It is not yet the state the user set with client-side input actions such as checking radio buttons or typing text. The next step updates the state of the various controls to reflect any client action. This final step ends the state restoration phase, and the page is now ready to process user code. Fire the OnLoad event.

Handling the Server-Side Event

The goal of a postback event is executing some server-side code to respond to user input. How can the page know what method it has to execute? The post is always the result of a form submission. However, the submit action can be accomplished in two ways: if the user clicks a control or if the user takes some action that causes some script to run. Let's examine the first case.

The HTML syntax dictates that the ID of the button clicked be inserted in the post data. On the server, the ASP.NET page digs out this information in its attempt to locate an element whose name matches one of its server controls. If a match is found, ASP.NET verifies that the control implements IPostBackEventHandler. This is enough to guarantee that the button clicked on the server was a submit button. Generally, it means the control has some code to execute upon postback. At this point, the page raises the postback event on the control and has the control execute some code. To raise the postback event on the control, the ASP.NET page invokes the control's RaisePostBackEvent method—one of the members of the IPostBackEventHandler interface. For a submit button, this causes the invocation of the method associated with the onclick property.

If some script code can post the page back to the server, the author of the script is responsible for letting ASP.NET know about the sender of the event. To understand how to accomplish this, let's see what happens when you use a Web control such as the LinkButton—a button rendered as a hyperlink:


 

The HTML code for the page changes slightly. A JavaScript function and a couple of extra hidden fields are added. The two hidden fields are __EVENTTARGET and __EVENTARGUMENT. They are empty when the page is rendered to the browser and contain event data when the page is posted back. In particular, __EVENTTARGET contains the name of the control that caused the postback. The __EVENTARGUMENT field contains any argument that might be useful to carry out the call. A short piece of JavaScript code sets the two fields and submits the form:


But what's the link between the __doPostBack function and the LinkButton object? Let's look at the HTML generated for the LinkButton:

<a href="javascript:__doPostBack(TheLink,\)">Convert...</a>

Quite simply, any click on the anchor is resolved by executing the __doPostBack function.

If you want to provide a completely custom mechanism to post the page back, just borrow the preceding code and make sure the form is submitted with the __EVENTTARGET field set to the name of the sender of the event. (For example, you could have the page post back when the mouse hovers over a certain image.)

To find the sender control that will handle the server-side event, ASP.NET looks at the __EVENTTARGET hidden field if no match is found between its controls and the content of the Request.Form collection—that is, if the previous case (the submit button) does not apply.

Rendering Back the Page

After executing the server-side code, the page begins its rendering phase. At first, the page fires the PreRender event; it then serializes the content of the ViewState object—that is, the current state of the various controls active on the page—to a Base64 string. Next, the HTML code is generated and that Base64 string becomes the value of the __VIEWSTATE hidden field. And so on for the next round-trip.

The Page Class

In the .NET Framework, the Page class provides the basic behavior for all objects that an ASP.NET application builds 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, we find the Control class. It defines the properties, methods, and events shared by all ASP.NET server-side elements—pages, controls, and user controls. It's useful to also look at the functionalities of the Page class from an interface perspective, as in Figure 2-9.

click to expand
Figure 2-9: The hierarchy of classes from which Page inherits.

From the parent Control class, Page inherits the base behavior for interacting with all other components in the CLR—the IComponent interface—and for releasing unmanaged resources (the IDisposable interface). In addition, the IParserAccessor interface groups the methods that ASP.NET controls must implement to recognize parsed child elements. The IParserAccessor interface plays a key role in the implementation of the control tree in the dynamically generated source code for requested ASP.NET pages. (Actually, the IParserAccessor interface consists of the single AddParsedSubObject method we saw largely used in the code shown in "Reviewing the Class Source Code" section.)

  Note

The Control class also implements the IDataBindingsAccessor interface. This interface, though, is not involved with the run-time behavior of server controls. The interface allows access to the collection of data-binding expressions on a control at design time.

Derived from a class—TemplateControl—that implements INamingContainer, Page 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 also a very important feature for iterative data-bound controls, such as DataGrid, and for user controls.)

The Page class also implements the methods of the IHttpHandler interface, thus qualifying as the handler of a particular type of HTTP requests—those for .aspx files. The key element of the IHttpHandler interface is the 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 Object

The properties of the Page object can be classified in three distinct groups: intrinsic objects, worker properties, and page-specific properties. Tables 2-4, 2-5, and 2-6 enumerate and describe them.

Table 2-4: ASP.NET Intrinsic Objects in the Page Class

Property

Description

Application

Instance of the HttpApplicationState class, represents the state of the application. 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 decay and expiration.

Request

Instance of the HttpRequest class, represents the current HTTP request. Functionally equivalent to the ASP intrinsic Request object.

Response

Instance of the HttpResponse class, sends HTTP response data to the client. Functionally equivalent to the ASP intrinsic Response object.

Server

Instance of the HttpServerUtility class, provides helper methods for processing Web requests. Functionally equivalent to the ASP intrinsic Server object.

Session

Instance of the HttpSessionState class, manages user-specific data. Functionally equivalent to the ASP intrinsic Session object.

Trace

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

User

An IPrincipal object that represents the user making the request.

We'll cover Request, Response, and Server in Chapter 13; Application, Cache, and Session in Chapter 14; and User and security will be the subject of Chapter 15.

Table 2-5: Worker Properties of the Page Class

Property

Description

Controls

Returns the collection of all the child controls contained in the current page

ErrorPage

Gets or sets the error page to which the requesting browser is redirected in case of an unhandled page exception

IsPostBack

Indicates whether the page is being loaded in response to a client postback or whether it is being loaded for the first time

IsValid

Indicates whether page validation succeeded

NamingContainer

Returns null

Page

Returns the current Page object

Parent

Returns null

TemplateSourceDirectory

Gets the virtual directory of the page

Validators

Returns the collection of all validation controls contained in the page

ViewStateUserKey

String property used to assign an identifier to the view state variable for individual users. This trick is a line of defense against one-click attacks. The property is not available with ASP.NET 1.0.

In the context of an ASP.NET application, the Page object is the root of the hierarchy. For this reason, inherited properties such as NamingContainer and Parent always return null. The Page property, on the other hand, returns an instance of the same object (this in C# and Me in Visual Basic .NET).

A special note deserves the ViewStateUserKey property that has been added with version 1.1 of the .NET Framework. A common use for the user key would be to stuff user-specific information that will then be used to hash the contents of the view state along with other information. (See Chapter 14.) A 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 couldn't construct a valid view state for your user account unless he could also authenticate as you. That way, you have another barrier against on-click attacks. This technique, though, might not be really effective for Web sites that allow anonymous access unless you have some other unique tracking device running.

Note that if you plan to set the ViewStateUserKey property, you must do that during the Page_Init event. If you attempt to do it later (for example, when Page_Load fires), an exception will be thrown.

Table 2-6: Page-Specific Properties of the Page Class

Property

Description

ClientID

Always returns the empty string.

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

Gets or sets 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.

ID

Always returns the empty string.

SmartNavigation

Gets or sets a value indicating whether smart navigation is enabled. Smart navigation exploits a bunch of browser-specific capabilities to enhance the user's experience with the page. The feature works only with Internet Explorer 5.5 and newer versions. You can also enable or disable this feature through the SmartNavigation attribute of the @Page directive.

UniqueID

Always returns the empty string.

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 Object

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 HTML for the page (as shown in Table 2-7); others are helper methods to build the page and manage the constituent controls (as shown in Table 2-8). Finally, a third group collects all the methods that have to do with client-side scripting (as shown in Table 2-9).

Table 2-7: Methods for HTML 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.

RegisterRequiresPostBack

Registers the specified control with the page so that the control will receive a post-back handling notice. In other words, the page will call the LoadPostData method of registered controls. LoadPostData requires the implementation of the IPostBackDataHandler interface.

RegisterRequiresRaiseEvent

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

RenderControl

Outputs the HTML text for the page, including tracing information if tracing is enabled.

VerifyRenderingInServerForm

Controls call this method when they render to ensure they are included in the body of a server form. The method does not return a value, but it throws an exception in case of error.

In an ASP.NET page, no control can be placed outside a tag with the runat attribute set to server. The VerifyRenderingInServerForm method is used by Web and HTML controls to ensure 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.

Table 2-8: Worker 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 like 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. (See the source code of the sample page class we discussed earlier in the "Reviewing the Class Source Code" section.) In the base Page class, the method implementation simply returns 0; significant numbers are returned by classes used for actual pages.

HasControls

Determines whether the page contains any child controls.

LoadControl

Compiles and loads a user control from a .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 a .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.

RegisterViewStateHandler

Mostly for internal use, the method sets an internal flag causing 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 on the page to validate their assigned information.

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 ParseControl 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 can be omitted from the strings you pass directly to ParseControl.

Table 2-9 enumerates all the methods in the Page class that have to do with HTML and script code to be inserted in the client page.

Table 2-9: Script-Related Methods

Method

Description

GetPostBackClientEvent

Calls into GetPostBackEventReference.

GetPostBackClientHyperlink

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

javascript:__doPostBack('CtlID','')

GetPostBackEventReference

Returns the prototype of the client-side script 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.

IsStartupScriptRegistered

Determines whether the specified client startup script is registered with the page.

RegisterArrayDeclaration

Use this method to add an ECMAScript array to the client page. This method accepts the name of the array and a string that will be used verbatim as the body of the array. For example, if you call the method with arguments such as "theArray" and "'a', 'b'", you get the following JavaScript code:

var theArray = new Array('a', 'b');

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

RegisterHiddenField

Use this method to automatically register a hidden field on the page.

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.

RegisterStartupScript

An ASP.NET page uses this method to emit client-side script blocks in the client page just before closing the HTML element.

Many methods listed in Table 2-9 let you emit script in the client page—either JavaScript or VBScript. When you use any of these methods, you actually tell the page to insert that script code when the page is rendered. So when any of these methods execute, the script-related information is simply cached in internal structures and used later when the page object generates its HTML text. The same pattern applies to hidden fields and ECMAScript arrays.

  Note

JavaScript is the script language that virtually any available browser supports. For this reason, some of the methods in Table 2-9 default to JavaScript. However, when you register a script block, nothing really prevents you from using VBScript as long as you set the language attribute of the

Although it's easy to understand and implement, the solution is still boring to code (or even to cut and paste) for each page of the application. The code-behind technique comes to the rescue. The idea is that you derive a new class from Page and make sure it contains the needed script code and runs it upon startup.

A Better Page Class

The following listing demonstrates an alternative Page class. It inherits from the System.Web.UI.Page class and exposes a Focus property that clients can set with the name of the HTML control with the input focus:

using System;
using System.Web.UI;
using System.Text;
 
namespace ProAspNet.CS.Ch02
{
 public class Page : System.Web.UI.Page
 {
 public Page()
 {
 }
 
 // Internals
 private const string FUNCTION_NAME = "__setFocus";
 private const string SCRIPT_NAME = "__inputFocusHandler";
 
 // Gets/Sets the name of the HTML control with the input focus
 public string Focus;
 
 // Registers client-side script 
 protected override void OnPreRender(EventArgs e)
 {
 base.OnPreRender(e);
 AddStartupScript();
 }
 
 // Create the script strings
 private void AddStartupScript()
 {
 // Find the ID of the ASP.NET form
 string formName = "";
 foreach(Control ctl in this.Controls)
 if (ctl is HtmlForm) {
 formName = """ + ctl.UniqueID + """;
 break;
 }
 
 // Add the script to declare the function
 StringBuilder sb = new StringBuilder("");
 sb.Append("

The __setFocus JavaScript function is first declared and then invoked with a particular argument. The script is registered using the RegisterStartupScript method. A startup script is placed exactly before closing the

tag and executed when the browser has finished with the rest of the form—in this sense, it is called a startup script and is functionally equivalent to setting the onload attribute of the page body.

Programmatically Set the Input Focus

Because the ProAspNet.CS.Ch02.Page class inherits from Page, you can use it wherever a page class is acceptable. To make sure that ASP.NET builds an .aspx page just from the ProAspNet.CS.Ch02.Page class, use this type with the Inherits attribute of the page directive. Of course, you need to have an assembly for the class in one of the paths the ASP.NET runtime can reach—typically the virtual folder's Bin directory. The following page automatically sets the input focus to the field named user. The code in boldface type is all you have to add to an existing page to support the feature, which sets a new base class and the input focus:


<%@ Page Language="C#" Inherits="ProAspNet.CS.Ch02.Page" %>


 

Pro ASP.NET (Ch 02)

Log on to the System

 

 

UserName

Password

The IsPostBack property is set to true if the page is being displayed after a postback. If you don't check against IsPostBack, the input focus will be set to the control named user each time the page is accessed; otherwise, it will be set only the first time the page is accessed in the session.

Changing the Default Page Base Type

As mentioned earlier, by default a page inherits from the Page class or from any other code-behind class specified in the @Page directive. If you need all pages in an application (or in a certain folder) to inherit from a common and user-defined class, you can override the base page type from the System.Web.UI.Page class to whatever class you want. Obviously, the new page class must in turn inherit from System.Web.UI.Page.

For example, to make all the pages in a Web application inherit the focus feature we discussed earlier, create a web.config file in the application's root folder and give it the following content:


 
 
 

The pageBaseType attribute must be set to a valid type name—that is, a comma-separated string formed by a type name and the assembly name. In the preceding code, the specified class is implemented in the page.dll assembly. Note that the base page could also be changed for all the applications running on a server machine. In this case, you must edit the machine.config file, locate the section, and add the pageBaseType attribute. By default, the attribute is initialized implicitly and is not listed in the configuration file. To edit both web.config and machine.config, you need administrator privileges.

  Note

In the next version of ASP.NET, the WebControl class and the Page class will provide a nearly identical feature through a method named SetFocus. The method takes the ID of a control and caches it internally. When the page is rendered to HTML, ad hoc script code will be generated to set the focus to the control.

Master Pages in ASP NET

A large number of Web sites these days contain similar-looking pages that share the same header, footer, and perhaps some navigational menus or search forms. What's the recommended approach for reusing code across pages? One possibility is wrapping these user-interface elements in user controls and referencing them in each page. Although the model is extremely powerful and produces highly modular code, when you have hundreds of pages to work with, it soon becomes unmanageable.

An alternative approach entails using code-behind to aim at a kind of visual inheritance akin to that of Windows Forms. In this case, the custom base class programmatically generates user-interface elements such as the page header. In addition, the page class could expose custom objects as programmable entities—for example, a collection for the links to display on the header. To build a new page, you simply set the Inherits attribute with the class name. When part of the user interface (UI) is programmatically generated, merging the base elements and the actual content of the content page can be problematic unless absolute positioning is used. In the sample code of this chapter (located at ch02Inherit), you find an application that solves this issue by forcing the client page to include an control with a particular ID just where a particular parent UI element should appear.

In the next version of ASP.NET, the concept of the master page will be introduced. A master page is a distinct file referenced at the application level, as well as at the page level, that contains the static layout of the page. Regions that each derived page can customize are referenced in the master page with a special placeholder control. A derived page is simply a collection of blocks the run time will use to fill the holes in the master. Master pages in the next version of ASP.NET are orthogonal to true visual inheritance. The contents of the master, in fact, are merged into the derived page rather than serving as a base class for the derived page.


Conclusion

ASP.NET is a complex technology built on top of a simple and, fortunately, solid and stable Web infrastructure. To provide highly improved performance and a richer programming tool set, 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.

There are two relevant aspects in the ASP.NET Web Forms model: the process model, including the Web server process model, and the page object model. ASP.NET anticipates some of the features of IIS 6.0—the new and revolutionary version of the Microsoft Web information services. ASP.NET applications run in a separate worker process (as all applications will do in IIS 6.0); ASP.NET applications support output caching (as IIS 6.0 will do on behalf of all types of Web applications); and finally, the ASP.NET runtime automatically recycles processes to guarantee excellent performance in spite of run-time anomalies, memory leaks, and programming errors. The same feature becomes a system feature in IIS 6.0.

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. The code-behind technique allows you to programmatically change the base class for a given .aspx resource. The net effect of code-behind is the possibility to implement very cool features such as code and layout separation and page inheritance.

In this chapter, we 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 various server controls, which include Web controls, HTML controls, validation controls, and data-bound controls.


Resources

  • HTTP Architecture (http://www.develop.com/conferences/conferencedotnet/materials/A4.pdf)
  • HTTP Pipeline (http://www.develop.com/summercamp/conferencedotnet/materials/W2.pdf)
  • Securely Implement Request Processing, Filtering, and Content Redirection with HTTP Pipelines in ASP.NET (http://msdn.microsoft.com/msdnmag/issues/02/09/HTTPPipelines)
  • Viewstate Optimization Strategies in ASP.NET (http://www.webreference.com/programming/asp/viewstate)
  • KB306005 Repair IIS Mapping After You Remove and Reinstall IIS
  • KB315158 ASP.NET Does Not Work with the Default ASPNET Account on a Domain Controller.


Chapter 3 ASP NET Core Server Controls



Programming Microsoft ASP. NET
Programming Microsoft ASP.NET 3.5
ISBN: 0735625271
EAN: 2147483647
Year: 2005
Pages: 209
Authors: Dino Esposito

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net