ASP.NET 4 Unleashed
In the following sections, you learn how to create custom HTTP handlers and modules. Both HTTP handlers and modules enable you to gain low-level access to HTTP requests and responses. Later, you learn how to implement a simple statistics application for your Web site by using a custom HTTP handler and module. Working with HTTP Handlers
An HTTP handler enables you to handle all requests made for a file with a certain extension, path , or request type. You can use HTTP handlers to handle requests for ASP.NET pages or any other file type, such as image or text files. Typical uses for handlers include implementations of custom authentication schemes and custom filters. For example, you can create a handler that authenticates requests for image files. Or you can create a handler that automatically transfers requests for one file to another file.
CLASSIC ASP HTTP handlers perform many of the same functions in the ASP.NET framework as ISAPI extensions performed in traditional Active Server Pages programming.
All requests made to an ASP.NET Web site are serviced by an HTTP handler. For example, by default, all requests made for files with the extension .aspx are handled by the PageHandlerFactory class. This class produces instances of the PageHandler class to service each request for an ASP.NET page. To create your own HTTP handler, complete the following steps:
Now, create a simple HTTP handler that retrieves product information from a database table. The handler will take a URL like this: http://yourSite.com/products/product1.aspx It will retrieve information for that product from the Products database table. The first step is to implement the IHttpHandler interface, which requires you to implement one property and one method: IsReusable and ProcessRequest() . The IsReusable property indicates whether the current handler can be reused for another request. The ProcessRequest() method contains the actual code to be executed in response to the request. The Visual Basic class in Listing 15.15 implements the products handler. Listing 15.15 ProductsHandler/ProductsHandler.vb
Imports System.Data Imports System.Data.SqlClient Imports System.Web Public Class ProductsHandler Implements IHttpHandler Public Sub ProcessRequest( objContext As HttpContext ) _ Implements IHttpHandler.ProcessRequest Dim intProductID As Integer Dim conNorthwind As SqlConnection Dim strSelect As String Dim cmdSelect As SqlCommand Dim dtrProducts As SqlDataReader intProductID = GetProductID( objContext.Request.Path ) conNorthwind = New SqlConnection( _ "Server=localhost;UID=sa;PWD=secret;Database=Northwind" ) strSelect = "Select ProductName, UnitPrice From Products" & _ " Where ProductID=@ProductID" cmdSelect = New SqlCommand( strSelect, conNorthwind ) cmdSelect.Parameters.Add( "@ProductID", intProductID ) conNorthwind.Open() dtrProducts = cmdSelect.ExecuteReader( CommandBehavior.SingleRow ) If dtrProducts.Read Then objContext.Response.Write( "<h2>Product Name:</h2>" ) objContext.Response.Write( dtrProducts( "ProductName" ) ) objContext.Response.Write( "<h2>Product Price:</h2>" ) objContext.Response.Write( String.Format( _ "{0:c}", _ dtrProducts( "UnitPrice" ) ) ) End If conNorthwind.Close() End Sub ReadOnly Property IsReusable() As Boolean _ Implements IHttpHandler.IsReusable Get Return True End Get End Property Function GetProductID( strPath As String ) As Integer Dim intCounter As Integer Dim strNumbers As String For intCounter = 0 To strPath.Length - 1 If Char.IsDigit( strPath.Chars( intCounter ) ) Then strNumbers &= strPath.Chars( intCounter ) End If Next If Not strNumbers = Nothing Then Return cINT( strNumbers ) Else Return -1 End If End Function End Class The C# version of this code can be found on the CD-ROM. The class in Listing 15.15 implements both the ProcessRequest() method and IsReusable property. The ProcessRequest() method uses the GetProductID() function to strip anything but numbers from the page requests. The resulting number is used to retrieve a product with a certain product ID from the Northwind database table. If the product is found, the name and price of the product are displayed (see Figure 15.5). Figure 15.5. Output of the ProductsHandler .
The IsReusable property simply returns the value True . This is all you need to implement for a simple HTTP handler. Before you can use the handler, you need to compile it. You can compile the class in Listing 15.15 by executing the following statement from the command line:
vbc /t:library /r:System.dll,System.Data.dll,System.Web.dll ProductsHandler.vb Next, you need to copy the compiled ProductsHandler class ( ProductsHandler.dll ) to your application /bin directory. The last step is to create the proper Web.Config file to associate the handler with a set of pages. The Web.Config file in Listing 15.16 associates the ProductsHandler class with all files requested in the current directory and all its subdirectories. Listing 15.16 Web.Config
<configuration> <system.web> <httpHandlers> <add verb="*" path="*" type="ProductsHandler,ProductsHandler" /> </httpHandlers> </system.web> </configuration> The C# version of this code can be found on the CD-ROM. The Web.Config file in Listing 15.16 associates the ProductsHandler class with all files. You can limit the files associated with the handler by changing the value of the path attribute. For example, if you want to associate the handler with only those files that have a name starting with product , you would specify the path like this:
<add verb="*" path="product*.*" type="ProductsHandler,ProductsHandler" /> You can also add multiple entries for a single handler in the Web.Config file. You should be cautious about one thing : Only certain file extensions are handled by the ASP.NET framework. For example, by default, files with the extension .asp and .html are not handled by the ASP.NET framework. This means that you cannot, by default, create a handler for files with these extensions. You can associate any file with the ASP.NET framework by modifying the mapping for an extension with the Internet Services Manager. To do so, follow these steps:
You can associate any file extension with the ASP.NET framework by mapping the file extension to the aspnet_isapi.dll file. For example, if you want to write a custom handler for HTML files, you need to map the .html extension to the aspnet_isapi.dll file. Working with HTTP Modules
An HTTP module is similar to a handler in that it enables you to gain low-level access to the HTTP requests and responses processed by the ASP.NET framework. However, an HTTP module, unlike a handler, enables you to participate in the processing of every request. The ASP.NET framework includes several standard modules for managing state and implementing authentication schemes. For example, the output cache, session state, forms authentication, and Windows authentication are all implemented as modules. You can replace any of the standard modules with your own module. For example, if you don't like the way that the ASP.NET framework implements session state, you can replace the standard session state module with one of your own. Or you might decide to extend the ASP.NET framework by creating a completely new module. For example, you might want to implement a custom caching mechanism or create your own custom authentication system. Creating your own module requires completing the following two steps:
For this next example, create a simple module that implements a custom authentication scheme. The module will require that a parameter named username be passed as part of the parameters collection to a page. If the username parameter is not present, you are automatically redirected to a login page. The first step is to implement the IHttpModule interface. This interface has two required methods : Init and Dispose . Within the Init subroutine, you can initialize any variables that you need in your module and initialize event handlers with the hosting application. The Dispose event cleans up any of your module's instance variables. The module in Listing 15.17 implements both of these required methods. (You can find this module in the ModuleAuth subdirectory on the CD-ROM that accompanies this book.) Listing 15.17 ModuleAuth/AuthModule.vb
Imports System Imports System.Web Imports Microsoft.VisualBasic Namespace myModules Public Class AuthModule Implements IHttpModule Public Sub Init( ByVal myApp As HttpApplication ) _ Implements IHttpModule.Init AddHandler myApp.AuthenticateRequest, AddressOf Me.OnEnter AddHandler myApp.EndRequest, AddressOf Me.OnLeave End Sub Public Sub Dispose() _ Implements IHttpModule.Dispose End Sub Public Sub OnEnter( s As Object, e As EventArgs ) Dim objApp As HttpApplication Dim objContext As HttpContext Dim strPath As String objApp = CType( s, HttpApplication ) objContext = objApp.Context strPath = objContext.Request.Path.ToLower() If Right( strPath, 10 ) <> "login.aspx" Then If objContext.Request.Params( "username" ) = Nothing Then objContext.Response.Redirect( "login.aspx" ) End If End If End Sub Public Sub OnLeave( s As Object, e As EventArgs) End Sub End Class End Namespace The C# version of this code can be found on the CD-ROM. The file in Listing 15.17 contains the code for a module named AuthModule . In the Init subroutine, the OnEnter subroutine is associated with the Application_Authenticate event, and the OnLeave subroutine is associated with the Application_EndRequest event.
NOTE You don't actually use the OnLeave subroutine in the AuthModule module. Typically, you use OnLeave to clean up any resources that you have allocated for your module.
All the work happens in the OnEnter subroutine, which checks whether the current page is the login.aspx page. If it's not, and the parameters collection does not contain a username parameter, the user is automatically redirected to the login.aspx page. So, if you request a page named secret like this http://yourSite.com/secret.aspx you are automatically redirected to the login.aspx page. However, if you request the secret page like this http://yoursite.com/secret.aspx?username=bob you can see the secret content in secret.aspx . You need to compile the file in Listing 15.17 before you can use AuthModule module. To do so, execute the following command from the command line:
vbc /t:library /r:System.dll,System.Web.dll AuthModule.vb After you compile the module, you need to move the AuthModule.dll file to your application's /bin directory. Before you can use the module, you must add a reference to the module in the Web.Config file. The Web.Config file in Listing 15.18 contains the needed configuration settings. Listing 15.18 Web.Config
<configuration> <system.web> <httpModules> <add name="AuthModule" type="myModules.AuthModule,AuthModule" /> </httpModules> </system.web> </configuration> The C# version of this code can be found on the CD-ROM. The Web.Config file in Listing 15.18 includes an httpModules section that contains the custom AuthModule module. The value of the type attribute is the name of the class and the name of the assembly. Creating the WhosOn Application
In the following sections, you build a simple statistics application called WhosOn that uses both an HTTP handler and module. The WhosOn application displays statistics on the users of a particular page. For example, if your application contains a page at the path /myApp/Products.aspx , you can view statistics on the users who last accessed the page by requesting the page at /myApp/Products.axd (see Figure 15.6). Figure 15.6. Output of the WhosOn statistics application.
The WhosOn page lists the time, browser type, referrer, and remote IP address for the users who last accessed the page. You can configure the number of users that the application should track per page by modifying a setting in the Web.Config file. To create the WhosOn application, you need to create three files:
You learn how to create each of these files in the following sections.
NOTE You can find all the WhosOn files in the WhosOn subdirectory on the CD that accompanies this book.
Creating the WhosOn Web.Config File
You can start this example by creating the Web.Config file contained in Listing 15.19. Listing 15.19 Web.Config
<configuration> <system.web> <httpModules> <add name="WhosOnModule" type="WhosOn.WhosOnModule,WhosOnModule" /> </httpModules> <httpHandlers> <add verb="*" path="*.axd" type="WhosOn.WhosOnHandler,WhosOnHandler" /> </httpHandlers> </system.web> <appSettings> <add key="whoson" value="5" /> </appSettings> </configuration> The C# version of this code can be found on the CD-ROM. The Web.Config file in Listing 15.19 adds the WhosOnModule to the current application. It associates the WhosOnHandler with all pages that end with the extension .axd . Finally, it sets the maximum number of entries tracked by WhosOnModule to the value 5. Creating the WhosOn Module
The WhosOnModule , contained in Listing 15.20, grabs statistics for each request and adds them to the application cache. Listing 15.20 WhosOnModule.vb
Imports System Imports System.Web Imports System.Collections Imports System.Configuration Imports Microsoft.VisualBasic Namespace WhosOn Public Class WhosOnModule Implements IHttpModule Public Sub Init( ByVal myApp As HttpApplication ) _ Implements IHttpModule.Init AddHandler myApp.BeginRequest, AddressOf Me.OnEnter End Sub Public Sub Dispose() _ Implements IHttpModule.Dispose End Sub Public Sub OnEnter( s As Object, e As EventArgs ) Dim objApp As HttpApplication Dim objContext As HttpContext Dim strPath As String Dim colPageStats As Queue Dim objStatsEntry As StatsEntry Dim intMaxEntries As Integer objApp = CType( s, HttpApplication ) objContext = objApp.Context strPath = objContext.Request.Path.ToLower() ' Don't keep stats on .axd pages If Right( strPath, 4 ) = ".axd" Then Exit Sub End If strPath = strPath.SubString( 0, InstrRev( strPath, "." ) - 1 ) ' Get Max Entries From Web.Config intMaxEntries = cINT( ConfigurationSettings.AppSettings( "whoson" ) ) ' Check whether Cache object exists colPageStats = CType( objContext.Cache( "whoson_" & strPath ), Queue ) If colPageStats Is Nothing Then colPageStats = New Queue() End If ' Add Current Request to the Queue objStatsEntry = New StatsEntry( _ objContext.TimeStamp, _ objContext.Request.Browser.Type, _ objContext.Request.UserHostName, _ objContext.Request.ServerVariables( "HTTP_REFERER" ) ) colPageStats.Enqueue( objStatsEntry ) ' Delete Previous Entries If intMaxEntries <> Nothing Then While colPageStats.Count > intMaxEntries colPageStats.Dequeue() End While End If ' Update the Cache objContext.Cache( "whoson_" & strPath ) = colPageStats End Sub End Class Public Class StatsEntry Public TimeStamp As DateTime Public BrowserType As String Public UserHostName As String Public Referrer As String Public Sub New( _ TimeStamp As DateTime, _ BrowserType As String, _ UserHostName As String, _ Referrer As String ) Me.TimeStamp = TimeStamp Me.BrowserType = BrowserType Me.UserHostName = UserHostName Me.Referrer = Referrer End Sub End Class End Namespace The C# version of this code can be found on the CD-ROM. The WhosOn module is triggered by the Application_BeginRequest event. When this event fires, the OnEnter subroutine is executed. The OnEnter subroutine first checks whether the current page has the extension .axd . Because you don't want to keep statistics on the statistics pages, the subroutine is exited if the current page is a statistics page. Next, the WhosOn key from the Web.Config appSetting section is retrieved. The value of the WhosOn key is assigned to a variable named intMaxEntries . The current page statistics are then retrieved from the application cache. The page statistics are represented in a Queue collection. Next, the OnEnter subroutine grabs the time stamp of the current request, the browser type, the remote host name, and the referrer server variable. This information is stored in a custom class named StatsEntry . The StatsEntry class is added to the page statistics queue . If the queue contains more than the maximum allowable number of entries, older entries are deleted. Finally, the page statistics are inserted back into the Cache object. Before you can use the WhosOnModule , you need to compile it. You can compile the file in Listing 15.20 by executing the following statement from the command line:
vbc /t:library /r:System.dll,System.Web.dll WhosOnModule.vb After you compile the module, remember to copy the compiled WhosOnModule.dll file to the application /bin directory. Creating the WhosOn Handler
The WhosOn handler, contained in Listing 15.21, displays the statistics for a particular page. You can view the output of the WhosOn handler by requesting a page with the extension .axd . For example, a request for the products.axd page would display user statistics for a page named products.aspx . Listing 15.21 WhosOnHandler.vb
Imports System.Web Imports System.Collections Imports Microsoft.VisualBasic Namespace WhosOn Public Class WhosOnHandler Implements IHttpHandler Public Sub ProcessRequest( objContext As HttpContext ) _ Implements IHttpHandler.ProcessRequest Dim colPageStats As Queue Dim strPath As String Dim objStatsEntry As StatsEntry ' Get Page Path strPath = objContext.Request.Path strPath = strPath.SubString( 0, InstrRev( strPath, "." ) - 1 ) ' Display the Stats colPageStats = CType( objContext.Cache( "whoson_" & strPath ), Queue ) If Not colPageStats Is Nothing Then objContext.Response.Write( "<table border=1 cellpadding=4>" ) objContext.Response.Write( "<tr><td colspan=4 bgcolor=orange>" ) objContext.Response.Write( "<b>Who's On</b>" ) objContext.Response.Write( "</td></tr>" ) objContext.Response.Write( "<tr colspan=4 bgcolor=#eeeeee>" ) objContext.Response.Write( "<th>Timestamp</th>" ) objContext.Response.Write( "<th>Browser Type</th>" ) objContext.Response.Write( "<th>Remote Address</th>" ) objContext.Response.Write( "<th>Referrer</th>" ) objContext.Response.Write( "</td></tr>" ) For each objStatsEntry in colPageStats objContext.Response.Write( "<tr>" ) objContext.Response.Write( "<td>" & objStatsEntry.TimeStamp & " </td>" ) objContext.Response.Write( "<td>" & objStatsEntry.BrowserType & " </td>" ) objContext.Response.Write( "<td>" & objStatsEntry.UserHostName & " </td>" ) objContext.Response.Write( "<td>" & objStatsEntry.Referrer & " </td>" ) Next objContext.Response.Write( "</table>" ) End If End Sub ReadOnly Property IsReusable() As Boolean _ Implements IHttpHandler.IsReusable Get Return True End Get End Property End Class End Namespace The C# version of this code can be found on the CD-ROM. The WhosOn handler contains one long subroutine named ProcessRequest , which retrieves the item from the Cache object associated with the current page. If this item exists, it is a Queue collection. Next, the subroutine iterates through the contents of the Queue collection, displaying each statistics entry. Before you can use the WhosOn handler, you need to compile the file in Listing 15.21. You can compile the handler by executing the following statement from the command line:
vbc /t:library /r:System.dll,System.Web.dll,WhosOnModule.dll WhosOnHandler.vb Notice that you must reference the WhosOnModule.dll assembly because the StatsEntry class is defined there. After you compile the handler, you need to copy the WhosOnHandler.dll file to your application's /bin directory. You can test the WhosOn application by requesting any page from your Web site, for example: http://localhost/default.aspx Next, request the page using the extension .axd , rather than .aspx : http://localhost/default.axd Statistics for the default.aspx page are displayed. |