4.5 Custom Modules


The last and perhaps most powerful point of extensibility in the HTTP pipeline is the module. Modules [10] live at the application scope and can tap into any of the HttpApplication events. They are created the first time the application is created and exist throughout the lifetime of the application. Modules are typically used to perform pre- or postprocessing on requests , similar in many ways to ISAPI filters in IIS. ASP.NET itself uses modules for a number of application-level features, including authentication, authorization, output caching, and out-of-process session state management. Table 4-4 shows the various system-provided modules and the services they provide.

[10] Note that the modules we are discussing in this section ( HttpModules ) are completely distinct from and unrelated to Modules in VB.NET.

Note that all these services require hooks into the pipeline before the request is issued to a handler. This is a common requirement of a module, to be able to tap into the request processing pipeline and potentially alter, abort, or otherwise change the current request.

To construct your own module, you begin by building a class that implements the IHttpModule interface, shown in Listing 4-18. This interface consists of two methods , Init() and Dispose() . The Init method is called when the module is first created, and it takes as a parameter a reference to the current HttpApplication object. Typically, a module uses the application class to register delegates to some of the events exposed by HttpApplication . The Dispose method is called when the application is being closed, giving modules an opportunity to perform any cleanup necessary.

Table 4-4. Modules Defined in ASP.NET

Module

Purpose

OutputCacheModule

Page-level output caching

SessionStateModule

Out-of-process session state management

WindowsAuthenticationModule

Client authentication using integrated Windows authentication

FormsAuthenticationModule

Client authentication using cookie-based forms authentication

PassportAuthenticationModule

Client authentication using MS Passport

UrlAuthorizationModule

Client authorization based on requested URL

FileAuthorizationModule

Client authorization based on requested file

Listing 4-18 IHttpModule Interface
 Public Interface IHttpModule   Sub Dispose()   Sub Init(context As HttpApplication) End Interface 

To see an example, let's build a module that tracks the request processing time for all requests serviced by an application. The logic for this class is identical to our earlier implementation of tracking request time through the global.asax file, and in fact, the capabilities of global.asax and modules largely overlap. We discuss how best to choose which technique to use in the next section. Listing 4-19 shows our TimerModule class, which will keep track of the start request time in the Items collection of the HttpContext class, as we did in our global.asax example. This time, for variety, instead of printing the timing results to the response buffer, we create a custom header that contains the timing information. [11]

[11] Thanks go to Aaron Skonnard, who first showed me this timer module example.

Listing 4-19 Sample Module to Collect Request Timing Information
 ' File: TimerModule.vb Public Class TimerModule        Implements IHttpModule   Public Sub Init(httpApp As HttpApplication) _              Implements IHttpModule.Init     AddHandler httpApp.BeginRequest, _                New EventHandler(AddressOf OnBeginRequest)     AddHandler httpApp.EndRequest, _                New EventHandler(AddressOf OnEndRequest)   End Sub   Public Sub OnBeginRequest(o As Object, e As EventArgs)     ' source object is always associated HttpApplication     Dim httpApp As HttpApplication     httpApp = CType(o, HttpApplication)     ' record time that event was handled in per-request     ' Items collection     httpApp.Context.Items("began") = DateTime.Now   End Sub   Public Sub OnEndRequest(o As Object, e As EventArgs)     ' source object is always associated HttpApplication     Dim httpApp As HttpApplication     httpApp = CType(o, HttpApplication)     ' retrieve time of BeginRequest event from per-request     ' Items collection     Dim st As DateTime     st = CType(httpApp.Context.Items("began"), DateTime)     ' measure time between BeginRequest event and current     Dim elapsed As TimeSpan = DateTime.Now.Subtract(st)     ' send elapsed time back to client via custom header     httpApp.Context.Response.AppendHeader("ElapsedTime", _                              elapsed.ToString())   End Sub   Public Sub Dispose() Implements IHttpModule.Dispose   End Sub End Class 

Now, to deploy this module, the class must be compiled into an assembly, deployed in the /bin directory of the application (or in the GAC), and an entry must be made in the web.config file of the application under the httpModules element. Listing 4-20 shows a sample web.config file that would register this module, assuming we compiled the class into an assembly named TimerModule . Note that the add element takes a name and a type for attributes, where the name is some application-unique string identifying the module, and the type is the class name (fully namespace qualified) followed by the assembly name .

Listing 4-20 Timer Module Configuration
 <!-- File: web.config --> <configuration>   <system.web>     <httpModules>       <add name="Timer"            type="TimerModule, TimerModule" />     </httpModules>   </system.web> </configuration> 

4.5.1 Modules as Filters

In addition to monitoring requests and supplementing responses, modules can be used to filter both requests and responses. Filtering is useful if you want to modify the incoming request before it reaches its handler or to change the outgoing response as it is being written. Filters can be useful for adding things like common footers to all pages processed by an application or even stripping off portions of a response before sending it back to the client.

To build a module that acts as a filter, you need to construct a new Stream -derived class and at the beginning of each request ( Application_BeginRequest ), create a new instance of your Stream -derived class and assign it to the Filter property of either the Request or the Response object. From this point on, all writes to the Response stream (or reads from the Request stream) will use your new stream class. The tricky part in getting a filter working properly is that you need to keep the old Request or Response stream around, cached in your custom stream, so that you can read from or write to the actual request or response stream when you need to. Your new stream class thus acts as a filter sitting between the original stream and any reads or writes. This relationship is shown in Figure 4-4.

Figure 4-4. Filtering with HttpModules

graphics/04fig04.gif

To see an example of building a filtering module, suppose we wanted to alter the outgoing response of all requests made to an application by stripping off some portion of the response and logging it to a file. As we will see in the next chapter, there is a compelling reason to build such a filtering module. One of the diagnostic features of ASP.NET, called tracing, augments the output of any given page with supplemental information about the request. This is extremely useful information, but there is no way to tell ASP.NET to write that trace information to a file instead of directly to the Response stream. So for our example, we will build an HttpModule that monitors all responses, looking for the beginning of the trace output of a response (if there is one). Once the trace output is detected , we will stop passing it along to the Response stream and instead redirect the output to a file stream to store on disk.

Our first task is to build the custom stream class that sits between any calls to Response.Write() and the actual Response stream. This class, which we call TraceRedirectStream , is shown in Listing 4-21. The first thing to note about this custom stream class is that it encapsulates two streams as data members . The first, called _primaryStream , is initialized to the actual Response stream. The second, called _otherStream , is initialized to a file stream to which the trace information is to be written. The constructor of our custom stream class takes the two streams as parameters, as well as the request URL so that it can document what request generated the trace information. Many of the Stream functions of this class simply forward along to the _primaryStream data member, so that it behaves like the standard Response stream in most ways. The interesting functions are Write and FindTraceStartTag . The purpose of FindTraceStartTag is to scan the text being written for the string that indicates the beginning of the trace output. This function is then used within the Write method to determine which stream to write to. The first time the trace start tag is encountered , we write out a header to the file stream to document the request URL, and then all subsequent writes to our stream are sent to the file stream and not to the original Response stream.

Listing 4-21 Custom Stream Class to Redirect Trace Output
 Public Class TraceRedirectStream        Inherits Stream   Private _primaryStream As Stream   Private _otherStream   As Stream   Private _inTrace       As Boolean = False   Private _requestUrl    As String   ' Signals the start of the trace information in a page   Private Const _cStartTraceStringTag As String  = _         "<div id=" & Chr(34) & "__asptrace" & Chr(34) & ">"   Public Sub New(primaryStream As Stream, _                  otherStream As Stream, _                  requestUrl As String)     _primaryStream = primaryStream     _otherStream   = otherStream     _requestUrl    = requestUrl   End Sub   Private Sub WriteLine( s As Stream, format As String, _                          ParamArray args() As Object)     Dim text As String     text = String.Format(format + Environment.Newline, args)     Dim textBytes() As Byte = Encoding.ASCII.GetBytes(text)     s.Write(textBytes, 0, textBytes.Length)   End Sub   Public Overrides ReadOnly Property CanRead As Boolean     Get       Return _primaryStream.CanRead     End Get   End Property   Public Overrides ReadOnly Property CanSeek As Boolean     Get       Return _primaryStream.CanSeek     End Get   End Property   Public Overrides ReadOnly Property CanWrite As Boolean     Get       Return _primaryStream.CanWrite     End Get   End Property   Public Overrides ReadOnly Property Length As Long     Get       Return _primaryStream.Length     End Get   End Property   Public Overrides Property Position As Long     Get       Return _primaryStream.Position     End Get     Set (value As Long)       _primaryStream.Position = _                      _otherStream.Position = value     End Set   End Property   Public Overrides Function Seek(offset As Long, _                         direction As SeekOrigin) _          As Long     Return _primaryStream.Seek(offset, direction)   End Function   Public Overrides Sub SetLength(length As Long)       _primaryStream.SetLength(length)   End Sub   Public Overrides Sub Close()     _primaryStream.Close()     _otherStream.Close()   End Sub   Public Overrides Sub Flush()     _primaryStream.Flush()     _otherStream.Flush()   End Sub   Public Overrides Function Read( buffer() As Byte , _                                   offset As Integer, _                                   count As Integer ) _                    As Integer     Return _primaryStream.Read(buffer, offset, count)   End Function   Public Overrides Sub Write(buffer() As Byte, _                              offset As Integer, _                              count As Integer )     If _inTrace Then     ' if we are writing out trace information, it is always     ' the last part of the output stream, so just continue     ' writing to the log file until the request completes.       _otherStream.Write(buffer, offset, count)     Else     ' We are not currently writing out trace information, so     ' as we write response information, look for the trace     ' start string, and begin writing to the trace log if we     ' encounter it. Scan the entire buffer looking for the     'trace start tag.       Dim idx As Integer       idx = FindTraceStartTag(buffer, offset, count)       If idx > 0 Then ' if non-negative, found start tag         WriteLine(_otherStream, _                   "<hr/><h3>Request URL: {0}</h3>", _                   _requestUrl)         _inTrace = true        ' write non-trace portion of buffer to primary stm         _primaryStream.Write(buffer, offset, idx)         ' write trace portion to other stream (log)         _otherStream.Write(buffer, idx+offset, count - idx)       End If     End If   End Sub   Public Overrides Function ReadByte() As Integer     Dim b As Integer = _primaryStream.ReadByte()     _otherStream.Position = _primaryStream.Position     Return b   End Function   Public Overrides Sub WriteByte(b As Byte)     If Me._inTrace Then       _otherStream.WriteByte(b)     Else       _primaryStream.WriteByte(b)     End If   End Sub   Private Function FindTraceStartTag(buffer As Byte(), _                                offset As Integer, _                                count As Integer) As Integer     Dim bufIdx As Integer = offset     Dim ret As Integer = -1     Do While (bufIdx < count+offset) And (ret < 0)       If buffer(bufIdx) = _              Val(TraceRedirectStream._                  _cStartTraceStringTag.Chars(0)) Then         Dim i As Integer = 1         Do While (i < TraceRedirectStream._                       _cStartTraceStringTag.Length) _                  And (bufIdx+i < count+offset)           If Not buffer(bufIdx+i) = _                 Val(TraceRedirectStream._                     _cStartTraceStringTag.Chars(i)) Then             Exit Do           End If           i += 1         Loop         If i >= TraceRedirectStream._                 _cStartTraceStringTag.Length Then           ret = bufIdx         End If       End If       bufIdx += 1     Loop     Return ret   End Function End Class 

Now we are left with the task of installing this stream in place from a module. Our custom HttpModule adds handlers for the BeginRequest and EndRequest events so that our custom stream can be installed and closed when the request is complete. The installation of the stream involves creating a new instance and assigning it to the Filter property of the Response object with each new request. This custom module is shown in Listing 4-22.

Listing 4-22 TraceDumpModule Class
 Public Class TraceDumpModule        Implements IHttpModule   Private _responseStream As TraceRedirectStream   Private _logFileName    As String   Public Sub Init( httpApp As HttpApplication) _              Implements IHttpModule.Init     _logFileName = "C:\temp\tracelog.htm"     AddHandler httpApp.BeginRequest, _                New EventHandler(AddressOf OnBeginRequest)     AddHandler httpApp.EndRequest, _                New EventHandler(AddressOf OnEndRequest)   End Sub   Sub OnBeginRequest( sender As Object, e As EventArgs)     Dim httpApp As HttpApplication     httpApp = CType(sender, HttpApplication)     Dim fiLogFile As FileInfo     If  File.Exists(_logFileName) Then         fiLogFile = New FileInfo(_logFileName)     End If     ' Open the log file (for appending) and log     ' any trace output made to that request.     '     Dim responseLog As Stream     responseLog = File.Open( _logFileName, _                              FileMode.Append, _                              FileAccess.Write )     Dim pos As Long = httpApp.Request.InputStream.Position     CopyStream(httpApp.Request.InputStream, responseLog)     httpApp.Request.InputStream.Position = pos     ' Set the response filter to refer to the trace redirect     ' stream bound to the original response stream and the     ' log file. As this stream processes the response data,     ' it will selectively send non-trace output to the     ' original stream, and trace output to the log file     '     _responseStream = _        New TraceRedirectStream(httpApp.Response.Filter, _                              responseLog, _                              httpApp.Request.Url.ToString())     httpApp.Response.Filter = _responseStream   End Sub   Sub OnEndRequest( sender As Object, e As EventArgs)     If Not _responseStream Is Nothing Then       _responseStream.Close()     End If   End Sub   Sub CopyStream( inStream As Stream, outStream As Stream )     Dim buf(128) As Byte     Dim bytesRead As Integer = 0     Do       bytesRead = inStream.Read(buf, 0, buf.Length)       outStream.Write(buf, 0, bytesRead)     Loop While bytesRead > 0   End Sub   Public Sub Dispose() Implements IHttpModule.Dispose   End Sub End Class 

4.5.2 Module Pooling

Unlike handlers, which are pooled only when you create custom handlers whose IsReusable property returns true , modules are always pooled. Each time a new instance of the HttpApplication -derived class is created, a new set of modules is created to accompany it. Thus, the advice given earlier for application classes holds true for custom modules as well ”save no state between requests.

4.5.3 Modules versus global.asax

As noted earlier, all the functionality of a module can also be implemented in the application-specific global.asax file. The converse is not true, however, because global.asax provides several additional events and features that are not available to modules, such as the session start event or the ability to use the object tag to declare application or session scoped objects. For many tasks at the application level, however, it is not always clear whether the feature should be implemented in a module or in the global.asax file. Table 4-5 provides a comparison of various features supported by modules and the global.asax file.

Table 4-5. Module versus global.asax

Feature

Module

global.asax

Can receive event notifications for all HttpApplication -generated events

Yes

Yes

Can receive event notifications for Session_Start / _End , Application_Start / _End

No

Yes

Can be deployed at the machine level

Yes

No

Supports declarative object instantiation

No

Yes

Note that the one significant advantage of modules is that they can be deployed at the machine level. By deploying your module's assembly in the GAC, you can add that module either directly to the machine-wide machine.config file or individually to multiple applications' web.config files, without having to recopy the assembly to each application's /bin directory. The one significant advantage of the global.asax file is that it supports additional events issued when the application starts and ends and when each session starts and ends. The global.asax file also supports declarative object instantiation, but as we have seen, this is a feature most developers are unlikely to make much use of anyway.

As a general guideline, it is usually wise to place any application-specific features that are unlikely to be useful in other applications in global.asax . If you find yourself building a feature, such as a response timer, that might be useful to more than one application, consider building it as a module instead of coding it directly into global.asax .



Essential ASP.NET with Examples in Visual Basic .NET
Essential ASP.NET with Examples in Visual Basic .NET
ISBN: 0201760398
EAN: 2147483647
Year: 2003
Pages: 94
Authors: Fritz Onion

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