Chapter 1. Architecture

ASP.NET introduces a slew of new features for the Web application developer, including compiled server-side code, a technique called code-behind to separate server-side logic from client-side layout, an extensible server-side control model, a well-designed and easy-to-use data binding model, xcopy deployment, and support for form validation on both clients and servers. More than all that, however, ASP.NET gives us unification: a unification of languages, tools, libraries, deployment models, system design, and diagnostics. Web application developers no longer need to differentiate between components used by their pages and components used elsewhere in their architecture. They no longer have to deal with a script debugger to diagnose problems in their pages. They are no longer subject to the often mysterious subtleties of untyped scripting languages and can now use whatever .NET language they prefer in building their pages. Building Web applications is now like any other software development on the .NET platform.

In this chapter, we introduce the architectural foundations of building Web applications with ASP.NET. We look at the new compilation model used to process requests, how to build code-behind classes, how the shadow copy mechanism enables xcopy deployment, and the new directives and intrinsics available.

Fundamentals

At its core, ASP.NET is a collection of .NET classes that work together to service HTTP requests. Some of these classes are defined in system assemblies as part of the base class libraries that are installed with the .NET runtime, some of these classes may be housed in assemblies deployed in the global assembly cache (GAC), and some of these classes are loaded from local assemblies that live in the virtual directory associated with this application. All these classes are loaded into an application domain within the ASP.NET worker process and interact to generate a response for a given request. Figure 1-1 shows this architecture.

High-Level Architecture of ASP.NET

Figure 1-1. High-Level Architecture of ASP.NET

The fundamental shift that most developers face when moving to ASP.NET is the fact that everything is a class loaded from an assembly. As in other class-based architectures, you build applications in ASP.NET by constructing classes that interact with other classes in the base framework. Some of your classes will derive from base classes in the framework, others may implement interfaces defined by the framework, and still others may simply interact with base classes in the framework by calling methods on them. Although ASP-style syntax is still supported, ASP.NET files with server-side code are turned into class definitions housed in assemblies when first accessed. The end result, therefore, is a collection of classes interacting within a process to service a request.

Another significant shift is the process model. The ASP.NET worker process is a distinct worker process, aspnet_wp.exe,[1] separate from inetinfo.exe (the Internet Information Server, or IIS, process), and the process model in ASP.NET is unrelated to process isolation settings in IIS. Although IIS is still typically used as the entry point to an ASP.NET application,[2] physically listening on the appropriate ports and dispatching the requests, its role has been lessened, and many of the tasks it used to handle are now handled by ASP.NET in its own worker process.

ASP 4.0

Although ASP.NET is not technically labeled ASP 4.0, in many ways it is just that—the next version of ASP. Because most people starting to work with ASP.NET come from an ASP background, we start our exploration by describing the similarities shared by the two technologies. Listing 1-1 shows a simple ASP page that intermingles server-side JavaScript with static HTML. Note that several server-side coding techniques are demonstrated in this file. There is a script block marked with the runat=server attribute containing a function called Add. The server-side evaluation syntax “<%=” is used to invoke the Add method and output the results of the expression to an h2 tag. The server-side script syntax “<%” is used to programmatically generate ten rows in a table, and finally, the intrinsic Response object is used to programmatically add a final h2 tag to the bottom of the page.

Example 1-1. Sample ASP Page

<!— File: test.asp —>
<%@ language=javascript %>

<script language='JScript' runat=server>
function Add(x, y)
{
  return x+y;
}
</script>

<html> <body>
<h1>Test ASP Page</h1>

<h2>2+2=<%=Add(2,2)%></h2>
<table border=2>
<%
  for (var i=0; i<10; i++) {
%>
  <tr><td>Row<%=i%> Col0</td><td>Row<%=i%> Col1</td></tr>
<%
  }
%>
</table>

<%
  Response.Write("<h2>Written directly to Response</h2>");
%>

</body> </html>

All these server-side programming techniques are supported in ASP.NET as well. In fact, if you take this file and simply change the extension from .asp to .aspx, you will find that it behaves exactly as the .asp version did. What is really happening under the covers when these two pages are accessed is dramatically different, as we will see, but on the surface, many traditional ASP pages can be brought forward as ASP.NET pages with no modifications.

In ASP.NET, we are no longer constrained to the two scripting languages available in traditional ASP: VBScript and JScript.[3] Any fully compliant .NET language can now be used with ASP.NET, including C# and VB.NET. To see an example, we can rewrite the ASP page presented in Listing 1-1 as an ASP.NET page using C# as the server-side language. Although it is not strictly required, we include the language preference within a Page directive, which is where most of the page-level attributes are controlled for ASP.NET pages. Listing 1-2 shows the page rewritten using C#, complete with an ASP.NET Page directive.

Example 1-2. Sample .aspx Page

<!— File: test.aspx —>
<%@ Page Language='C#' %>

<script runat=server>
int Add(int x, int y)
{
  return x+y;
}
</script>

<html> <body>
<h1>Test ASP.NET Page</h1>

<h2>2+2=<%=Add(2,2)%></h2>
<table border=2>
<%
  for (int i=0; i<10; i++) {
%>
  <tr><td>Row<%=i%> Col0</td><td>Row<%=i%> Col1</td></tr>
<%
  }
%>
</table>

<%
  Response.Write("<h2>Written directly to Response</h2>");
%>

</body> </html>

Compilation versus Interpretation

The first time you access the ASP.NET page shown in Listing 1-2, the most remarkable thing you will see differentiating it from the traditional ASP version is the amount of time it takes for the page to load. It is slower. Quite a bit slower, in fact. Any subsequent access to that page, however, will be markedly faster. The overhead you will see on the first access is the launching of the ASP.NET worker process plus the parsing and compilation of the .aspx files into an assembly. This is in contrast to how the ASP engine executes server-side code, which is always through an interpreter (JScript or VBScript).

When a traditional ASP page is requested, the text of that page is parsed linearly. All content that is not server-side script is rendered as is back to the response. All server-side script in the page is first run through the appropriate interpreter (JScript or VBScript), the output of which is then rendered back to the response. This architecture affects the efficiency of page rendering in several ways. First, interpreting the server-side script on the fly is less efficient than executing precompiled code on the server. As a side effect, one common optimization for ASP applications is to move a lot of server-side script into precompiled COM components to improve response times. A second efficiency concern is that intermingling server-side evaluation blocks with static HTML is less efficient than evaluating a single server-side script block, because the interpreter has to be invoked over and over again. Thus, to improve efficiency of rendering, many ASP developers resort to large blocks of server-side script, replacing static HTML elements with Response.Write() invocations instead. Finally, this ASP model actually allows different blocks of script within a page to be written in different script languages. While this may be appealing in some ways, it also degrades performance by requiring that a particular page load both scripting engines to process a request, which takes more time and memory than using just one language.

In contrast, ASP.NET pages are always compiled into .NET classes housed within assemblies. This class includes all of the server-side code and the static HTML, so once a page is accessed for the first time (or any page within a particular directory is accessed), subsequent rendering of that page is serviced by executing compiled code. This eliminates all the inefficiencies of the scripting model of traditional ASP. There is no longer any performance difference between compiled components and server-side code embedded within a page—they are now both compiled components. There is also no performance difference between interspersing server-side code blocks among static HTML elements, and writing large blocks of server-side code and using Response.Write() for static HTML content. Also, because the .aspx file is parsed into a single code file and compiled, it is not possible to use multiple server-side languages within a single .aspx file.

There are several other immediate benefits to working with a compilation model instead of an interpreted one. In addition to improved performance over the interpreted model, pages that are compiled into classes can be debugged using the same debugging tools available to desktop applications or component developers. Errors with pages are generated as compiler errors, and there is a good chance that most errors will be found at compilation time instead of runtime, because VB.NET and C# are both strongly typed languages. Plus, all the tools available to the .NET developer are applicable to the .aspx developer. In fact, this distinction between Web application script developers and component developers, which has traditionally been very clear, is gone completely. Web developers using ASP.NET are constructing classes and building hierarchies using the same technologies and languages as their component developer peers, even when they are simply writing .aspx files with embedded server-side code. This is a fundamental shift in design from traditional ASP and bears repeating. Whenever you author an ASP.NET page, you are authoring a new class.

System.Web.UI.Page

Now that you understand that every page is compiled into a class definition, the next step is to understand exactly how that class is created and what control you have over its creation. As a first experiment, because we know that our page is turned into a class, we can display the type of our page and the class from which it inherits. Figure 1-2 shows a sample .aspx file, along with its output, that prints out the type of the page and its base class, using the GetType() method and the BaseType property.[4]

ASP.NET Page Type

Figure 1-2. ASP.NET Page Type

Notice that the type of the page is ASP.ShowPageType_aspx, which is simply the name of the file with the “.” replaced by an “_” character. More interestingly, the base class is System.Web.UI.Page, which defines most of the functionality for processing requests in ASP.NET. By default, every .aspx page you author derives from the Page base class. As with any other class hierarchy, it is important to understand the features and functionality of the class from which you inherit. Listing 1-3 shows some of the most interesting members of the Page class.

Example 1-3. Important Members of System.Web.UI.Page

public class Page : TemplateControl, IHttpHandler
{
        // State management
    public HttpApplicationState Application {get;}
    public virtual HttpSessionState Session { get;}
    public Cache Cache {get;}

        // Intrinsics
    public HttpRequest Request {get;}
    public HttpResponse Response {get;}
    public HttpServerUtility Server {get;}
    public string MapPath(string virtualPath);

        // Client information
    public ClientTarget ClientTarget {get; set;}
    public IPrincipal User {get;}

    // Core
    public UserControl LoadControl(string virtualPath);
    public virtual ControlCollection Controls {get;}
    public override string ID { get; set;}

    public bool IsPostBack {get;}
    protected void
              RenderControl(HtmlTextWriter writer);

    //...
}

The Page class provides facilities for state management, including the familiar Application and Session state objects plus a new Cache object, the details of which are discussed in Chapter 9. All the familiar intrinsics ASP programmers are used to can be found exposed as properties in the Page class, including the Response, Request, and Server objects. This means that the familiar ASP-style syntax of accessing something like the Response object will compile because it maps onto a property of the class from which your page inherits. The details of the new classes that replace the ASP intrinsics are discussed later in this chapter.

Once you are aware that your pages are turned into classes, you can start taking advantage of this fact by adding features to your page as you might add features to a class. For example, consider the page shown in Listing 1-4.

Example 1-4. Sample ASP.NET Page with Data Members

<!— SamplePage.aspx —>
<%@ Page Language="C#" %>
<html><body>
<script language="C#" runat=server>
  private ArrayList _values = new ArrayList();
  private void PopulateArray()
  {
      _values.Add("v1");
      _values.Add("v2");
      _values.Add("v3");
      _values.Add("v4");
  }
</script>

<h2>aspx==class!</h2>
<ul>
<%
  PopulateArray();
  for (int i=0; i<_values.Count; i++)
    Response.Output.Write("<li>{0}</li>", _values[i]);
%>
</ul>
</body> </html>

Because our page is now within a class definition, we can do things such as specifying the protection level of fields and methods, using field initializers, and pretty much anything else you might add to a class definition. In Listing 1-4, we defined a private field called _values of type ArrayList, initialized it to a newly allocated instance, and built a method called PopulateArray() to fill the field with values. Then in the body of our page we were able to invoke the PopulateArray() method and access the contents of the private _values field within a server-side script block. Notice that in this example, all the field and method declarations were placed in a server-side script block, while invocations of methods and access to fields were placed within server-side script tags (<% %>). This is important because code placed in each of these two places will be placed in the generated class definition in very different places. Code that falls within a server-side script block (<script runat=server></script>) will be placed directly into the class definition. Code that falls within server-side script tags (<% %>) will be placed into the body of a function of the class that will be called when the page is rendered. Figure 1-3 shows the relationship between these two server-side code notations and their placement in the generated class definition.

Server-Side Code Placement in Page Compilation

Figure 1-3. Server-Side Code Placement in Page Compilation

It is important to note this server-side code placement distinction, especially if you are migrating existing ASP pages to ASP.NET. It is no longer possible to include executable code outside the scope of a function within a script block marked as runat=server, and conversely, it is no longer possible to define a function within a pair of server-side script tags. Note also that the generated class definition provides a default constructor for you, and if you try to define your own default constructor within your page, it will cause a compiler error. This can be somewhat frustrating if you are trying to properly initialize elements of your class (such as filling up our array of values or subscribing to events). Fortunately, an alternative technique gives you more complete control over the class definition while separating the layout from the page logic. This technique is called code-behind.

Code-Behind

One of the more frustrating aspects of building traditional ASP pages was the convoluted nature of mixing server-side script with static layout elements. As ASP pages grow in size, they often become harder and harder to understand as the interplay between server-side script and static HTML becomes more complex. There are ways of dealing with this complexity, including imposing standards mandating scripting techniques and the use of server-side include directives to remove some of the code from a page. ASP.NET adds another, even more appealing option for separating programmatic logic from static page layout with a technique called code-behind.

In our earlier examples, we saw how each page in ASP.NET is compiled into a Page-derived class. Code-behind is the technique of creating an intermediate base class that sits between the Page base class and the machine-generated class from the .aspx file. This intermediate base class derives directly from Page, and the class generated from the .aspx file derives from the intermediate base class instead of directly from Page. With this technique, you can add fields, methods, and event handlers in your code-behind class and have these features inherited by the class created from the .aspx file, removing potentially significant amounts of code from the .aspx file. This technique relies on the ability to specify an alternative base class for the autogenerated class, which is done using the Inherits attribute of the Page directive. Listings 1-5 and 1-6 show our earlier sample page rewritten to use this code-behind technique.

Example 1-5. Sample .aspx File Using Code-Behind

<!— Codebehind.aspx —>
<%@ Page Language="C#"
    Inherits="EssentialAspDotNet.Architecture.SamplePage"%>
<html><body>

<h2>aspx==class!</h2>
<ul>
<% WriteArray(); %>
</ul>
</body> </html>

Example 1-6. Sample Code-Behind File

// SampleCodeBehind.cs

using System;
using System.Web;
using System.Web.UI;
using System.Collections;

namespace EssentialAspDotNet.Architecture
{
  public class SamplePage : Page
  {
    private ArrayList _values = new ArrayList();
    public SamplePage()
    {
      _values.Add("v1");
      _values.Add("v2");
      _values.Add("v3");
      _values.Add("v4");
    }

    protected void WriteArray()
    {
      for (int i=0; i<_values.Count; i++)
        Response.Output.Write("<li>{0}</li>",
                                _values[i]);
    }
  }
}

Note that we were able to move the initialization into the constructor of our class. This code-behind class must be compiled into an assembly and deployed in the /bin directory of this application for this to work (as we will see, all assemblies placed in the /bin directory of an ASP.NET application are implicitly added as references to the page compilation command). The class created from our .aspx file is now derived from SamplePage instead of Page because we are inserting a class into the hierarchy, as shown in Figure 1-4.

Class Hierarchy Created Using Code-Behind

Figure 1-4. Class Hierarchy Created Using Code-Behind

As an alternative to precompiling the code-behind file, you can use the src attribute, as shown in Listing 1-7. Any file referenced with the src attribute of the Page directive is compiled into a separate assembly and added to the list of referenced assemblies when the page is compiled. The advantage of using the src attribute for your code-behind files is that you can update a code-behind file just by replacing the file, and the next request that comes in causes ASP.NET to recompile the referenced file. This saves the step of compiling the code into an assembly yourself and updating the physical assembly in the /bin directory. It also ensures that the file will be compiled with the correct version of the .NET libraries, if for some reason you have different versions installed on different machines. On the other hand, if you have a compilation error in your source file, it will not be detected until you deploy the file and the page is accessed again. Precompiling the assembly beforehand guarantees that you will catch all compilation errors before deployment.

Example 1-7. Using the src Attribute to Reference a Code-Behind File

<!— Codebehind.aspx —>
<%@ Page Language="C#" src="SampleCodeBehind.cs"
    Inherits="EssentialAspDotNet.Architecture.SamplePage"%>
<!— ... —>

Event Handling

The code-behind example shown in the previous section extracted code from the .aspx page by defining methods and fields in the code-behind class, resulting in a “cleaner” page with less code clutter. In addition to methods and fields, code-behind classes can define handlers for events issued by the Page base class, which can be a useful way to manipulate the rendering of a page without adding code to the .aspx file.

The Page base class defines four events (actually, they are inherited from the Control base class) that are called in sequence during its lifetime: Init, Load, PreRender, and Unload, as shown in Listing 1-8. In addition, it defines four virtual function handlers for these events that are invoked when the events are fired, also shown in Listing 1-8. Thus, you can register a handler for any of these events in two ways: by subscribing a delegate to the event manually or by providing an overridden version of the virtual function defined in the base class.

Example 1-8. Events Defined in System.Web.UI.Page

public class Page : TemplateControl, IHttpHandler
{
  // Events
  public event EventHandler Init;
  public event EventHandler Load;
  public event EventHandler PreRender;
  public event EventHandler Unload;

  // Predefined event handlers
  protected virtual void OnInit(EventArgs e);
  protected virtual void OnLoad(EventArgs e);
  protected virtual void OnPreRender(EventArgs e);
  protected virtual void OnUnload(EventArgs e);
}

The Init event occurs before any server-side controls have had their state restored. The Load event occurs after all server-side controls have had their state restored but before any server-side events have been fired. The PreRender event fires after all server-side events have fired but before anything has been rendered—that is, before any HTML has been returned. The Unload event takes place after page rendering has completed.

These events give you fairly complete control over the generation of the page; however, Load is typically the most useful of all of them because it gives you a chance to modify the state of controls before rendering but after their state has been restored. The server-side control model is discussed in detail in the next chapter.

For an example of adding handlers for these events, consider the code-behind class shown in Listing 1-9. This demonstrates a technique used by Visual Studio .NET when working with code-behind files, which is to provide an overridden version of the virtual OnInit method to manually subscribe a delegate to the Load event of the Page class. In this case, the OnInit virtual function is called first, in which the delegate for the Load event is subscribed. When the Load event fires, the MyLoadHandler is invoked and prints a line of text at the top of the page since it is invoked before the page's rendering process. Note that there is no real advantage or disadvantage to either technique, except that the manual delegate subscription requires an extra step to actually hook it up.

Example 1-9. Trapping Page Events in Code-Behind

// File: EventsPage.cs
public class EventsPage : Page
{
  // Override OnInit virtual function to manually
  // subscribe a delegate to the Load event
  protected override void OnInit(EventArgs e)
  {
    this.Load += new EventHandler(MyLoadHandler);
  }

  // Load event handler
  protected void MyLoadHandler(object src, EventArgs e)
  {
    Response.Write("<tiny>rendered at top of page</tiny>");
  }
}

There is one additional mechanism for subscribing to events issued by the Page class called AutoEventWireup. This technique works by simply adding a method to your Page-derived class, named Page_Init, Page_Load, Page_PreRender, or Page_Unload, with the signature required by the EventHandler delegate. When the Page-derived class is created, one of the initialization steps it goes through uses reflection to look for any functions with these exact names. If it finds any, the initialization routine creates a new delegate initialized with that function and subscribes it to the associated event. Listing 1-10 shows a sample .aspx file that has defined a Page_Load method that is wired up using this technique. Note that this function was not a virtual function override nor was it explicitly wired up as an event handler in our code. This technique works similarly in code-behind classes as well.

Example 1-10. Using AutoEventWireup to Add an Event Handler

<!— AutoEventWireup.aspx —>
<%@ Page Language='C#' %>

<script runat=server>
  protected void Page_Load(object src, EventArgs e)
  {
    Response.Write("<h4>Load event fired!</h4>");
  }
</script>

<html>
<body>
<h1>AutoEventWireup Page</h1>
</body>
</html>

Unlike the other two mechanisms for subscribing to events, this mechanism has the disadvantage of relying on runtime type information to look up the method name and perform the event subscription, which is less efficient. If you know you are not going to take advantage of this event subscription technique, you can disable the runtime type lookup by setting the AutoEventWireup attribute of the Page directive to false, as shown in Listing 1-11. If you are building pages with Visual Studio .NET, this flag is set to false in any pages you create with the designer.

Example 1-11. Disabling AutoEventWireup

<%@ Page Language='C#' AutoEventWireup='false' %>
<!— ... —>

Shadow Copying

In our first example of the code-behind technique, we compiled an assembly containing the code-behind class and deployed it in the /bin directory of our ASP.NET application. Our page was then able to reference the class that was added to the assembly via the Inherits attribute of the Page directive. Assemblies deployed in the /bin directory are implicitly available to all pages of that application because they are added to the list of referenced assemblies during page compilation. This is a convenient mechanism not only for deploying code-behind classes, but also for deploying utility or business-logic classes that may be useful across all pages in an application.

To see an example, suppose we have built a utility class to convert temperature from Fahrenheit to centigrade and back again, as shown in Listing 1-12. To deploy this class as a utility class that would be universally accessible among all the pages within our application, we would compile it into an assembly and deploy the assembly in the /bin directory of our application. The compilation of every .aspx file in our application would then include an implicit reference to this assembly. Listing 1-13 shows a sample page using our utility class.

Example 1-12. Sample Utility Class for Temperature Conversion

// File: TempConverter.cs
public class TempConverter
{
  static public double FahrenheitToCentigrade(double val)
  {
    return ((val-32)/9)*5;
  }

  static public double CentigradeToFahrenheit(double val)
  {
    return (val*9)/5+32;
  }
}

Example 1-13. Sample Page Using the Utility Class

<!— File: TempConverter.aspx —>
<%@ Page Language='C#' %>
<html><body>
<h2>32 degrees Fahrenheit is
 <%= TempConverter.FahrenheitToCentigrade(32)%>
  degrees centigrade</h2>
</body></html>

In traditional ASP applications, components used by pages and deployed in this fashion were notoriously difficult to update or replace. Whenever the application was up and running, it held a reference to the component file; so to replace that file, you had to shut down IIS (temporarily taking your Web server offline), replace the file, and restart IIS. One of the goals of ASP.NET was to eliminate the need to stop the running Web application whenever components of that application need to be updated or replaced—that is, updating an application should be as simple as using xcopy to replace the components on the Web server with the new updated versions. To achieve this xcopy deployment capability, the designers of ASP.NET had to ensure two things: first, that the running application not hold a reference to the component file; and second, that whenever the component file was replaced with a new version, that new version was picked up with any subsequent requests made to the application. Both of these goals are achieved by using the shadow copy mechanism provided by the Common Language Runtime (CLR).

Shadow copying of assemblies is something you can configure when you create a new application domain in .NET. The AppDomainSetup class (used to initialize an AppDomain) exposes a Boolean property called ShadowCopyFiles and a string property called CachePath, and the AppDomain class exposes a method called SetShadowCopyPath() to enable shadow copying for a particular application domain. The Boolean property turns the mechanism on for a particular application domain, the CachePath specifies the base directory where the shadowed copies should be placed, and the SetShadowCopyPath() method specifies which directories should have shadow copying enabled.

ASP.NET creates a distinct application domain for each application it hosts in its worker process; and for each application domain, it enables shadow copying of all assemblies referenced in the /bin directory. Instead of loading assemblies directly from the /bin directory, the assembly loader physically copies the referenced assembly to a separate directory (also indicated in the configuration settings for that application domain) and loads it from there. This mechanism also keeps track of where the assembly came from, so if a new version of that assembly is ever placed in the original /bin directory, it will be recopied into the “shadow” directory and newly referenced from there. Figure 1-5 shows the shadow copy mechanism in action for an ASP.NET application.

Shadow Copy Mechanism Used by ASP.NET

Figure 1-5. Shadow Copy Mechanism Used by ASP.NET

In addition to shadow copying of assemblies, ASP.NET needs the ability to create and load assemblies on the fly. The first time an .aspx page is referenced, as we have seen, it is compiled into an assembly and loaded by ASP.NET. What we haven't seen is where those assemblies are located once they are compiled. Application domains also support the concept of a “dynamic directory” specified through the DynamicBase property of the AppDomainSetup class, which is a directory designed for dynamically generated assemblies that can then be referenced by the assembly loader. ASP.NET sets the dynamic directory of each application it houses to a subdirectory under the system Temporary ASP.NET Files directory with the name of the virtual directory of that application. Figure 1-6 shows the location of dynamically generated assemblies for an ASP.NET application with a virtual directory named test.

Dynamic Base Directory Used by ASP.NET

Figure 1-6. Dynamic Base Directory Used by ASP.NET

One consequence of both dynamic assembly generation and shadow copying is that many assemblies are copied during the lifetime of an ASP.NET application. The assemblies that are no longer being referenced should be cleaned up so that disk space usage doesn't become a limiting factor in application growth. In the current release of ASP.NET (version 1.0.3705) shadow copied assemblies are removed as soon as possible after a new version of that assembly is copied, and dynamically generated assemblies that are no longer used are cleaned up the next time the ASP.NET worker process is bounced and the particular application associated with the dynamic assemblies is run again. In general, this means that you shouldn't have to worry about unused assemblies generated by ASP.NET wasting space on your machine for very long.

Directives

Throughout this chapter we have used the @Page directive to control the default language, code-behind class, and implicit assembly compilation of our .aspx files. In addition to the @Page directive, several other directives are available for use in .aspx files, as shown in Table 1-1. Because every .aspx file is compiled into a class, it is important to have control over that compilation, just as you would via compiler switches if you were compiling a class yourself. These directives give you control over many options that affect the compilation and running of your page.

Table 1-1. .aspx File Directives

Directive Name

Attributes

Description

@Page

See Table 1-2

Top-level page directive

@Import

Namespace

Imports a namespace to a page (similar to the using keyword in C#)

@Assembly

Name

Src

Links an assembly to the current page when it is compiled (using src implicitly compiles the file into an assembly first)

@OutputCache

Duration

Location

VaryByCustom

VaryByHeader

VaryByParam

VaryByControl

Controls output caching for a page (see Chapter 9 for more details)

@Register

Tagprefix

Namespace

Assembly

Src

Tagname

Registers a control for use within a page (see Chapter 8 for more details)

@Implements

Interface

Adds the specified interface to the list of implemented interfaces for this page

@Reference

Page

Control

Specifies a page or user control that this page should dynamically compile and link to at runtime

The @Assembly directive provides a way for .aspx files to reference assemblies that are deployed in the global assembly cache or, using the src attribute, to reference a source file that is compiled and referenced implicitly when the page is referenced. The @Import directive serves the same purpose as the using keyword in C#, which is to implicitly reference types within a specified namespace. The @Implements directive gives you the ability to implement an additional interface in your Page-derived class, and the @Reference directive provides a mechanism for referencing the generated assemblies of other pages or user controls.

To see an example of when you might use some of these directives, suppose we decide to deploy the TempConverter class in the global assembly cache so that all the applications on our machine can access its functionality. We also wrap it in a namespace to ensure that there is no clash with other components in our system, and sign it with a public/private key pair. Listing 1-14 shows the source file for our TempConverter component.

Example 1-14. Source File for TempConverter Component

// File: TempConverter.cs
using System;
using System.Reflection;
[assembly : AssemblyKeyFile("pubpriv.snk")]
[assembly : AssemblyVersion("1.0.0.0")]

namespace EssentialAspDotNet.Architecture
{
  public class TempConverter
  {
    static public double FahrenheitToCentigrade(double val)
    {
      return ((val-32)/9)*5;
    }

    static public double CentigradeToFahrenheit(double val)
    {
      return (val*9)/5+32;
    }
  }
}

To reference the TempConverter in one of our pages, we need to reference the TempConverter assembly deployed in the GAC, and we need to fully scope the reference to the class with the proper namespace. To reference a GAC-deployed assembly, we use the @Assembly directive, and to implicitly reference the namespace in which the TempConverter class is defined, we use the @Import directive. A sample page using these directives to work with the TempConverter component is shown in Listing 1-15. Note that to successfully reference a GAC-deployed assembly, you must use the full four-part name of the assembly, including the short name, the version, the culture, and the public key token.

Example 1-15. Sample .aspx Page Using the TempConverter Component

<!— TempConverter.aspx —>
<%@ Page Language='C#' %>
<%@ Assembly Name="TempConverter, Version=1.0.0.0,
Culture=Neutral,PublicKeyToken=a3494cd4f38077bf" %>
<%@ Import Namespace="EssentialAspDotNet.Architecture" %>
<html>
<body>
<h2>32deg F =
<%=TempConverter.FahrenheitToCentigrade(32)%> deg C</h2>
</body>
</html>

The @Page directive has by far the most attributes of any of the directives. Some of these attributes have been brought forward from similar directives in traditional ASP pages, while many are new and unique to ASP.NET. Table 1-2 shows the various @Page directive attributes available, along with their possible values and a description of the attribute usage. The underlined value is the default that will be used if the attribute is left off the @Page directive.

Table 1-2. @Page Directive Attributes

Attribute

Values

Description

AspCompat

true | false

Causes this page to run on an STA thread for backward compatibility

AutoEventWireup

true | false

Determines whether events will be auto-wired up (see Chapter 2)

Buffer

true | false

Enables HTTP response buffering

ClassName

Any name

Specifies the name of the class to be generated by this file

ClientTarget

User agent or alias

Specifies the target user agent to use when accessing this page

CodePage

Code page value

Code page value for the response

CompilerOptions

String of compiler options

Compiler options to be added to the compilation command when the page is compiled

ContentType

Any HTTP type string

MIME type of response for this page

Culture

Culture name

Culture to be used when rendering this page

Debug

true | false

Whether this page is compiled with debug symbols

Description

Any text

Not used

EnableSessionState

true | false

Whether session state is used on this page (see Chapter 10)

EnableViewState

true | false

Whether view state is enabled for this page (see Chapter 2)

EnableViewStateMac

true | false

Whether ASP.NET computes a Message Authentication Code (MAC) on the page's view state (to prevent tampering)

ErrorPage

Valid URL

Target URL redirection for unhandled exceptions (see Chapter 5)

Explicit

true | false

For VB.NET, whether explicit declaration of variables is mandated

Inherits

Class name

Code-behind class from which this page will inherit

Language

Any .NET language

Default source language of this page

LCID

Locale ID

Locale ID to use for this page

ResponseEncoding

ASCIIEncoding

UnicodeEncoding

UTF7Encoding

UTF8Encoding

Response encoding for the content of this page

Src

Source file name

Source file name of the code-behind class to be dynamically compiled when the page is compiled

SmartNavigation

true | false

Enables smart navigation feature for IE 5 and higher (saves scroll position without flicker)

Strict

true | false

For VB.NET, whether option strict mode is enabled

Trace

true | false

Whether tracing is enabled for this page

TraceMode

SortByTime

SortByCategory

When tracing is enabled, how the messages are displayed

Transaction

Disabled

NotSupported

Supported

Required

RequiresNew

Whether and how this page participates in a transaction

UICulture

Any valid UI culture

UI culture setting for this page

WarningLevel

0–4

Warning level at which compilation for this page is aborted

One of the attributes that may be of particular interest to developers migrating existing ASP applications is the AspCompat attribute. This attribute changes the way a page interacts with COM objects. If you are using COM objects that were written in the single-threaded apartment (STA) model (all VB COM objects fall into this category), there will be additional overhead in invoking methods on that object because ASP.NET pages will by default run in the multithreaded apartment (MTA) when accessing COM objects. If you find that you are writing a page that has a significant number of method calls to STA-based COM objects, you should consider setting the AspCompat attribute to true to improve the efficiency of communication with those objects. Be aware that enabling this attribute also creates COM wrappers on top of the Request and Response objects enabled with ObjectContext, adding some overhead to interacting with these classes.

The ClassName attribute lets you decide the name of your Page-derived class, instead of accepting the default name, which is derived from the .aspx file name. With the CompilerOptions attribute you can specify any additional compiler switches you would like to include when your page is compiled. For example, Listing 1-16 shows a page that has requested that warnings be treated as errors when this page is compiled and that the page be compiled with overflow checking enabled for arithmetic operations.

Example 1-16. Specifying Additional Compiler Options for a Page

<%@ Page Language='C#'
    CompilerOptions="/warnaserror+ /checked+" %>

The New Intrinsics

This chapter ends with a more detailed look at the new classes provided by ASP.NET to replace the old intrinsic objects in ASP. These include the HttpRequest, HttpResponse, and HttpServerUtility classes. While a lot of the contents of these classes will be familiar to developers who have worked with the traditional ASP instrinsics, there are also several new properties and methods with which ASP.NET developers should become acquainted. Listing 1-17 shows the HttpRequest class, which takes on the responsibilities of the old Request intrinsic in ASP.

Example 1-17. HttpRequest Class

public sealed class HttpRequest
{
  public string[]                AcceptTypes     {get;}
  public string                  ApplicationPath {get;}
  public HttpBrowserCapabilities Browser         {get; set;}
  public HttpClientCertificate   ClientCertificate {get;}
  public Encoding                ContentEncoding {get;}
  public int                     ContentLength   {get;}
  public string                  ContentType     {get;}
  public HttpCookieCollection    Cookies         {get;}
  public string        CurrentExecutionFilePath  {get;}
  public string                  FilePath        {get;}
  public HttpFileCollection      Files           {get;}
  public Stream                  Filter          {get; set;}
  public NameValueCollection     Form            {get;}
  public NameValueCollection     Headers         {get;}
  public string                  HttpMethod      {get;}
  public Stream                  InputStream     {get;}
  public bool                   IsAuthenticated  {get;}
  public bool                IsSecureConnection  {get;}
  public NameValueCollection    Params           {get;}
  public string                 Path             {get;}
  public string                 PathInfo         {get;}
  public string         PhysicalApplicationPath  {get;}
  public string                 PhysicalPath     {get;}
  public NameValueCollection    QueryString      {get;}
  public string                 RawUrl           {get;}
  public string                 RequestType      {get; set;}
  public NameValueCollection    ServerVariables  {get;}
  public int                    TotalBytes       {get;}
  public Uri                    Url              {get;}
  public Uri                    UrlReferrer      {get;}
  public string                 UserAgent        {get;}
  public string                 UserHostAddress  {get;}
  public string                 UserHostName     {get;}
  public string[]               UserLanguages    {get;}
    // Methods
  public byte[] BinaryRead(int count);
  public int[]  MapImageCoordinates(string name);
  public string MapPath(string path);
  public void   SaveAs(string filename, bool includeHdrs);
}

Many properties of the HttpRequest class are type-safe accessors to underlying server variables. Although you can still access all these properties using the ServerVariables collection, as you can in traditional ASP, it is usually more convenient and type-safe to access the information using the provided property. For example, the Url property is an instance of the Uri class that provides an interface to interact with any URI. Several new properties also are available in the HttpRequest class, such as the Browser property, which provides a collection of information about the capabilities of the current client in the form of the HttpBrowserCapabilities class, the full features of which are described in Chapter 8. Another new addition is the Filter property exposed by both the HttpRequest and HttpResponse classes. With the Filter property, you can define your own custom Stream-derived class through which the entire contents of the request (or response) will pass, giving you the opportunity to change the request or response stream at a very low level. Chapter 4 further discusses request and response filters.

Similarly, the HttpResponse class is used to represent the state of the response during the processing of a request. Listing 1-18 shows the main properties and methods available in the HttpResponse class.

Example 1-18. HttpResponse Class

public sealed class HttpResponse
{
  public bool                 Buffer            {get; set;}
  public bool                 BufferOutput      {get; set;}
  public HttpCachePolicy      Cache             {get;}
  public string               CacheControl      {get; set;}
  public string               Charset           {get; set;}
  public Encoding             ContentEncoding   {get; set;}
  public string               ContentType       {get; set;}
  public HttpCookieCollection Cookies           {get;}
  public int                  Expires           {get; set;}
  public DateTime             ExpiresAbsolute   {get; set;}
  public Stream               Filter            {get; set;}
  public bool                 IsClientConnected {get;}
  public TextWriter           Output            {get;}
  public Stream               OutputStream      {get;}
  public string               Status            {get; set;}
  public int                  StatusCode        {get; set;}
  public string               StatusDescription {get; set;}
  public bool                 SupressContent    {get; set;}
    // Methods
  public void AddHeader(string name, string value);
  public void AppendCookie(HttpCookie cookie);
  public void AppendHeader(string name, string value);
  public void AppendToLog(string value);
  public void BinaryWrite(byte[] data);
  public void Clear();
  public void ClearContent();
  public void ClearHeaders();
  public void Close();
  public void End();
  public void Flush();
  public void Pics(string value);
  public void Redirect(string url);
  public void SetCookie(HttpCookie cookie);
  public void Write(string value);
  public void WriteFile(string fileName);
}

Similar to the request class, the HttpResponse class provides all the familiar methods and properties that are exposed by the traditional ASP Response object. It also provides new features that fill some holes that existed in the old ASP model. For example, the Cache property provides access to an application-wide data cache, the details of which are discussed in Chapter 9.

Finally, the server utility functions are still accessible through the Server property of a page, but the functionality is now encapsulated in the HttpServerUtility class, shown in Listing 1-19.

Example 1-19. HttpServerUtility Class

public sealed class HttpServerUtility
{
  public string MachineName {get;}
  public int ScriptTimeout {get; set;}

    // Methods
  public void ClearError();
  public object CreateObject(string obj);
  public object CreateObject(Type objType);
  public object CreateObjectFromClsid(string clsid);
  public void Execute(string url);
  public void Execute(string url, TextWriter output);
  public Exception GetLastError();
  public string HtmlDecode(string value);
  public string HtmlEncode(string value);
  public string MapPath(string path);
  public void Transfer(string url);
  public string UrlDecode(string url);
  public string UrlEncode(string url);
  public string UrlPathEncode(string url);
}

Like the other two intrinsic replacements, the HttpServerUtility class provides a mix of familiarity and new features for ASP developers. HtmlEncode and HtmlDecode provide conversions to and from HTML-compatible strings, translating characters that need escaping, such as “<”, into HTML escape sequences such as “&lt;”, and back again. Similarly, UrlEncode and UrlDecode translate characters that need escaping within a URL, such as “?” or “/”.

SUMMARY

On the surface, ASP.NET looks much like its predecessor, ASP 3.0. It supports interspersed server-side script, has the same set of intrinsics available, and provides the same ability to mix static HTML layout with dynamic server-side code. Under the covers, however, ASP.NET is dramatically different. Instead of using script interpretation, each page is now compiled in its entirety to a class that derives from System.Web.UI.Page. The entire request processing architecture is now based on a set of classes that model each aspect of a request and its response. ASP.NET introduces a new technique called code-behind that involves injecting a class in the hierarchy between the Page base class and your .aspx file–generated class, creating a clean separation of page layout from server-side code. Finally, ASP.NET solves the headache of Web application deployment by using the shadow copy mechanism in .NET to load “shadowed” versions of your component assemblies, leaving the original assemblies unlocked and available for replacement without shutting down the Web server.



[1] In IIS 6.0, which ships with Windows Server 2003, the worker process model is somewhat different and is discussed in Chapter 3. At this point, it is worth noting that unless you are running IIS 6.0 in IIS 5.0 isolation mode, ASP.NET is housed in a worker process named w3wp.exe.

[2] There are already several examples of using ASP.NET without IIS as the front-end Web server. Cassini is a sample Web server produced by Microsoft and is available with full source code at http://www.asp.net, which, among other projects, has been used to host ASP.NET with Apache.

[3] Although, as we have seen already, JScript is a fully supported .NET language and can be used in ASP.NET pages. VBScript, in contrast, is not directly supported in ASP.NET, although full-fledged Visual Basic .NET can be used.

[4] In this example, note the use of the Output property of the Response object. This is an instance of the TextWriter class, which writes to the response buffer. This class is convenient to use when you need to construct strings, because it supports the formatting of strings, which is more efficient than string concatenation.

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

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