Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Asynchronous HTTP Handler for Asynchronous BLOB DataReader

0.00/5 (No votes)
5 Sep 2006 2  
Includes description of an asynchronous HTTP handler (vs. alternative WebHandler) implementations that uses an asynchronous DB read operation to render a BLOB DB column as an image to any client browser.

Introduction

Storing and retrieving BLOB images from a database column has been implemented since the first days of ASP in several ways. Scalability (more users using the same resources) and performance (faster execution times) are important factors in today's high demanding websites. There are 1001 ways to write code to do a predefined task (and they might all even work), yet getting it right requires paying attention to the details. With the introduction of HTTP handlers in ASP.NET, this can be done better than using a standard ASP.NET (.aspx) page; with async DB operations in ADO.NET v2 combined with async HTTP handlers, this gets even better!

Background

  • Why use HTTP handlers?

    The difference between an HTTP handler and a normal ASP.NET page is mainly performance, since an HTTP handler doesn't go through the full page events, browser compatibility tests, and formatting that a standard ASP.NET page does. Thereafter, if your page has no output (such as for audit logging) or has non-HTML output (such as XML, IMAGE, or anything else), you'll gain more scalability by implementing it as an HTTP handler.

  • Why use Asynchronous HTTP handlers?

    Asynchronous pages are a lot easier to implement in ASP.NET v2, and allow for greater scalability (more users on same hardware) on pages that require extensive I/O (thread delays while waiting for a response). When ADO.NET executes a database operation, the thread executing that will "hang" while waiting for a reply from the DB; if the latter is, for example, down and the timeout is set to 5 minutes (incredible but true), that thread will be wasted for 5 minutes. If your thread limit is set to 25, that number of users will already cause thread starvation. With asynchronous operations, the thread is released back to the thread pool while waiting for the response, and as such allows to answer to other pages on the web (that might or might not hit the DB again). In any case, it's primarily a scalability consideration that, along the run, improves performance as well (better resources usage).

Using the code

The solution source code includes the following multiple demonstrations:

  • syncFlavors/ folder (each is a full standalone example) includes the "old" synchronous way of doing things:
    • AddImage.aspx - was initially used to add an image to the included SQLExpress database
    • ShowImageScalar.aspx - ASP.NET page that uses the ADO.NET ExecuteScalar method to render an image to the browser
    • ShowImageReader.aspx - ASP.NET page that uses the ADO.NET ExecuteReader method to render an image to the browser
    • ShowImageAsyncReader.aspx - ASP.NET page that uses the new ADO.NET v2 BeginExecuteReader method to render an image to the browser
  • Asynchronous HTTP Handler:
    • App_Code/ImageAsyncHandler.cs - the class for the HTTP handler
    • web.config - requires changes to register our HTTP handler
    • ShowImage.aspx - includes an image tag to retrieve BLOB using our HTTP handler
  • Asynchronous WebHandler (alternative implementation)
    • ImageAsync.ashx - our WebHandler (same as HTTP handler, but requires no web.config changes!)
    • ShowImage.aspx - includes an image tag to retrieve BLOB using our WebHandler

The only difference between the implementations is the requirement for HTTP handlers to be registered in the web.config file, which gives us more flexibility in the URL, but limits us, for example, when deploying to ISPs (since they don't always allow changes to the web.config or IIS mappings). That's why WebHandlers where created to workaround those changes - it's in fact exactly the same, without the need to register any handlers or IIS mappings (to map to our new file type, since .ASHX files are mapped by default when installing ASP.NET). Personally, I find the WebHandler implementation "cleaner" and easier to maintain, at the cost of avoiding interesting URLs.

Points of interest

  • web.config ConnectionStrings property

    This new web.config section allows us to centrally store our connection strings, identified by name.

    <connectionStrings>
      <add name="LocalBLOB" 
         connectionString="Data Source=.\SQLEXPRESS;
                           AttachDbFilename=|DataDirectory|BLOBTest.mdf;
                           Integrated Security=True;User Instance=True;
                           Asynchronous Processing=true"/>
    </connectionStrings>

    Note the "|DataDirectory|" special keyword which will be replaced on runtime by ASP.NET with our App_Data folder to avoid hard-coding paths. In code, we can then retrieve this, by using:

    ConfigurationManager.ConnectionStrings["LocalBLOB"].ConnectionString
  • Asynchronous HTTP handler with asynchronous DB read

    An asynchronous HTTP handler uses an IAsyncResult object to notify when the operation has been completed, freeing our thread from waiting for a response. Combined with an asynchronous DB operation (which also returns an IAsyncResult object!), ASP.NET will continue the execution of the handler (and re-assign the thread) once the DB operation is finished.

    // retrieve result
    
    using (SqlDataReader reader = cmd.EndExecuteReader(result))
    {
      this.renderImage(context, reader);
    }
  • Asynchronous HTTP handler vs. WebHandler (.ashx)

    An HTTP handler requires a change to the web.config file to make it visible to ASP.NET. The "path" property allows us to specify almost any URL we can think of, and the "type" property points to our class.

    <httpHandlers>
      <add verb="*" path="img/*" type="ImageAsyncHandler"/>
    </httpHandlers>

    Our HTTP handler uses URL segments to figure out the "*" in the URL; to retrieve an image served by our HTTP handler, we can specify an image tag like the following:

    <img src="img/960a3be7-80d1-4528-bfaa-975cf9d53800" />

    WebHandlers overcome this requirement by making the .ASHX extension already known to ASP.NET (pre-configured IIS mapping), and as such do not require any web.config changes. Of course, this also means that we must use a different URL, and as such to retrieve an image from our WebHandler (which now uses the query string), the following image tag is required:

    <img src="ImageAsync.ashx?960a3be7-80d1-4528-bfaa-975cf9d53800" />
  • ADO.NET v2 Asynchronous DataReader

    The new BeginExecuteReader method requires a slightly different code design since now the execution of the query and actually using the results are in two different methods. This also allows better thread reuse since our main thread does not wait for the DB operation to finish.

  • Buffering BLOB fields

    When using the DataReader, each row in the database is fully loaded into memory before it gets to our code; obviously, if our BLOB column includes, let's say, 1GB of data, this would exhaust our memory resources. If we instead specify a different CommandBehavior we can then better buffer it from the DB, saving us memory.

    // start async DB read
    
    return cmd.BeginExecuteReader(cb, context,
        // doesn't load whole column into memory
    
        CommandBehavior.SequentialAccess |
        // performance improve since we only want one row
    
        CommandBehavior.SingleRow |
        // close connection immediately after read
    
        CommandBehavior.CloseConnection);
  • Rendering an error message as an image

    An HTTP handler that returns an image makes it difficult to show any exception details, thus hindering the debugging and administration of a live site. In my case, I preferred showing the real exception to the user by rendering during runtime an image that includes the error text.

    context.Response.ContentType = "image/jpeg";
    // calculate the image width by message length
    
    Bitmap bitmap = new Bitmap(7 * msg.Length, 30);
    Graphics g = Graphics.FromImage(bitmap);
    // create a background filler
    
    g.FillRectangle(new SolidBrush(Color.DarkRed), 0, 0, 
                    bitmap.Width, bitmap.Height);
    // draw our message
    
    g.DrawString(msg, new Font("Tahoma", 10, FontStyle.Bold), 
                      new SolidBrush(Color.White), new PointF(5, 5));
    // stream it to the output
    
    bitmap.Save(context.Response.OutputStream, 
                System.Drawing.Imaging.ImageFormat.Jpeg);

History

  • First release - feel free to use and let me know your feedback!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here