All great things are simple, and many can be expressed in single words.
Each ASP.NET request goes hand in hand with a companion object for its entire lifetime—an instance of the HttpContext class. The HttpContext object wraps up all the HTTP-specific information available about the request. It is then used by the various HTTP modules and used to group references to intrinsic worker objects such as Request, Response, and Server.
In this chapter, we’ll first review the startup process of the ASP.NET application and then move on to examine the various objects that form the context of the HTTP request.
Each ASP.NET request is carried out by an ASP.NET application object. An ASP.NET application consists of an instance of the HttpApplication class that you briefly met in Chapter 2. HttpApplication is a global.asax-derived object that handles all HTTP requests directed to a particular virtual folder.
An ASP.NET running application is wholly represented by its virtual folder and, optionally, by the global.asax file. The virtual folder name is a sort of key that the HTTP runtime uses to selectively identify which of the running applications should take care of the incoming request. The global.asax file, if present, contains settings and code for responding to application-level events raised by ASP.NET or by registered HTTP modules that affect the application.
The particular HttpApplication selected is responsible for managing the entire lifetime of the request it is assigned to. That instance of HttpApplication can be reused only after the request has been completed. If no HttpApplication object is available, either because the application has not been started yet or all valid objects are busy, a new HttpApplication is created and pooled.
Although the HttpApplication provides a public constructor, user applications never need to create instances of the HttpApplication class directly. The ASP.NET runtime infrastructure always does the job for you. As mentioned, instances of the class are pooled and, as such, can process many requests in their lifetime, but always one at a time. Should concurrent requests arrive for the same application, additional instances are created. Table 16-1 lists the properties defined for the class.
Table 16-1. HttpApplication Properties
The HttpApplication is managed by the ASP.NET infrastructure, so how can you take advantage of the fairly rich, public programming interface of the class? The answer is that properties and, even more, overridable methods and class events can be accessed and programmatically manipulated in the global.asax file. (I’ll return to global.asax in a moment.)
The property Modules returns a collection of application-wide components providing ad hoc services. An HTTP module component is a class that implements the IHttpModule interface. Modules can be considered the managed counterpart of ISAPI filters; they are kind of request interceptors with the built-in capability of modifying the overall context of the request being processed. The Microsoft .NET Framework defines a number of standard modules, as listed in Table 16-2. Custom modules can be defined too. I cover this particular aspect of HTTP programming in Chapter 18.
Table 16-2. ASP.NET Modules
The list of default modules is defined in the machine.config file. By creating a proper web.config file, you can also create an application-specific list of modules. (Configuration is covered in Chapter 3.)
The methods of the HttpApplication class can be divided into two groups: operational methods and event handler managers. The HttpApplication operational methods are described in Table 16-3.
Table 16-3. HttpApplication Operational Methods
Method | Description |
---|---|
CompleteRequest | Sets an internal flag that causes ASP.NET to skip all successive steps in the pipeline and directly execute EndRequest. It’s mostly useful to HTTP modules. |
Dispose | Overridable method, cleans up the instance variables of all registered modules after the request has been served. At this time, Request, Response, Session, and Application are no longer available. |
GetOutputCacheProviderName | Overridable method, returns the currently configured provider for handling output page caching. (I’ll say more about output page caching in Chapter 18.) |
GetVaryByCustomString | Overridable method, provides a way to set output caching based on a custom string for all pages in the application. (I’ll say more about output page caching in Chapter 18.) |
Init | Overridable method that executes custom initialization code after all modules have been linked to the application to serve the request. You can use it to create and configure any object that you want to use throughout the request processing. At this time, Request, Response, Session, and Application are not yet available. |
Note that the Init and Dispose methods are quite different from well-known event handlers such as Application_Start and Application_End.
Init executes for every request directed to the Web application, whereas Application_Start fires only once in the Web application’s lifetime. Init indicates that a new instance of the HttpApplication class has been initialized to serve an incoming request; Application_Start denotes that the first instance of the HttpApplication class has been created to start up the Web application and serve its very first request. Likewise, Dispose signals the next termination of the request processing but not necessarily the end of the application. Application_End is raised only once, when the application is being shut down.
The lifetime of any resources created in the Init method is limited to the execution of the current request. Any resource you allocate in Init should be disposed of in Dispose, at the latest. If you need persistent data, resort to other objects that form the application or session state.
In addition to the operational methods in Table 16-3, a few other HttpApplication methods are available to register asynchronous handlers for application-level events. These methods are of little interest to user applications and are used only by HTTP modules to hook up the events generated during the request’s chain of execution.
Table 16-4 describes the event model of the HttpApplication class—that is, the set of events that HTTP modules, as well as user applications, can listen to and handle.
Table 16-4. HttpApplication Events
To handle any of these events asynchronously, an application will use the corresponding method whose name follows a common pattern: AddOnXXXAsync, where XXX stands for the event name. To hook up some of these events in a synchronous manner, an application will define in the global.asax event handler procedures with the following signature:
public void Application_XXX(Object sender, EventArgs e) { // Do something here }
Of course, the XXX placeholder must be replaced with the name of the event from Table 16-4. All the events in the preceding table provide no event-specific data. You can also use the following simpler syntax without losing additional information and programming power:
public void Application_XXX() { // Do something here }
In addition to the events listed in Table 16-4, in global.asax an application can also handle Application_Start and Application_End. When ASP.NET is about to fire BeginRequest for the very first time in the application lifetime, it makes Application_Start precede it. EndRequest will happen at the end of every request to an application. Application_End occurs outside the context of a request, when the application is ending.
As you saw in Chapter 2, application events are fired in the following sequence:
BeginRequest The ASP.NET HTTP pipeline begins to work on the request. This event reaches the application after Application_Start.
AuthenticateRequest The request is being authenticated. All the internal ASP.NET authentication modules subscribe to this event and attempt to produce an identity. If no authentication module produced an authenticated user, an internal default authentication module is invoked to produce an identity for the unauthenticated user. This is done for the sake of consistency so that code doesn’t need to worry about null identities.
PostAuthenticateRequest The request has been authenticated. All the information available is stored in the HttpContext’s User property.
AuthorizeRequest The request authorization is about to occur. This event is commonly handled by application code to do custom authorization based on business logic or other application requirements.
ResolveRequestCache The ASP.NET runtime verifies whether returning a previously cached page can resolve the request. If a valid cached representation is found, the request is served from the cache and the request is short-circuited, calling only any registered EndRequest handlers.
PostResolveRequestCache The request can’t be served from the cache, and the procedure continues. An HTTP handler corresponding to the requested URL is created at this point. If the requested resource is an .aspx page, an instance of a page class is created.
MapRequestHandler The event is fired to determine the request handler.
PostMapRequestHandler The event fires when the HTTP handler corresponding to the requested URL has been successfully created.
AcquireRequestState The module that hooks up this event is willing to retrieve any state information for the request. A number of factors are relevant here: the handler must support session state in some form, and there must be a valid session ID.
PostAcquireRequestState The state information (such as Application, Session) has been acquired.
PreRequestHandlerExecute This event is fired immediately prior to executing the handler for a given request. The handler does its job and generates the output for the client.
ExecuteRequestHandler The handler does its job and processes the request.
PostRequestHandlerExecute This event is raised when the handler has generated the response text.
ReleaseRequestState This event is raised when the handler releases its state information and prepares to shut down. This event is used by the session state module to update the dirty session state if necessary.
PostReleaseRequestState The state, as modified by the page execution, has been persisted. Any relevant response filtering is done at this point. (I’ll say more about this topic later.)
UpdateRequestCache The ASP.NET runtime determines whether the generated output, now also properly filtered by registered modules, should be cached to be reused with upcoming identical requests.
PostUpdateRequestCache The page has been saved to the output cache if it was configured to do so.
LogRequest The event indicates that the runtime is ready to log the results of the request. Logging is guaranteed to execute even if errors occur.
EndRequest This event fires as the final step of the HTTP pipeline. Control passes back to the HttpRuntime object, which is responsible for the actual forwarding of the response to the client. At this point, the text has not been sent yet.
If an unhandled error occurs at any point during the processing, it is treated using the code (if any) associated with the Error event. As mentioned, events can be handled in HTTP modules as well as in global.asax.
The Error event provides a centralized console for capturing any unhandled exception in order to recover gracefully or just to capture the state of the application and log it. By writing an HTTP module that just intercepts the Error event, you have a simple but terribly effective and reusable mechanism for error handling and logging. At the end of the day, this is the core of the engine of popular tools for ASP.NET error handling, logging, and reporting—ELMAH.
The global.asax file is used by Web applications to handle some application-level events raised by the ASP.NET runtime or by registered HTTP modules. The global.asax file is optional. If it is missing, the ASP.NET runtime environment simply assumes you have no application or module event handlers defined. To be functional, the global.asax file must be located in the root directory of the application. Only one global.asax file per application is accepted. Any global.asax files placed in subdirectories are simply ignored. Note that Microsoft Visual Studio doesn’t list global.asax in the items you can add to the project if there already is one.
When the application is started, global.asax, if present, is parsed into a source class and compiled. The resultant assembly is created in the temporary directory just as any other dynamically generated assembly would be. The following listing shows the skeleton of the C# code that ASP.NET generates for any global.asax file:
namespace ASP { public class global_asax : System.Web.HttpApplication { // // The source code of the "global.asax" file is flushed // here verbatim. For this reason, the following code // in global.asax would generate a compile error. // int i; // i = 2; // can't have statements outside methods // } }
The class is named ASP.global_asax and is derived from the HttpApplication base class. In most cases, you deploy global.asax as a separate text file; however, you can also write it as a class and compile it either in a separate assembly or within your project’s assembly. The class source code must follow the outline shown earlier and, above all, must derive from HttpApplication. The assembly with the compiled version of global.asax must be deployed in the application’s Bin subdirectory.
Note, though, that even if you isolate the logic of the global.asax file in a precompiled assembly, you still need to have a (codeless) global.asax file that refers to the assembly, as shown in the following code:
<%@ Application Inherits="MyApp.Global" %>
You’ll learn more about the syntax of global.asax in the next section, “Syntax of global.asax.” With a precompiled global application file, you certainly don’t risk exposing your source code over the Web to malicious attacks. However, even if you leave it as source code, you’re somewhat safe.
The global.asax file, in fact, is configured so that any direct URL request for it is automatically rejected by Internet Information Services (IIS). In this way, external users cannot download or view the code it contains. The trick that enables this behavior is the following line of code, excerpted from machine.config:
<add verb="*" path="*.asax" type="System.Web.HttpForbiddenHandler" />
ASP.NET registers with IIS to handle .asax resources, but then it processes those direct requests through the HttpForbiddenHandler HTTP handler. As a result, when a browser requests an .asax resource, an error message is displayed on the page, as shown in Figure 16-1.
When the global.asax file of a running application is modified, the ASP.NET runtime detects the change and prepares to shut down and restart the application. It waits until all pending requests are completed and then fires the Application_End event. When the next request from a browser arrives, ASP.NET reparses and recompiles the global.asax file, and again raises the Application_Start event.
A few elements determine the syntax of the global.asax file. They are application directives, code declaration blocks, server-side <object> tags, and static properties. These elements can be used in any order and number to compose a global.asax file.
The global.asax file supports three directives: @Application, @Import, and @Assembly. The @Import and @Assembly directives work as shown in Chapter 3. The @Import directive imports a namespace into an application; the @Assembly directive links an assembly to the application at compile time.
The @Application directive supports a few attributes: Description, Language, and Inherits. Description can contain any text you want to use to describe the behavior of the application. This text has only a documentation purpose and is blissfully ignored by the ASP.NET parser. Language indicates the language being used in the file. The Inherits attribute indicates a code-behind class for the application to inherit. It can be the name of any class derived from the HttpApplication class. The assembly that contains the class must be located in the Bin subdirectory of the application.
A global.asax file can contain code wrapped by a <script> tag. Just as for pages, the <script> tag must have the runat attribute set to server. The language attribute indicates the language used throughout:
<script language="C#" runat="server"> ... </script>
If the language attribute is not specified, ASP.NET defaults to the language set in the configuration, which is Microsoft Visual Basic .NET. The source code can also be loaded from an external file, whose virtual path is set in the Src attribute. The location of the file is resolved using Server.MapPath—that is, starting under the physical root directory of the Web application.
<script language="C#" runat="server" src="somecode.aspx.cs" />
In this case, any other code in the declaration <script> block is ignored. Notice that ASP.NET enforces syntax rules on the <script> tag. The runat attribute is mandatory, and if the block has no content, the Src must be specified.
The server-side <object> tag lets you create new objects using a declarative syntax. The <object> tag can take three forms, as shown in the following lines of code, depending on the specified reference type:
<object id="..." runat="server" scope="..." class="..." /> <object id="..." runat="server" scope="..." progid="..." /> <object id="..." runat="server" scope="..." classid="..." />
In the first case, the object is identified by the name of the class and assembly that contains it. In the last two cases, the object to create is a COM object identified by the program identifier (progid) and the 128-bit CLSID, respectively. As one can easily guess, the classid, progid, and class attributes are mutually exclusive. If you use more than one within a single server-side <object> tag, a compile error is generated. Objects declared in this way are loaded when the application is started.
The scope attribute indicates the scope at which the object is declared. The allowable values are defined in Table 16-5. Unless otherwise specified, the server-side object is valid only within the boundaries of the HTTP pipeline that processes the current request. Other settings that increase the object’s lifetime are application and session.
Table 16-5. Feasible Scopes for Server-Side <object> Tags
Description | |
---|---|
pipeline | Default setting, indicates the object is available only within the context of the current HTTP request |
application | Indicates the object is added to the StaticObjects collection of the Application object and is shared among all pages in the application |
session | Indicates the object is added to the StaticObjects collection of the Session object and is shared among all pages in the current session |
If you define static properties in the global.asax file, they will be accessible for reading and writing by all pages in the application:
<script language="C#" runat="server"> public static int Counter = 0; </script>
The Counter property defined in the preceding code works like an item stored in Application—namely, it is globally visible across pages and sessions. Consider that concurrent access to Counter is not serialized; on the other hand, you have a strong-typed, direct global item whose access speed is much faster than retrieving the same piece of information from a generic collection such as Application.
To access the property from a page, you must use the ASP.global_asax qualifier, shown here:
Response.Write(ASP.global_asax.Counter.ToString());
If you don’t particularly like the ASP.global_asax prefix, you can alias it as long as you use C#. Add the following code to a C#-based page (or code-behind class) for which you need to access the globals:
using Globals = ASP.global_asax;
The preceding statement creates an alias for the ASP.global_asax class (or whatever name your global.asax class has). The alias—Globals in this sample code—can be used throughout your code wherever ASP.global_asax is accepted. In ASP.NET 4, however, you can also rely on the dynamic type.
During the various steps of the request’s chain of execution, an object gets passed along from class to class—this object is the HttpContext object. HttpContext encapsulates all the information available about an individual HTTP request that ASP.NET is going to handle. The HttpContext class is instantiated by the HttpRuntime object while the request processing mechanism is being set up. Next, the object is flowed throughout the various stages of the request’s lifetime.
Before I get into the details of HttpContext and other ASP.NET intrinsic objects, I should note that in ASP.NET 4 all these objects inherit from a base class. For example, HttpContext derives from HttpContextBase and HttpResponse extends the capabilities of HttpResponseBase. The reason is to make it easier to write unit tests to check the behavior of code-behind classes. By using base classes, you can more easily create mocks of intrinsic objects and inject them into the classes. In Chapter 15, you saw an approach to testability that will benefit from base classes for intrinsic objects. Note that the ASP.NET Cache is not included in the list of objects with a base class.
Table 16-6 enumerates all the properties exposed by the HttpContext class. The class represents a single entry point for a number of intrinsic objects such as classic ASP intrinsics and ASP.NET-specific Cache and User objects.
Table 16-6. HttpContext Properties
The Current property is a frequently used static member that returns the HttpContext object for the request being processed.
The Items property is a dictionary object—a hash table, to be exact—that can be used to share information between the modules and handlers involved with the particular request. By using this property, each custom HTTP module or handler can add its own information to the HttpContext object serving the request. The information stored in Items is ultimately made available to the page. The lifetime of this information is limited to the request.
Table 16-7 lists the methods specific to the HttpContext class.
Table 16-7. HttpContext Methods
Method | Description |
---|---|
AddError | Adds an exception object to the AllErrors collection. |
ClearError | Clears all errors for the current request. |
GetAppConfig | Returns requested configuration information for the current application. The information is collected from machine.config and the application’s main web.config files. It is marked as obsolete in ASP.NET 4.0. |
Returns requested configuration information for the current request. The information is collected at the level of the requested URL, taking into account any child web.config files defined in subdirectories. It is marked as obsolete in ASP.NET 4.0. | |
GetGlobalResourceObject | Loads a global resource. |
GetLocalResourceObject | Loads a local, page-specific resource. |
GetSection | Returns requested configuration information for the current request. |
RemapHandler | Allows you to programmatically set the handler to serve the request. It must be invoked before the runtime reaches the MapRequestHandler stage. If the Handler property of HttpContext is not null at that stage, the runtime defaults to it. |
Mostly for internal use; overwrites URL and the query string of the current Request object. | |
SetSessionStateBehavior | Allows you to programmatically set the expected behavior for the session state—either read-only, read-write, or no session. It must be called before the AcquireRequestState event fires. |
Over time, the GetSection method has replaced GetConfig, which has been marked as obsolete and should not be used. If you have old code using GetConfig, just change the name of the method. The prototype is the same. Also, GetAppConfig is marked as obsolete in ASP.NET 4. It has been replaced by GetWebApplicationSection, a static member of the new WebConfigurationManager class. Also, in this case, no changes are required to be made to the prototype. Let’s spend a few more words to dig out some interesting characteristics of other methods of the HttpContext class.
The RewritePath method lets you change the URL of the current request on the fly, thus performing a sort of internal redirect. As a result, the displayed page is the one you set through RewritePath; the page shown in the address bar remains the originally requested one. The change of the final URL takes place on the server and, more importantly, within the context of the same call. RewritePath should be used carefully and mainly from within the global.asax file. If you use RewritePath in the context of a postback event, you can experience some view-state problems.
protected void Application_BeginRequest(Object sender, EventArgs e) { var context = HttpContext.Current; var o = context.Request["id"]; if (o != null) { var id = (Int32) o; var url = GetPageUrlFromId(id); context.RewritePath(url); } } protected String GetPageUrlFromId(Int32 id) { // Return a full URL based on the input ID value. ... }
The preceding code rewrites a URL such as page.aspx?id=1234 to a specific page whose real URL is read out of a database or a configuration file.
In general, IIS-level URL rewriting (which was discussed in Chapter 2) is a better alternative. The newer and more general ASP.NET Routing is perhaps better suited for a more complex use case, but it can achieve the same result pretty easily.
In Chapter 7, we discussed expressions allowed in ASP.NET pages to bind control properties to embedded global or local resources. The $Resources and meta:resourcekey expressions for global and local resources, respectively, work only at design time. What if you need to generate text programmatically that embeds resource expressions, instead? Both the Page and HttpContext classes support a pair of programmatic methods to retrieve the content of resources embedded in the application.
GetGlobalResourceObject retrieves a global resource—that is, a resource defined in an .resx file located in the App_GlobalResources special folder. GetLocalResourceObject does the same for an .resx file located in the App_LocalResources special folder of a given page.
msg1.Text = (String) HttpContext.GetGlobalResourceObject( "Test", "MyString"); msg2.Text = (String) HttpContext.GetLocalResourceObject( "/MyApp/Samples/ResPage.aspx", "PageResource1.Title");
The first parameter you pass to GetGlobalResourceObject indicates the name of the .resx resource file without an extension; the second parameter is the name of the resource to retrieve. As for GetLocalResourceObject, the first argument indicates the virtual path of the page; the second is the name of the resource.
In the all-encompassing container represented by the HttpContext object, a few popular objects also find their place. Among them are Server, Request, and Response. They are old acquaintances for ASP developers and, indeed, they are feature-rich elements of the ASP.NET programming toolkit. The set of properties and methods still makes these objects a fundamental resource for developers. Let’s learn more about them, starting with the Server object.
The functionality of the ASP intrinsic Server object in ASP.NET is implemented by the HttpServerUtility class. An instance of the type is created when ASP.NET begins to process the request and is then stored as part of the request context. The bunch of helper methods that HttpServerUtility provides are publicly exposed to modules and handlers—including global.asax, pages, and Web services—through the Server property of the HttpContext object. In addition, to maintain ASP.NET coding as close as possible to the ASP programming style, several other commonly used ASP.NET objects also expose their own Server property. In this way, developers can use in the code, say, Server.MapPath without incurring compile errors.
This class provides two properties, named MachineName and ScriptTimeout. The MachineName property returns the machine name, whereas ScriptTimeout gets and sets the time in seconds that a request is allowed to be processed. This property accepts integers and defaults to 90 seconds; however, it is set to a virtually infinite value if the page runs with the attribute debug=true, as shown here:
this.Server.ScriptTimeout = 30000000;
The ScriptTimeout property is explicitly and automatically set in the constructor of the dynamically created class that represents the page.
Table 16-8 lists all methods exposed by the HttpServerUtility class. As you can see, they constitute a group of helper methods that come in handy at various stages of page execution. The class provides a couple of methods to create instances of COM components and a few others to deal with errors. Another group of methods relates to the decoding and encoding of content and URLs.
Table 16-8. Methods of the Server Object
Description | |
---|---|
ClearError | Clears the last exception that was thrown for the request. |
CreateObject | Creates an instance of the specified COM object. |
CreateObjectFromClsid | Creates an instance of the COM object identified by the specified CLSID. The class identifier is expressed as a string. |
Passes control to the specified page for execution. The child page executes like a subroutine. The output can be retained in a writer object or automatically flushed in the parent response buffer. | |
GetLastError | Returns the last exception that was thrown. |
HtmlDecode | Decodes a string that has been encoded to eliminate invalid HTML characters. For example, it translates < into <. |
HtmlEncode | Encodes a string to be displayed in a browser. For example, it encodes < into <. |
MapPath | Returns the physical path that corresponds to the specified virtual path on the Web server. |
Transfer | Works as a kind of server-side redirect. It terminates the execution of the current page and passes control to the specified page. Unlike Execute, control is not passed back to the caller page. |
UrlDecode | Decodes a string encoded for HTTP transmission to the server in a URL. The decoded string can be returned as a string or output to a writer. |
UrlEncode | Encodes a string for HTTP transmission to a client in a URL. The encoded string can be returned as a string or output to a writer. |
UrlPathEncode | Encodes only the path portion of a URL string, and returns the encoded string. This method leaves the query string content intact. |
UrlTokenDecode | Converts a URL string token, which encodes binary data as base 64 digits, to its equivalent byte array representation. |
UrlTokenEncode | Encodes a byte array into its equivalent string representation using base 64 digits, which is usable for transmission on the URL. |
HTML and URL encoding are ways of encoding characters to ensure that the transmitted text is not misunderstood by the receiving browser. HTML encoding, in particular, replaces <, >, &, and quotes with equivalent HTML entities such as <, >, &, and ". It also encodes blanks, punctuation characters, and in general, all characters not allowed in an HTML stream. On the other hand, URL encoding is aimed at fixing the text transmitted in URL strings. In URL encoding, the same critical characters are replaced with different character entities than in HTML encoding.
The Execute method allows you to consider an external page as a subroutine. When the execution flow reaches the Server.Execute call, control is passed to the specified page. The execution of the current page is suspended, and the external page is spawned. The response text generated by the child execution is captured and processed according to the particular overload of Execute that has been used. Table 16-9 lists the overloads of the Execute method.
Table 16-9. Overloads of the Execute Method
Note that if a TextWriter object is specified, the response text of the child execution is accumulated into the writer object so that the main page output can be used later at will. Here’s some sample code:
void Page_Load(Object sender, EventArgs e) { var builder = new StringBuilder(); builder.Append("<b>Response generated before Execute is called</b><hr/>"); // Capture child content var writer = new StringWriter(); Server.Execute("child.aspx", writer); builder.Append(writer.ToString()); builder.Append("<hr/><b>Response generated after the call to Execute.</b>"); Label1.Text = builder.ToString(); }
It’s interesting to look at the internal implementation of the Execute method. Both the main and child pages are run by the same HttpApplication object as if they were the same request. What happens within the folds of Execute is a sort of context switch. First, the method obtains an HTTP handler from the application factory to serve the new request. The original handler of the main request is cached and replaced with the new handler. The spawned page inherits the context of the parent; when this step is finished, any modification made to Session or Application is immediately visible to the main page.
The handler switching makes the whole operation extremely fast, as there’s no need to create a new object to serve the request. When the child page returns, the original handler is restored. The execution of the main page continues from the point at which it was stopped, but it uses the context inherited from the child page.
ASP.NET directly calls the handler indicated by the Execute method without reapplying any authentication and authorization logic. If your security policy requires clients to have proper authorization to access the resource, the application should force reauthorization. You can force reauthorization by using the Response.Redirect method instead of Execute. When Redirect is called, the browser places a new request in the system, which will be authenticated and authorized as usual by IIS and ASP.NET. As an alternative, you can verify whether the user has permission to call the page by defining roles and checking the user’s role before the application calls the Execute method.
The Transfer method differs from the Execute method in that it terminates the current page after executing the specified page. The new page runs as if it was the originally requested one. The Transfer method has the following overloads:
public void Transfer(String); public void Transfer(String, Boolean); public void Transfer(IHttpHandler, Boolean);
The string parameter indicates the destination URL. The Boolean parameter indicates what to do with regard to the QueryString and Form collections. If the parameter is true, the collections are preserved; otherwise, they are cleared and made unavailable to the destination page (which is the recommended approach). You can also directly indicate the HTTP handler to invoke, with the same security issues that were mentioned for Execute.
All the code that might be following the call to Transfer in the main page is never executed. In the end, Transfer is just a page redirect method. However, it is particularly efficient for two reasons. First, no roundtrip to the client is requested, as is the case, for example, with Response.Redirect. Second, the same HttpApplication that was serving the caller request is reused, thus limiting the impact on the ASP.NET infrastructure.
In ASP.NET, the HTTP response information is encapsulated in the HttpResponse class. An instance of the class is created when the HTTP pipeline is set up to serve the request. The instance is then linked to the HttpContext object associated with the request and exposed via the Response property. The HttpResponse class defines methods and properties to manipulate the text that will be sent to the browser. Although user-defined ASP.NET code never needs to use the HttpResponse constructor, looking at it is still useful to get the gist of the class:
public HttpResponse(TextWriter writer);
As you can see, the constructor takes a writer object, which will then be used to accumulate the response text. All calls made to Response.Write (and similar output methods) are resolved in terms of internal calls to the specified writer object.
All properties of the class are grouped and described in Table 16-10. You set a few of these properties to configure key fields on the HTTP response packet, such as content type, character set, page expiration, and status code.
Table 16-10. HttpResponse Properties
Let’s find out more about cache and expiration properties.
The response object has three properties dedicated to controlling the ability of the page being sent to the browser to be cached. The Expires and ExpiresAbsolute properties define relative and absolute times, respectively, at which the page cached on the client expires and is no longer used by the browser to serve a user request. In fact, if the user navigates to a currently cached page, the cached version is displayed and no roundtrip occurs to the server. A third property somehow related to page caching is CacheControl. The property sets a particular HTTP header—the Cache-Control header. The Cache-Control header controls how a document is to be cached across the network. These properties represent the old-fashioned programming style and exist mostly for compatibility with classic ASP applications.
In ASP.NET, all caching capabilities are grouped in the HttpCachePolicy class. With regard to page caching, the class has a double role. It provides methods for both setting cache-specific HTTP headers and controlling the ASP.NET page output cache. In this chapter, we’re mostly interested in the HTTP headers, and we’ll keep page output caching warm for Chapter 18.
To set the visibility of a page in a client cache, use the SetCacheability method of the HttpCachePolicy class. To set an expiration time, use the SetExpires method, which takes for input an absolute DateTime object. Finally, to set a lifetime for the cached page, pass to SetExpires the current time plus the desired interval.
In the case of conflicting cache policies, ASP.NET maintains the most restrictive settings. For example, if a page contains two controls that set the Cache-Control header to public and private, the most restrictive policy will be used. In this case, Cache-Control: Private is what will be sent to the client.
In ASP.NET, a new component makes its debut—the response filter. A response filter is a Stream-derived object associated with the HttpResponse object. It monitors and filters any output being generated by the page. If you set the Filter property with the instance of a class derived from Stream, all output being written to the underlying HTTP writer first passes through your output filter.
The custom filter, if any, is invoked during the HttpResponse’s Flush method before the actual text is flushed to the client. An output filter is useful for applying the final touches to the markup, and it is sometimes used to compact or fix the markup generated by controls.
Building a response filter is a matter of creating a new stream class and overriding some of the methods. The class should have a constructor that accepts a Stream object. In light of this, a response filter class is more a wrapper stream class than a purely inherited stream class. If you simply try to set Response.Filter with a new instance of, say, MemoryStream or FileStream, an exception is thrown.
The following listing shows how to create a stream class that works as a response filter. For simplicity, the class inherits from MemoryStream. You might want to make it inherit from Stream, but in this case you need to override (because they are abstract) a number of methods, such as CanRead, CanWrite, CanSeek, and Read. The class converts lowercase characters to uppercase ones.
public class MyFilterStream : MemoryStream { private Stream m_Stream; public MyFilterStream(Stream filterStream) { m_Stream = filterStream; } // The Write method actually does the filtering public override void Write(byte[] buffer, int offset, int count) { // Grab the output as a string string buf = UTF8Encoding.UTF8.GetString(buffer, offset, count); // Apply some changes // Change lowercase chars to uppercase buf = buf.ToUpper(); // Write the resulting string back to the response stream byte[] data = UTF8Encoding.UTF8.GetBytes(buf.ToString()); m_Stream.Write(data, 0, data.Length); } }
Use the following code to associate this output filter with the Response.Filter property. Here’s a sample page:
void Page_Load(object sender, EventArgs e) { Response.Filter = new MyFilterStream(Response.Filter); }
Response filters provide an interesting opportunity for developers to build more powerful applications, but I caution you to be careful when considering this option. As the sample demonstrates, changing the case of the entire output is not a smart move. If done without care, the change ends up affecting the view state and the internal script code, both of which consist of case-sensitive text, seriously compromising the functionality of the page. Second, filters must be activated on a per-page basis. If you need to filter all the pages in a Web site, you’re better off writing an HTTP module.
Table 16-11 lists all the methods defined on the HttpResponse class.
Table 16-11. HttpResponse Methods
Method | Description |
---|---|
AddCacheDependency | Adds an array of cache dependencies to make the cached page output invalid if any dependency gets broken. In the array, you can have any class that inherits from CacheDependency. |
AddCacheItemDependencies | Adds an array of strings representing names of items in the ASP.NET Cache. When any of the specified items vary, the cached page output becomes invalid. |
AddCacheItemDependency | Description is the same as for the previous item, except that AddCacheItemDependency adds a single cache item name. |
AddFileDependencies | Adds a group of file names to the collection of file names on which the current page is dependent. When any of the files are modified, the cached output of the current page is deemed invalid. |
AddFileDependency | Adds a single file name to the collection of file names on which the current page is dependent. If the file is modified, the cached output of the current page becomes invalid. |
AddHeader | Adds an HTTP header to the output stream. It is provided for compatibility with previous versions of ASP. In ASP.NET, you should use AppendHeader. |
AppendCookie | Adds an HTTP cookie to the cookie collection. |
AppendHeader | Adds an HTTP header to the output stream. |
AppendToLog | Adds custom log information to the IIS log file. |
ApplyAppPathModifier | Adds a session ID to the specified virtual path, and returns the result. It is mostly used with cookieless sessions to construct absolute HREFs for hyperlinks. |
BinaryWrite | Writes binary characters to the HTTP output stream. It is subject to failures with very large files. (See the references to this method later in the chapter.) |
Clear | Clears all content output from the buffer stream. |
ClearContent | Calls into Clear. |
ClearHeaders | Clears all headers from the buffer stream. |
Close | Closes the socket connection with the client. |
DisableKernelCache | Disables kernel caching for the current response. If kernel caching is not supported, the method has no effect. |
End | Sends all buffered text to the client, stops execution, and raises the end event for the request. |
Flush | Sends all currently buffered output to the client. |
Pics | Appends a PICS-Label HTTP header to the output. PICS stands for Platform for Internet Content Selection and is a World Wide Web Consortium (W3C) standard for rating pages. Any string is acceptable as long as it doesn’t exceed 255 characters. |
Redirect | Redirects a client to a new URL. It needs a roundtrip. The browser receives an HTTP 302 status code, meaning that the resource has been temporarily moved. |
RedirectPermanent | Redirects a client to a new URL. It needs a roundtrip. The browser receives an HTTP 301 status code, meaning that the resource has been permanently moved to a new location. |
RedirectToRoute | Redirects a client to a URL specified as a route. The method works if Web Forms routing is used to specify routes. |
RemoveOutputCacheItem | A static method that takes a file system path and removes from the cache all cached items associated with the specified path. |
SetCookie | Updates an existing cookie in the cookie collection. |
TransmitFile | Just like BinaryWrite and WriteFile, it writes the specified file directly to the output stream. You can safely use TransmitFile regardless of the size of the file that you want to transmit. |
Write | Writes content to the underlying output stream. The method can write a string, a single character, or an array of characters, as well as an object. In this case, though, what gets written is the output of the object’s ToString method. |
Writes the specified file (or a portion of it) directly to the output stream. The file can be identified with its path or a Win32 handle (an IntPtr object). It is subject to failures with very large files. (See the references to this method later in the chapter.) | |
WriteSubstitution | Allows fragments of a page to be substituted and sent to the output cache. (We’ll cover this method in more detail in Chapter 18.) |
The HttpResponse class has several methods to make the page response it represents dependent on files or cache item changes. The methods AddFileDependency and AddCacheItemDependency (and their versions that handle multiple dependencies) make the page response invalid when the specified file or files or cached item or items are modified.
This is a simple form of programmatic page output caching, not as powerful as the API that we’ll examine in Chapter 18, but still worth a look. The API discussed in Chapter 18 is superior because it allows you to control how the page response is cached, assigning also the cached output a duration and perhaps a location.
The method AddCacheDependency completes the offering, as it gives you the possibility to make the page response dependent on any dependency object available to your application, including custom dependency objects. See Chapter 18 for more details on custom dependency objects.
As you can see, there are three methods for writing potentially large chunks of data down to the output stream: BinaryWrite, WriteFile, and TransmitFile. Of the three methods, TransmitFile is the most stable and reliable, although you won’t notice any significant difference for most files.
Both the WriteFile and BinaryWrite methods seem perfect for streaming binary data down to the client. However, both can put the Web server memory under pressure if called to work on very large files. Why? It’s because both methods load the entire data block (the contents of the file or the byte array) into the Web server’s memory. For large files, this can cause severe problems that can culminate in the recycling of the ASP.NET process. The TransmitFile method is designed to elegantly work around the problem. It sends output directly from a file to the ASP.NET ISAPI extension and then down to the client, without passing a humongous string to the ISAPI extension.
Although TransmitFile makes large file downloads more stable than ever and fixes the problem of recycling, it is far from being a full solution to the problem of tracking and resuming large file downloads. For example, if a download fails, for whatever reason, TransmitFile can start it again only from the beginning. The article found at the following Web site discusses a better approach to the problem: http://www.devx.com/dotnet/Article/22533.
The HttpRequest object groups all the information contained in the HTTP packet that represents the incoming Web request. The contents of the various HTTP headers, the query string, or the form’s input fields, path, and URL information are organized in a series of collections and other ad hoc objects for easy and effective programmatic access. The HttpRequest object is populated as soon as ASP.NET begins working on a Web request, and it’s made available through the Request property of HttpContext.
HttpRequest exposes a fair number of properties and is one of the objects that has been more significantly enriched in the transition from ASP to ASP.NET.
The class properties can be categorized into three groups based on the type of information they contain: the type of the request, client data, and connection.
Table 16-12 lists the properties that define the type of request being issued.
Table 16-12. Properties Describing the Request Type
The anonymous ID is usually transmitted through a cookie (whose default name is .ASPXANONYMOUS) and serves the purpose of giving an identity to nonauthenticated users, mainly for user profile functions. The anonymous ID is a GUID and is transmitted as clear text. It doesn’t play any relevant role with authentication and security; it is merely a way to track nonregistered users as they move around the site. (See Chapter 7 for profiles and Chapter 19, for user authentication.)
Initially, CurrentExecutionFilePath and FilePath share the same content—the requested URL. However, in cases of server-side redirects, the value of CurrentExecutionFilePath is automatically updated. You should check CurrentExecutionFilePath for up-to-date information about the target URL.
The HttpBrowserCapabilities object groups in a single place values that identify a fair number of browser capabilities, including support for ActiveX controls, scripting languages, frames, cookies, and more. When the request arrives, the user agent information is used to identify the requesting browser and an instance of the HttpBrowserCapabilities class is created and populated with browser-specific information. The information is in no way dynamically set by the browser; instead, it is retrieved from an offline server-side repository.
Table 16-13 lists the HttpRequest properties that expose the client data that ASP.NET pages might want to use for server-side processing. The following table includes, for example, cookies, forms, and query string collections.
Table 16-13. Properties Describing the Client Data
The Params collection combines four different but homogeneous collections—QueryString, Form, ServerVariables, and Cookies—and it replicates the information contained in each of them. The collections are added in the following order: QueryString, Form, Cookies, and finally ServerVariables.
Table 16-14 lists the properties that relate to the open connection.
Table 16-14. Properties Describing the Connection
The Uri class provides an object representation of a Uniform Resource Identifier (URI)—a unique name for a resource available on the Internet. The Uri class provides easy access to the parts of the URI as well as properties and methods for checking host, loopback, ports, and DNS.
The server variables set in the ServerVariables collection are decided by the run-time environment that processes the request. The information packed in the collection is, for the most part, excerpted from the HTTP worker request object; another part contains Web server–specific information. The ServerVariables collection is just a friendly name/value model to expose that information.
Table 16-15 lists all methods exposed by the HttpRequest class.
Table 16-15. HttpRequest Methods
The SaveAs method lets you create a file to store the entire content of the HTTP request. Note that the storage medium can only be a disk file; no stream or writer can be used. Because ASP.NET by default isn’t granted write permissions, this method causes an access-denied exception unless you implement ad hoc measures. Granting the ASP.NET account full control over the file to be created (or over the whole folder) is one of the possible ways to successfully use the SaveAs method. The following listing shows possible content that SaveAs writes to disk:
GET /MyApp/Samples/Ch14/Misc/TestFilter.aspx HTTP/1.1 Connection: Keep-Alive Accept: */* Accept-Encoding: gzip, deflate Accept-Language: it,en-us;q=0.5 Cookie: .ASPXANONYMOUS=AGzHqyVAyAEkAAAAO ... MWE3YZreWoYt-jkSc_RwU169brWNTIw1 Host: localhost:1066 User-Agent: ... UA-CPU: x86
If the intercepted request is a POST, you’ll find posted values at the bottom of the string.
A golden rule of Web security claims that all user input is evil and should always be filtered and sanitized before use. The @Page directive has an attribute—ValidateRequest—that automatically blocks postbacks that contain potentially dangerous data. This feature is not the silver bullet of Web input security, but it helps detect possible problems. From a general security perspective, you’re better off replacing the automatic input validation with a strong, application-specific validation layer.
The automatic input validation feature—ValidateRequest—is enabled by default and implemented via a call to the HttpRequest’s ValidationInput method. ValidateInput can be called by your code if the validation feature is not enabled. Request validation works by checking all input data against a hard-coded list of potentially dangerous data. The contents of the collections QueryString, Form, and Cookies are checked during request validation.
In this chapter, we covered some basic objects that are the foundation of ASP.NET programming: Server, Response, Request, and others. An ASP.NET application is represented by an instance of the HttpApplication class properly configured by the contents of the global.asax file. And both the HttpApplication class and the global.asax file found their space in this chapter too.
While discussing the interface of the objects that generate the context of an HTTP request, we reviewed in detail some specific programming issues, such as server-side page redirection and the setup of response filters. In the next chapter, we’ll discuss an important topic related to Web applications and ASP.NET—state management. Fundamentally, Web applications are stateless, but ASP.NET provides various mechanisms for maintaining application state and caching pages.
In ASP.NET 4, all intrinsic objects (except Cache) have been derived from a new base class to give developers better chances to be able to write testable Web pages.
3.149.236.27