Sponsors

Using the SimpleHandlerFactory to Generate XML

Posted on 6/5/2007

I wrote all of the code to manage my blog code myself. Most of the blogging frameworks on the net seemed so bloated and messy to me and I wanted to try my hand at creating my blog from the ground up. I like what I wrote but it needed a Really Simple Syndication (RSS) feed to make it complete. Search engines are tuned into RSS nowadays so having a syndication page makes your blog much more googleable. So I found some ASP.NET 1.1 code that emits XML from an ASPX page started hacking it in. This article describes what I had to change to make ASP.NET 2.0 emit a non-HTML document.

In ASP.NET 1.1, you could coerce the PageHandlerFactory that renders ASPX pages into generating any kind of content you wanted simple by setting the ContentType (through the Response object or declaratively in the @Page directive). And the Visual Studio .NET 2003 IDE didn’t care if your ASPX page had no real HTML in it. So you could bind a DataRepeater to blog data, for example, to render XML. It seemed that I had found an easy way to render my RSS feed.

Unfortunately, the HTML validator in Visual Studio 2005 is strict enough that you can no longer remove the and tags from an ASPX page without seeing scads of validation errors. So I could no longer get my site to build and needed a fix. I read a few articles that said I could turn the validation off but that idea didn’t appeal to me. I knew there had to be a better way. Another article suggested that I write an IHttpHandler to generate the XML I needed and register a new file extension with IIS. I liked the idea of using an IHttpHandler but I couldn’t make changes to the IIS metabase on the servers where my blog is deployed since it’s in a shared hosting environment.

If only there were a way to hook into the HTTP pipeline to generate content that’s not HTML. Well, if you snoop around in the IIS configuration, you’ll find it under file mappings. Remember the PageHandlerFactory I mentioned before. That’s the IHttpHandler that handles ASPX pages in ASP.NET. There’s also a WebServiceHandlerFactory that handles ASMX requests. Scanning that list, you’ll see another handler called SimpleHandlerFactory which is mapped to requests ending in ASHX.

The SimpleHandlerFactory is exactly what its name implies: simple. When you create an ASHX page, it contains no markup except for a <% WebHandler %> directive at the top. All the rest is pure C# or VB.NET code. Inside the WebHandler directive, all you need to do is aim the SimpleHandlerFactory at the class that implements IHttpHandler or IHttpAsyncHandler in your application. Best of all, that handler can be implemented right in the ASHX page. Here’s how my RSS.ashx page goes:

<%@ WebHandler Language="C#" Class="RSSHandler" %>

using System;
using System.Web;
using System.Data;
using System.Data.OleDb;
using System.Text;

public class RSSHandler : IHttpHandler
{
    public void ProcessRequest( HttpContext context )
    {
        HttpResponse r = context.Response;

        // set the MIME type to XML
        r.ContentType = "text/xml";
        r.Write( GetHeader() );

        OleDbConnection conn = null;
        OleDbDataReader rdr = null;
        try
        {
            // insert code here to return the following fields
            // from a database containing the blog articles:
            // LINK, AUTHOR, ARTICLE_ID, KEYWORDS, TITLE,
            // DATE_POSTED, EXCERPT

            conn = new OleDbConnection(
                "Put your connection string here." );
            conn.Open();
            OleDbCommand cmd = new OleDbCommand(
                "Put your query here.", conn );
            rdr = cmd.ExecuteReader();
            while (rdr.Read())
                r.Write( GetItemXML( rdr ) );
        }
        catch { }
        finally
        {
            if ((object)rdr != null && !rdr.IsClosed)
                rdr.Close();
            if ((object)conn != null)
                conn.Close();
        }

        r.Write( GetFooter() );
    }

    private string GetHeader()
    {
        StringBuilder sbHeader = new StringBuilder();
        sbHeader.Append( "<?xml version=\"1.0\" "+
            "encoding=\"utf-8\"?>" );
        sbHeader.Append( "<?xml-stylesheet type=\"text/xsl\" "+
            "href=\"rsspretty.xsl\" version=\"1.0\"?>" );
        sbHeader.Append( "<rss xmlns:dc=\"http://purl.org/dc/" +
            "elements/1.1/\" version=\"2.0\">" );
        sbHeader.Append( "<channel>" );
        sbHeader.Append( "<title>My Blog</title>" );
        sbHeader.Append( "<link>Insert master blog link here.</link>" );
        sbHeader.Append( "<description>My cool blog.</description>" );
        sbHeader.Append( "<language>en-us</language>" );
        return sbHeader.ToString();
    }
    
    private string GetItemXML( OleDbDataReader rdr )
    {
        StringBuilder sbItem = new StringBuilder();
        sbItem.Append( "<item>" );
        sbItem.AppendFormat( "<title>{0}</title>",
            FormatForXML( rdr["TITLE"] ) );
        sbItem.AppendFormat( "<link>{0}</link>",
            FormatForXML( rdr["LINK"] ) );
        sbItem.AppendFormat( "<description>{0}</description>",
            FormatForXML( rdr["EXCERPT"] ) );
        sbItem.AppendFormat( "<dc:creator>{0}</dc:creator>",
            FormatForXML( rdr["AUTHOR"] ) );
        sbItem.AppendFormat( "<keywords>{0}</keywords>",
            FormatForXML( rdr["KEYWORDS"] ) );
        sbItem.AppendFormat( "<pubDate>{0}</pubDate>",
            ((DateTime)rdr["DATE_POSTED"]).ToShortDateString() );
        sbItem.Append( "</item>" );
        return sbItem.ToString();
    }

    private string GetFooter()
    {
        StringBuilder sbFooter = new StringBuilder();
        sbFooter.Append( "</channel>" );
        sbFooter.Append( "</rss>" );
        return sbFooter.ToString();
    }

    private string FormatForXML( object input )
    {
        string data = input.ToString();

        data = data.Replace( "&", "&amp;" );
        data = data.Replace( "\"", "&quot;" );
        data = data.Replace( "'", "&apos;" );
        data = data.Replace( "<", "&lt;" );
        data = data.Replace( ">", "&gt;" );

        return data;
    }

    public bool IsReusable
    {
        get { return true; }
    }
}

As you can see, the WebHandler directive at the top of the ASHX file references the RSSHandler class defined below it. In the ProcessRequest method, once I set the MIME type using the ContentType property of the Response object, I begin rendering the XML for the RSS feed through simple Write statements.

To recap, the SimpleHandlerFactory defers to an IHttpHandler of your choice through the Class attribute in the WebHandler directive in any ASHX pages that you include in your site. In Visual Studio 2005, it’s even easier to start ASHX pages because there’s a new choice called Generic Handler in the Add New Item dialog. If you select Generic Handler from the Add New Item dialog, Visual Studio 2005 will insert an ASHX page, including the WebHandler directive and a reference to a stub of an IHttpHandler as shown above.

Using this technique, I generated XML from my RSS.ashx page, of course. But I could have rendered any other kind of content, too, including images, Acrobat documents, script code, etc. The possibilities are endless.

Comments

POST A COMMENT
Your name:
Your e-mail address (will not be shared with others):
Your comment: