When a user at a Web browser issues a request for an ordinary HTML page, what happens is simple. The request is conveyed to some Web server, which reads the file the user has specified and sends back the HTML this file contains. This kind of simple communication is what browsers were originally designed to do.
Creating an environment that lets developers create full-fledged Web applications isn't so simple. The browser still expects the same thingHTML pages that perhaps contain some embedded code in a language such as JavaScript[1]and so the job of a Web application must be to create this. Yet like any application, software written for the Web can contain complex logic, access data, and more. Reconciling these requirements with the simple capabilities of a Web browser presents a challenge.
The goal of ASP.NET is to meet this challenge. To do this, the technology defines a specific file extension, .aspx, for Web pages. Browser requests made to Internet Information Services (IIS), Microsoft's Web server, for pages with this extension will actually be handed off to ASP.NET. Figure 5-1 shows the broad outlines of how this process works. Figure 5-1. When a browser requests a page whose URL ends with .aspx, ASP.NET executes the associated .aspx file.
As the figure suggests, a user issues a request for a page in the usual way (step 1). If the URL for this page ends in .aspx, however, IIS hands this request to an ASP.NET worker process. Each .aspx page is part of a specific ASP.NET application, so this process must determine whether this is the first time this application is being accessed. If so, the worker process creates an app domain for this ASP.NET application, then creates a standard group of context objects (step 2). The root object in this group, called HttpContext, allows access to other objects that hold particular kinds of information. Those other objects include HttpRequest, which is populated with information such as the type of browser that made this request and any cookies that accompanied this request, and HttpResponse, which is used to hold the response that's sent back to the user. Once all of this infrastructure is ready, the .aspx file the user specified is loaded and executed (step 3). The execution of this file creates a response, perhaps accessing data or doing other things along the way, then sends the response back to the browser via IIS (step 4).
Given this big-picture view of what's going on, it's possible to understand how the .aspx files at the heart of any ASP.NET application are constructed. How this is done is described next. Creating .aspx FilesAn .aspx file can contain text, HTML, and executable code. Any code in the file must either be in a script block, bracketed by the tags <script> and </script>, or be wrapped in the symbols "<%" and "%>". Here's a very simple example of an .aspx file that contains text, HTML, and a few lines of Visual Basic (VB) code: <html> The date and time: <% =Now() %> <hr> <% ShowNumbersAndBrowser() %> <script runat="server" language="vb"> Sub ShowNumbersAndBrowser() Dim I As Integer For I = 0 To 5 Response.Write(I) Next Response.Write("<hr>") Response.Write("Browser: " _ & Request.Browser.Type) End Sub </script> </html>
After the opening <html> tag, this file begins with a text string"The date and time:"followed by a call to the built-in VB function Now. This call is wrapped in <% and %>, as just described, allowing ASP.NET to distinguish this text from the literal string that precedes it. The horizontal rule tag, <hr>, appears next, followed by a call to the ShowNumbersAndBrowser method. Since it's code, this call also appears inside <% and %>.
Next comes the ShowNumbersAndBrowser method itself, defined inside a script block. The attributes in the opening <script> tag indicate that this code should be run at the server rather than at the client and that the code is written in VB. (VB is the default language for .aspx pages, so this second attribute isn't strictly required.) The ShowNumbersAndBrowser method begins with a simple loop that writes out the numbers 0 through 5. To accomplish this, that code calls the Write method of ASP.NET's built-in Response object. This object is actually a reference to the HttpResponse object shown in Figure 5-1, and it's used to contain output that will eventually be sent to the browser.
Following this simple loop is another call to Response.Write, this time outputting another <hr> tag. Because we're inside a script block, ASP.NET expects everything to be code, and so the <hr> tag can't appear on its own. Finally, one more call to Response.Write appears that writes out another text string. Concatenated with this string is the result of accessing the Browser.Type property of the built-in Request object. Request is a reference to the HttpRequest object shown in Figure 5-1, and it contains information about the request. Included in this information is the type of browser from which the request for this page was made.
What this .aspx file is doing is just creating an ordinary HTML page. When this page is accessed from Internet Explorer 6, the result is: The date and time: 12/8/2006 5:25:40 PM ____________________________________________ 012345 ____________________________________________ Browser: IE6 Just for completeness, the actual HTML that is created by this .aspx file looks like this: <html> The date and time: 12/8/2006 5:25:40 PM <hr> 012345<hr>Browser: IE6 </html> As this simple example shows, creating a basic .aspx file isn't complicated. Yet even though the contents of an .aspx file created using VB look quite different from an ordinary VB program, ASP.NET applications are .NET Framework applications, just like those seen throughout this book. They look different only because the designers of ASP.NET wanted to give developers the familiar, easy-to-use model of Web scripting. The truth is that every .aspx file is automatically turned into a class, then compiled into an assembly the first time it's accessed by a client.
Figure 5-2 gives an abstracted view of how this simple example file gets converted into a class. (What's shown here isn't literally correctthe complete truth is a bit more complicated.) The generated class's name is derived from the name of the file containing this page, and this new class inherits from the Page class defined in System.Web.UI. As the diagram shows, any code contained in a script block is inserted into the class itself. In this case, the page's simple ShowNumbersAndBrowser method becomes a method in the generated class. The rest of this page, including any text, HTML tags, and code wrapped in "<% . . . %>", gets dropped into a single Render method for this class. Figure 5-2. Each .aspx file is converted into a class.
The class is then compiled into MSIL and packaged into an assembly. What is produced isn't a static assembly, however, but rather a dynamic assembly built directly in memory using types provided by the System.CodeDom namespace. Once this dynamic assembly has been created, it's written to disk and then used to handle all future requests for this page. If the file is changed, the process happens again, and a new assembly is generated. In the absence of any changes, the original assembly is all that's needed, so only one compilation is required for each .aspx file.
Each file is turned into a class before it's executed, but how does that execution happen? The answer is that, like Windows Forms, ASP.NET uses an event-driven model. When a file is accessed, the assembly generated from that file is executed, and an instance of that assembly's page class is created. This page object receives a series of events. The object can provide a method to handle each event, and each of those methods can produce output that gets sent to the client's browser. Once all events have been handled, the page object is destroyed.
For example, every page object receives a Page_Load event, sent immediately after the page object has been created. Every page object also receives a Page_Unload event just before it is destroyed. In between these two bookend events, the page object can receive and process various other events. Sometime prior to receiving Page_Unload, every page object will receive a Render event. This event causes the object's Render method to execute and thus displays the page's output. Given that a primary goal of ASP.NET is to create effective user interfaces, and that user interfaces are by nature event driven, it makes sense to apply this model here.
Using Web ControlsEvent-driven user interfaces have always been the norm for graphical Windows applications. But along with events, there's another idea that's long been popular in building Windows graphical user interfaces (GUIs): packaging discrete chunks of reusable functionality into controls. Each control commonly provides some part of a user interface, such as a button or text box, and so controls can be combined as needed to build an effective GUI. Since ASP.NET relies on the notion of event-based programming, why not use reusable interface components to create Web GUIs as well?
This is exactly what ASP.NET's Web controls do. They're conceptually close to the Windows Forms controls described in Chapter 4 in that each one provides its own user interface and carries out its own function. Unlike Windows Forms controls, however, Web controls run on the serverthey're classes that become part of a page classand they produce their user interfaces by generating appropriate HTML[2]. It's also possible for a Web control to learn what kind of browser it's communicating with, as shown earlier, then send the appropriate outputHTML, Dynamic HTML, or something elsefor that browser.
ASP.NET provides a large set of standard Web controls. All of them inherit from the base class WebControl in the namespace System.Web.UI.WebControls. The available choices include the common atoms of GUI design, such as Button, TextBox, CheckBox, RadioButton, and ListBox. Several more complex controls are also provided. There's a Calendar control, for instance, that displays months and allows users to select dates and an AdRotator control capable of automatically cycling through a series of Web-based advertisements. There are also controls focused on working with user information, accessing data, and more, as described later. And of course, it's possible to create custom Web controls, just as there are custom Windows Forms controls. Third parties have built a large selection of these in the last few years.
Here's a simple example, once again in VB, that illustrates using Web controls: <html> <form runat="server"> <asp:Button runat="server" width="175px" height="50px" text="Click Here" onClick="Button_Click"/> <asp:Label runat="server"/> </form> <script runat="server"> Sub Button_Click(ByVal Sender As Object, _ ByVal E As System.EventArgs) Output.Text = "Button clicked" End Sub </script> </html> This .aspx file begins with a standard HTML form element that contains two Web controls: a Button and a Label. The tag identifying both controls begins with asp:, which indicates that these controls are defined by ASP.NET. Both of them, along with many others, are contained in the namespace System.Web.UI.WebControls. The Button element contains a number of attributes that indicate where the control should run, its width and height in pixels, and the text the Button should display. The element ends with the onClick attribute, specifying that a method called Button_Click should be run when this Button receives a Click event. The Label element also contains a few attributes. One of them, id, gives this Label a name so it can be referred to later in the page. This simple Label initially displays no text but is instead used to provide a way to send output to the browser, as described next.
Following the form is a short script containing just the single method Button_Click. When the button is clicked, this method will be executed. The only thing this method does is assign a value to the Label's Text property, causing it to be displayed on the screen. Figure 5-3 shows the result of loading this page and clicking the button it displays. If you recall the Windows Forms example from Chapter 4, this should look familiar. Once again, a Button control is created, its size is determined, and an event handler is associated with the Button's Click event. By using Web controls, developers can build browser-based applications in a familiar style. In fact, by analogy with Windows Forms, ASP.NET pages are sometimes referred to as Web forms. Figure 5-3. The simple .aspx file described in this section shows a button, then displays a message when the button is clicked.
ASP.NET's event-driven approach certainly is very similar to the event-driven model used in Windows Forms applications, and many Web controls even have the same names as their Windows Forms analogs. Still, there are significant differences between the controls used in Windows Forms applications and those used with ASP.NET applications. The most important difference grows out of the fact that while Windows applications handle events that were generated on the same machine, ASP.NET events are typically generated on a client machine and then are handled by a remote server system. This greatly increases the cost of raising an event, since each one results in a round trip across the network from the browser to the ASP.NET application and back. Accordingly, there are some kinds of events that just don't make sense for Web controls.
For example, both Windows Forms and ASP.NET include a Button control. Yet the number of events supported by the Web control Button is much smaller than that supported by its Windows Forms cousin. Both Buttons support an event called Click, for instance, so an application using either one can contain an event handler that runs when the control is clicked, as shown earlier. The Windows Forms control, however, also has an event called MouseMove that occurs when the mouse pointer is moved over the on-screen button, along with many other mouse-related events. None of these is available in the Web control Button. Raising each of these events on a single machine is cheap, but to send an HTTP request to the Web server every time the mouse moves over a new control in the browser would result in an unacceptably large amount of network traffic. Accordingly, Web controls are substantially more limited in the events they can accept and process than their Windows Forms brethren.
Another difference between Windows Forms controls and those used with ASP.NET is how they maintain their state. Each control has properties, such as the text it displays or its on-screen size. Windows Forms controls maintain this state in the control's memory, which is simple and efficient. Sadly, this isn't possible with Web controls. With an ASP.NET application, every object created to handle a request from a user is destroyed when that request is completedapplication scalability would suffer if this weren't doneso an application does not by default maintain any in-memory information about a client between requests. Because it retains no knowledge of the client's state between requests, an ASP.NET application is said to be stateless. This makes them more scalable, but it also creates problems. One of those problems is finding a way for Web controls to maintain their state between client requests. The solution adopted by ASP.NET is to insert each control's state into the Web page sent back to the browser. When the user submits another request, the page is sent back, and this state information is copied back into each Web control. (In ASP.NET 2.0, some control state can also be saved in other ways.)
The process is arguably inelegant, but given the limitations imposed on Web-based applications, it's an effective solution. Despite the simple example shown earlier, building browser GUIs by hand makes no more sense than building Windows GUIs by hand. Using a tool that allows creating a browser GUI graphically is a much better approach. Visual Studio 2005, for example, includes a designer referred to as Visual Web Developer that allows a developer to drag and drop Web controls on a form, set their properties, and attach code to the events they generate, just as with Windows Forms. While it's useful to know what's going on under the covers, real applications should be built using real tools whenever possible.
Separating User Interface from Code: Code-BehindOne problem with creating browser-based applications is the inescapable need to combine HTML with code written in a language such as VB or C#. Simple pages like those shown earlier in this chapter don't create much of a problem, but real applications can get hard to read and maintain when HTML and code are mingled in the same file. While ASP.NET allows doing this, as the single-file examples so far have shown, it also provides a mechanism known as code-behind for separating the GUI-oriented HTML from the code behind that GUI.
Code-behind is a straightforward idea. Rather than mixing HTML and code in a single file, this approach allows putting all of a page's HTML in its .aspx file and then inserting a reference in that file to another file that contains all of the code for that page. The result is significantly easier to work with, since the two very different worlds of HTML and a CLR-based programming language can remain distinct from one another.
Here's how the simple ASP.NET page just shown might look if the code-beside option were used. The file containing just the code (which might have the filename Example.aspx.vb) is as follows: Partial Class Example Inherits System.Web.UI.Page Sub Button_Click(ByVal Sender As Object, _ ByVal E As System.EventArgs) Output.Text = "Button clicked" End Sub End Class This is the same Button_Click method that appeared in the script block in the previous example. It's now contained inside the class Example, which is declared using the Partial keyword. As described in Chapter 3, a partial class can be combined with another partial class at compile time to create a complete, fully defined class. With code-behind, a partial class like the one above is combined with another partial class generated by ASP.NET from an .aspx file. For this simple example, that .aspx file might look like this: <%@ Page Language="VB" CodeFile="Example.aspx.vb" Inherits="Example" %> <html> <form runat="server"> <asp:Button runat="server" width="175px" height="50px" Text="Click Here" onclick="Button_Click" /> <asp:Label runat="server" /> </form> </html> This page begins with a Page directive. ASP.NET defines several such directives, each indicated by the "<%@" that precedes them. Different directives are used for different things, but the Page directive is always used to specify page-specific attributes in an .aspx file. As used here, this directive indicates that the associated code is written in VB (optional in this case, since VB is ASP.NET's default language), specifies that the associated code can be found in the file Example.aspx.vb, and that the relevant class is named Example. The rest of the page contains the same form and control definitions shown in the earlier example, defining a button and a label for output. When this page is accessed, it's compiled into a class together with the partial class Example defined in Example.aspx.vb. The result is identical to the earlier example, producing what's shown in Figure 5-3. This simple example can make code-behind seem like more trouble than it's worth. In any reasonably complex ASP.NET application, however, this approach leads to much more maintainable code, since it cleanly separates HTML from whatever CLR-based language is used to create the application logic. It's also worth mentioning that the example shown here illustrates code-behind as it's implemented in ASP.NET 2.0. The original release of this technology had a similar but slightly more complex mechanism (there were no partial classes) for doing the same thing. While that older approach is still supported, applications created today should use the style provided by this newer version of the technology.
Defining ApplicationsSo far, the term "application" has been used in a fairly general way. The truth is that ASP.NET defines quite clearly what it means to be an application. Each ASP.NET application comprises some number of .aspx pages, assemblies, and other files, all of which must be installed beneath a common directory. Each application can also optionally have application-wide logic stored in a file named global.asax. This file can contain code that handles application-wide events that are issued when the application begins executing, when it ends, when an error occurs, and at other times. Each ASP.NET application also has its own Web.config file that controls many aspects of the application's behavior. Web.config files can also be used to configure all ASP.NET applications on a particular machine, specific pages of an application, and in other ways.
Figure 5-4 shows one way in which the files that comprise a single ASP.NET application might be organized. In this example, the application consists of only two .aspx files, named page1.aspx and page2.aspx, each of which has an associated code-behind file containing logic written in VB. The application also has a Web.config file, and all of its files are contained in a directory called application1 that lives beneath the standard root directory for ASP.NET. Figure 5-4. An ASP.NET application might include .aspx files, code files, and a Web.config file.
Figure 5-5 shows another ASP.NET application. As always, all of its files live below a common root directory, here named application2. Unlike the previous example, however, this application includes a global.asax file containing event handlers for application-level events. It also includes two precompiled assemblies, both stored in a directory named bin, whose classes can be accessed at runtime by the application's .aspx files. Figure 5-5. An ASP.NET application can also include precompiled assemblies, a global.asax file, and more.
ASP.NET applications can also be structured in other ways; there are possibilities that aren't shown here. Source code written in VB, C#, or another language can be stored in a directory named App_Code, for instance, and other directories with standard names can be used for other purposes. While there's no formal connection among these various files, the fact that all live below a common root and all follow certain naming conventions groups them together in a single ASP.NET application.
Because ASP.NET applications are .NET Framework applications, installing an ASP.NET application requires just copying the application's files to the target machine. There's no need to restart IIS to begin using the new application. Deleting the application is equally simpleall that's required is deleting the application's files. It's even possible to install new versions of pages or assemblies while an ASP.NET application is running. Requests in progress will complete using the old code, while new requests will automatically use the new version.
Using Context InformationWhen a request from a client is processed, the request passes through a series of objects sometimes referred to collectively as the HTTP pipeline. These objects all need access to information about the request, so an instance of the HttpContext class, contained in the System.Web namespace, is created for each incoming request. Among other things, this object connects to the HttpRequest and HttpResponse objects shown in Figure 5-1. To access these objects and other information, the HttpContext object contains a large number of properties, including the following:
The most important properties of the HttpContext object are also accessible directly through the Page class. The properties that Page exposes include Request, Response, Session, Application, Error, and Cache, each of which is actually a way to access the corresponding property of the HttpRequest object for this request. Since Page is the parent class for every .aspx page, this information is readily accessible to every application, as the examples earlier in this chapter demonstrated.
|