4.4 Custom Handlers


The second and most commonly used point of extensibility in the pipeline is the handler. Every time you author a new .aspx file, you are creating a new Page -derived class, which acts as an endpoint to a request, or a handler . To act as a handler within the pipeline, a class must implement the IHttpHandler interface, shown in Listing 4-8.

Listing 4-8 The IHttpHandler Interface
 public interface IHttpHandler {   void ProcessRequest(HttpContext ctx);   bool IsReusable {get;} } 

The Page class implements this interface, and in its implementation of the ProcessRequest method, it populates the response buffer by rendering all the controls contained within the page. When you create a new .aspx file, your only concern is to create controls on the page, which are in turn rendered at the appropriate time by the Page base class from which you derive.

Suppose, however, that you want to build a class that services requests but doesn't take advantage of the Page control-based rendering mechanism. You could create a page that contained nothing but code, or you could take more complete control over the request processing mechanism and author your own handler directly. You may also want to use the HTTP pipeline to service requests that are made to non-ASP.NET extensions. In either case, you can construct a custom handler by creating a class that implements the IHttpHandler interface directly, and then adding information to your configuration file indicating when that handler should be used.

To see an example, suppose we wanted to create a simple calculator handler that accepted GET requests, with an accompanying query string providing the parameters and operation, as shown in Listing 4-9.

Listing 4-9 GET-Based Calculator Request
 http://localhost/httppipeline/calc.calc?a=3&b=4&op=multiply 

The first step is to create a new class that implements the IHttpHandler interface. In our implementation of ProcessRequest , we look for the a , b , and op variables passed through the query string, perform the calculation, and write the result back to the response object. The read-only property, IsReusable , decides whether a new instance of this class will be created for each request or shared, pooled instances of this class will be used. The CalcHandler class is shown in Listing 4-10.

Listing 4-10 Sample Calc Handler Class
 // File: CalcHandler.cs public class CalcHandler : IHttpHandler {   public void ProcessRequest(HttpContext ctx)   {     int a = int.Parse(ctx.Request["a"]);     int b = int.Parse(ctx.Request["b"]);     switch (ctx.Request["op"])     {       case "add":         ctx.Response.Write(a+b);         break;       case "subtract":         ctx.Response.Write(a-b);         break;       case "multiply":         ctx.Response.Write(a*b);         break;       default:         ctx.Response.Write("Unrecognized operation");         break;     }   }   public bool IsReusable { get { return true; } } } 

The next step is to make ASP.NET aware that we would like our class to be used as the endpoint for any GET requests made to our application with the endpoint of calc.calc . To do this, we need to add an entry to our configuration file within the httpHandlers element, as shown in Listing 4-11. Note that for the verb, we restrict requests to be GET requests only. You could also specify "*" here if you didn't want to restrict the types of requests serviced by your handler. The path attribute indicates the endpoint name you want mapped onto this handler, and the type attribute defines the type and assembly from which the type should be loaded to service requests.

Listing 4-11 web.config Entry for Calc Handler
 <! file: web.config > <configuration>   <system.web>     <httpHandlers>       <add verb="GET" path="calc.calc"           type="CalcHandler, CalcHandler" />     </httpHandlers>   </system.web> </configuration> 

The last step in wiring up a handler is to let IIS know that you would like requests of a given extension to be mapped into the ASP.NET worker process. By default, only ASP.NET-specific extensions (.aspx, .ascx, .ashx, and so on) are mapped to the ASP.NET worker process. Figure 4-3 shows a sample extension mapping created through the Internet Information Services utility.

Figure 4-3. Extension Mapping in IIS

graphics/04fig03.gif

4.4.1 Custom Handlers for File Processing

One common example of when you might want to implement a custom handler is when you have existing (non-HTML) files on your system that you would like to expose as pages in a Web application. Instead of converting the files to an HTML format, you could write a handler that would read the files at request time and dynamically generate "HTMLized" content.

To see an example, suppose we want to display source code files with syntax coloring. That is, every time someone navigates to a .cs file in our application, we render it not as plain text but as HTML-enhanced text to show the keywords in blue and the comments in green, much as the Visual Studio .NET editor does. The first step is to construct a new class that implements the IHttpHandler interface. In our implementation of ProcessRequest , we open the requested file, parse its contents, and populate the response buffer with a syntax- colorized version of the code. The shell of such a class is shown in Listing 4-12.

Listing 4-12 A Custom Handler for Viewing .cs Source Files
 // File: CsSourceHandler.cs public class CsSourceHandler : IHttpHandler {   public void ProcessRequest(HttpContext ctx)   {     try     {       StreamReader sr =                new StreamReader(ctx.Request.PhysicalPath);       // write out html and body elements first       context.Response.Output.Write("<html><body>");       // extract short file name to print at top of file       string filename = context.Request.PhysicalPath;       int idx = filename.LastIndexOf("\");       if (idx >=  0)         filename = filename.Substring(idx+1,                                      filename.Length-idx-1);       context.Response.Output.Write("//File: {0}<br>",                                     filename);       string str;       do       {         str = sr.ReadLine();         if (str != null)           context.Response.Output.Write("{0}<br/>",                /*convert str to colorized html here*/);       } while (str != null);       context.Response.Write("</body></html>");     }     catch (FileNotFoundException)     {       context.Response.Write("<h2>Missing file</h2>");     }   }   public bool IsReusable   {     get {return false; }   } } 

The code for colorizing the source code text is not shown here but is included in the set of samples for this book. [10] Once we add the mapping for .cs files to our handler in our web.config file, clients can view any .cs files with syntax coloring. If a request for a file ending with the .cs extension comes into our application, a new instance of our CsSourceHandler class is created, and its ProcessRequest method is invoked. The only remaining concern is that we must ensure that IIS passes requests for .cs extensions to ASP.NET, which will then route the request appropriately. Typically, the last step in hooking up a custom handler, therefore, is to map the desired extension to the ASP.NET ISAPI extension DLL, as shown in the previous example in Figure 4-3. Note that in this particular case, this step is unnecessary because when ASP.NET is installed, it associates all .NET source code files with ASP.NET, which in turn maps requests for source code files to the HttpForbiddenHandler class, preventing users from accessing any source files in your application by default. The entry in our local web.config file for .cs extensions overrides the one found in machine.config , and our handler is called.

[10] Samples for this book are available at http://www.develop.com/books/essentialasp.net.

4.4.2 .ashx

In addition to creating custom handlers by building classes and configuring them through web.config and IIS, you can create them without the configuration steps by using the .ashx extension. Any file ending with .ashx (where the "h" stands for "handler") goes through the same parsing and compilation phase that .aspx files go through, but the resulting class must implement the IHttpHandler interface directly instead of deriving from Page . The format of .ashx files begins with a WebHandler directive, followed by a class definition much like one you would place directly in a source file. The class you would like to serve as the endpoint for requests made to this file is indicated through the Class attribute of the WebHandler directive, and the class must implement IHttpHandler . Using .ashx files to define custom handlers is convenient because there is no need to go through the process of registering a new extension, nor do you have to add any configuration elements to your web.config file. Listing 4-13 shows a sample .ashx file that implements the same calculator handler shown earlier, and Listing 4-14 shows a request to perform a calculation using this file as an endpoint.

Listing 4-13 Building a Custom Handler with .ashx Files
 <! file: calc.ashx > <%@ WebHandler Language="C#" Class="CalcHandler" %> using System; using System.Web; public class CalcHandler : IHttpHandler {   public void ProcessRequest(HttpContext ctx)   {     int a = int.Parse(ctx.Request["a"]);     int b = int.Parse(ctx.Request["b"]);     switch (ctx.Request["op"])     {       case "add":         ctx.Response.Write(a+b);         break;       case "subtract":         ctx.Response.Write(a-b);         break;       case "multiply":         ctx.Response.Write(a*b);         break;       default:         ctx.Response.Write("Unrecognized operation");         break;     }   }   public bool IsReusable { get { return false; } } } 
Listing 4-14 Calculator Request for .ashx Handler
 http://localhost/httppipeline/calc.ashx?a=3&b=4&op=multiply 

4.4.3 Handler Pooling

You may have noticed that the IHttpHandler interface supports a read-only property called IsReusable , used to indicate whether instances of a particular handler can be safely pooled. If you build a custom handler and return true from this property, ASP.NET pools instances of your handler as they are used to service requests. If you return false , a new instance of your handler is created each time a request is serviced. In general, it typically doesn't make that much difference whether your handlers are pooled or not, because the instantiation mechanism in the CLR and the garbage collector are quite efficient, so typically little is gained by doing pooling on your handler classes. The one case you might consider enabling pooling is if it takes significant time to set up the handler. For example, if your handler retrieved information from a database to perform its request processing, and that information did not change from one request to the next, it might make sense to request pooling on your handler. Handler pooling is never used by the standard handlers provided by ASP.NET. The Page class, which is by far the most common handler, returns false from IsReusable , and the factory class that allocates pages does not even perform pooling. The same goes for .ashx handlers ”they are never pooled.

4.4.4 Custom Handler Factories

If you want more control over the creation of your handler, you can write a custom handler factory ”a class that implements the IHttpHandlerFactory interface, as shown in Listing 4-15. The first method, GetHandler() , is called when a new instance of the requested handler is needed. The ReleaseHandler() method is called when the pipeline is finished processing a request with a handler, placing the control of handler creation and destruction in your hands. The deployment of a handler factory is identical to that of a custom handler, but instead of specifying a class that implements IHttpHandler , you specify a class that implements IHttpHandlerFactory . When a handler is requested, the runtime queries the class listed in the configuration file, and if it supports the IHttpHandlerFactory interface, it uses the factory to create an instance of the handler. Otherwise, it queries the class for its IHttpHandler interface and creates it directly.

Listing 4-15 IHttpHandlerFactory Interface
 public interface IHttpHandlerFactory {   IHttpHandler GetHandler(HttpContext ctx, string             requestType, string url, string translatedPath);   void ReleaseHandler(IHttpHandler handler); } 

You may want to consider implementing your own custom handler factory if you want to build your own pooling mechanism, or if you want to initialize your handlers with some data as they are being created by passing data into a nondefault constructor of your handler class. Listing 4-16 shows a slightly more involved example with a class called PooledCalcFactory that implements a pooling handler for the calculator handler class we wrote earlier. The pooling implementation uses a static instance of the Stack collection class with a limited size of ten. Note that every access to the shared stack object is protected by a lock on the Type object of the PooledCalcFactory class. This guarantees that no more than one instance will ever manipulate the stack shared by all instances of the class.

Listing 4-16 A Custom Pooling Factory for the Calc Handler
 // File: PoolingFactory.cs public class PooledCalcFactory : IHttpHandlerFactory {   const int cPoolSize = 10;   // Static stack of CalcHandler instances   private static Stack _handlers = new Stack(cPoolSize);   // GetHandler returns a CalcHandler instance from   // the static stack, if available, or a new instance   public IHttpHandler GetHandler(HttpContext ctx,       string requestType, string url, string translatedPath)   {     IHttpHandler handler = null;     // Acquire a lock on the SyncBlock associated with our     // Type object to prevent concurrent access to our     // static stack     lock (this.GetType())     {       // if handler is available on stack, pop it       if (_handlers.Count > 0)         handler = (IHttpHandler) _handlers.Pop();     }     // if no handler was available, create new instance     if (handler == null)       handler = new CalcHandler();     return handler;   }   // ReleaseHandler puts a handler back on the static   // stack, if the handler is reusable and the stack   // is not full   public void ReleaseHandler(IHttpHandler handler)   {     if (handler.IsReusable)       lock(this.GetType())       {         if (_handlers.Count < cPoolSize)           _handlers.Push(handler);       }   } } 

As mentioned earlier, the deployment of this factory class, along with the calculator handler, is nearly identical to the way we deployed the calculator handler initially. This time, however, we specify the type and assembly of our factory class instead of the handler class directly, as shown in Listing 4-17.

Listing 4-17 Configuration File for Specifying a Custom Handler Factory
 <! File: web.config > <configuration>   <system.web>     <httpHandlers>       <add verb="GET" path="calc.calc"            type="PooledCalcFactory, PoolingFactory" />     </httpHandlers>   </system.web> </configuration> 


Essential ASP.NET With Examples in C#
Essential ASP.NET With Examples in C#
ISBN: 0201760401
EAN: 2147483647
Year: 2003
Pages: 94
Authors: Fritz Onion

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