An HTTP Handler for Quick Data Reports
With their
relatively simple programming model, HTTP handlers give you a means of
interacting with the low-level request and response services of IIS. In
the previous example, we returned only constant text and made no use of
the request information. In the next example, we’ll configure the
handler to intercept and process only requests of a particular type and
generate the output based on the contents of the requested resource.
The idea is to build an HTTP handler for custom .sqlx
resources. A SQLX file is an XML document that expresses the statements
for one or more SQL queries. The handler grabs the information about
the query, executes it, and finally returns the result set formatted as a
grid. Figure 2 shows the expected outcome.
To start, let’s examine the source code for the IHttpHandler class.
Warning
Take
this example for what it really is—merely a way to process a custom XML
file with a custom extension doing something more significant than
outputting a “hello world” message. Do not take this handler as a realistic prototype for exposing your Microsoft SQL Server databases over the Web. |
Building a Query Manager Tool
The HTTP handler should get into the game whenever the user requests an .sqlx
resource. Assume for now that the system knows how to deal with such a
weird extension, and focus on what’s needed to execute the query and
pack the results into a grid. To execute the query, at a minimum, we
need the connection string and the command text. The following text
illustrates the typical contents of an .sqlx file:
<queries>
<query connString="DATABASE=northwind;SERVER=localhost;UID...;">
SELECT firstname, lastname, country FROM employees
</query>
<query connString="DATABASE=northwind;SERVER=localhost;UID=...;">
SELECT companyname FROM customers WHERE country='Italy'
</query>
</queries>
The XML document is formed by a collection of <query> nodes, each containing an attribute for the connection string and the text of the query.
The ProcessRequest method extracts this information before it can proceed with executing the query and generating the output:
class SqlxData
{
public string ConnectionString;
public string QueryText;
}
public class QueryHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
// Parses the SQLX file
SqlxData[] data = ParseFile(context);
// Create the output as HTML
StringCollection htmlColl = CreateOutput(data);
// Output the data
context.Response.Write("<html><head><title>");
context.Response.Write("QueryHandler Output");
context.Response.Write("</title></head><body>");
foreach (string html in htmlColl)
{
context.Response.Write(html);
context.Response.Write("<hr />");
}
context.Response.Write("</body></html>");
}
// Override the IsReusable property
public bool IsReusable
{
get { return true; }
}
...
}
The ParseFile helper function parses the source code of the .sqlx file and creates an instance of the SqlxData class for each query found:
private SqlxData[] ParseFile(HttpContext context)
{
XmlDocument doc = new XmlDocument();
string filePath = context.Request.Path;
using (Stream fileStream = VirtualPathProvider.OpenFile(filePath)) {
doc.Load(fileStream);
}
// Visit the <mapping> nodes
XmlNodeList mappings = doc.SelectNodes("queries/query");
SqlxData[] descriptors = new SqlxData[mappings.Count];
for (int i=0; i < descriptors.Length; i++)
{
XmlNode mapping = mappings[i];
SqlxData query = new SqlxData();
descriptors[i] = query;
try {
query.ConnectionString =
mapping.Attributes["connString"].Value;
query.QueryText = mapping.InnerText;
}
catch {
context.Response.Write("Error parsing the input file.");
descriptors = new SqlxData[0];
break;
}
}
return descriptors;
}
The SqlxData internal class groups the connection string and the command text. The information is passed to the CreateOutput function, which will actually execute the query and generate the grid:
private StringCollection CreateOutput(SqlxData[] descriptors)
{
StringCollection coll = new StringCollection();
foreach (SqlxData data in descriptors)
{
// Run the query
DataTable dt = new DataTable();
SqlDataAdapter adapter = new SqlDataAdapter(data.QueryText,
data.ConnectionString);
adapter.Fill(dt);
// Error handling
...
// Prepare the grid
DataGrid grid = new DataGrid();
grid.DataSource = dt;
grid.DataBind();
// Get the HTML
string html = Utils.RenderControlAsString(grid);
coll.Add(html);
}
return coll;
}
After executing the query, the method populates a dynamically created DataGrid control. In ASP.NET pages, the DataGrid
control, like any other control, is rendered to HTML. However, this
happens through the care of the special HTTP handler that manages .aspx resources. For .sqlx resources, we need to provide that functionality ourselves. Obtaining the HTML for a Web control is as easy as calling the RenderControl method on an HTML text writer object. This is just what the helper method RenderControlAsString does:
static class Utils
{
public static string RenderControlAsString(Control ctl)
{
StringWriter sw = new StringWriter();
HtmlTextWriter writer = new HtmlTextWriter(sw);
ctl.RenderControl(writer);
return sw.ToString();
}
}
Note
An HTTP handler that needs to access session-state values must implement the IRequiresSessionState interface. Like INamingContainer, it’s a marker interface and requires no method implementation. Note that the IRequiresSessionState
interface indicates that the HTTP handler requires read and write
access to the session state. If read-only access is needed, use the IReadOnlySessionState interface instead. |
Registering the Handler
An HTTP handler is a class and must be compiled to an assembly before you can use it. The assembly must be deployed to the Bin
directory of the application. If you plan to make this handler
available to all applications, you can copy it to the global assembly
cache (GAC). The next step is registering the handler with an individual
application or with all the applications running on the Web server. You
register the handler in the configuration file:
<system.web>
<httpHandlers>
<add verb="*"
path="*.sqlx"
type= "Core35.Components.QueryHandler,Core35Lib" />
</httpHandlers>
</system.web>
You add the new handler to the <httpHandlers> section of the local or global web.config file. The section supports three actions: <add>, <remove>, and <clear>. You use <add> to add a new HTTP handler to the scope of the .config file. You use <remove> to remove a particular handler. Finally, you use <clear> to get rid of all the registered handlers. To add a new handler, you need to set three attributes—verb, path, and type—as shown in Table 2.
Table 2. Attributes Needed to Register an HTTP Handler
Attribute | Description |
---|
Verb | Indicates the list of the supported HTTP verbs—for example, GET, PUT, and POST. The wildcard character (*) is an acceptable value and denotes all verbs. |
Path | A wildcard string, or a single URL, that indicates the resources the handler will work on—for example, *.aspx. |
Type | Specifies
a comma-separated class/assembly combination. ASP.NET searches for the
assembly DLL first in the application’s private Bin directory and then in the system global assembly cache. |
These attributes are mandatory. An optional attribute is also supported—validate. When validate is set to false,
ASP.NET delays as much as possible loading the assembly with the HTTP
handler. In other words, the assembly will be loaded only when a request
for it arrives. ASP.NET will not try to preload the assembly, thus
catching any error or problem with it.
So far, you have correctly deployed and registered the HTTP handler, but if you try invoking an .sqlx
resource, the results you produce are not what you’d expect. The
problem lies in the fact that so far you configured ASP.NET to handle
only .sqlx resources, but IIS still doesn’t know anything about them!
A request for an .sqlx resource is handled by IIS before it is handed to the ASP.NET ISAPI extension. If you don’t register some
ISAPI extension to handle ..sqlx resource requests, IIS will treat each
request as a request for a static resource and serve the request by
sending back the source code of the .sqlx file. The extra step required
is registering the .sqlx extension with the IIS 6.0 metabase such that requests for .sqlx resources are handed off to ASP.NET, as shown in Figure 3.

The dialog box in the
figure is obtained by clicking on the properties of the application in
the IIS 6.0 manager and then the configuration of the site. To involve
the HTTP handler, you must choose aspnet_isapi.dll as the ISAPI extension. In this way, all .sqlx requests are handed out to ASP.NET and processed through the specified handler. Make sure you select aspnet_isapi.dll from the folder of the ASP.NET version you plan to use.
Caution
In Microsoft Visual Studio, if you test a sample .sqlx resource using the local embedded Web server, nothing happens that forces you to register the .sqlx
resource with IIS. This is just the point, though. You’re not using
IIS! In other words, if you use the local Web server, you have no need
to touch IIS; you do need to register any custom resource you plan to
use with IIS before you get to production. |
Registering the Handler with IIS 7.0
If you run IIS 7.0, you don’t strictly need to change anything through the IIS Manager. You can add a new section to the web.config
file and specify the HTTP handler also for static resources that would
otherwise be served directly by IIS. Here’s what you need to enter:
<system.webServer>
<add verb="*"
path="*.sqlx"
type="Core35.Components.QueryHandler, Core35Lib" />
</system.webServer>
The new section is a direct child of the root tag <configuration>.
Without this setting, IIS can’t recognize the page and won’t serve it
up. The configuration script instructs IIS 7.0 to forward any *.sqlx requests to your application, which knows how to deal with it.