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

Creating a WebDav to RSS adapter

0.00/5 (No votes)
28 Feb 2006 4  
How to develop a handler that reads Exchange WebDAV folders and returns RSS.

Introduction

RSS is the standard format for feeds. There are hundreds of readers and aggregators for every platform. WebDAV, on the other side, is an obscure and poorly documented/supported protocol.

The purpose of this article is to create an adapter that allows users to access a Microsoft Exchange folder exposed through WebDAV using an RSS reader.

We're going to use IHttpHandlers, XSLT, WebDAV, and HTTP Authentication, all of which are described in the Background section.

Using the Code

Setting up the WebDAVAdapter is easy:

  • Create an empty application in IIS
  • Configure it to use ASP.NET 2.0 and anonymous access
  • Copy the source files to the virtual directory
  • Point your RSS reader to http://WEBSERVER/VDIR/GetFeed.ashx?URL=http(s)://EXCHANGESERVER/Exchange/USER/FOLDER/

Background

About IHttpHandler

We've had HTTP Handlers for a lot of years. Those .asp "pages" were simply that: classes that take an HTTP request, process it without any inherited structure, and output text and headers.

ASP.NET .aspx pages are more complex classes that extend IHttpHandler and add abstractions to the processing model in order to make it easier to develop event-driven web pages.

IHttpHandler provides just two methods:

public interface IHttpHandler
{
  bool IsReusable { get; }
  void ProcessRequest(HttpContext context);
}

IsReusable should return true if another request can use the IHttpHandler instance. ProcessRequest contains all of our code.

About WebDAV

WebDAV (Web-based Distributed Authoring and Versioning) is a set of extensions to the HTTP protocol that provide additional "verbs" besides the standard GET and POST. It is used by Outlook Web Access to provide a rich web interface.

The verb we'll be using in this module is SEARCH, which provides a SQL-like syntax to query folders.

About XSLT

XSLT (eXtensible Stylesheet Language Transformation) is a language for transforming XML documents into other XML documents. It's based on templates that match nodes of the source document and generate markup on the target.

About HTTP Authentication

HTTP Authentication is described in RFC 2617. It describes a method for the server to require a set of credentials from the client.

To authenticate using the Basic scheme, the client must send an Authorization header containing:

Authorization: Basic user:pass

with user:pass encoded in Base64.

If the header is not present, or the credentials are invalid, a 401 (Unauthorized) status code is returned, and an Authenticate header like the following is added:

WWW-Authenticate: Basic realm="The Site"

When the browser finds this header, it usually presents the user with a dialog box to enter his username and password, and retries the request.

The Code

The first thing we need to do is create a request for the WebDAV folder URL:

string url = context.Request.QueryString["URL"];
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.Method = "SEARCH";
request.ContentType = "text/xml";

Notice that the Method (verb) is changed from the default GET to SEARCH. We also set ContentType to let the server know we are sending XML in the request body.

Next, we parse the Authentication header and create a credential for the request:

string authorizationHeader = context.Request.Headers["Authorization"];
if (!string.IsNullOrEmpty(authorizationHeader))
{
    //Format is "Basic BASE64STRING"
    string userPassBase64 = authorizationHeader.Split(' ')[1];
    byte[] userPassBytes = Convert.FromBase64String(userPassBase64);
    string userPassString = Encoding.Default.GetString(userPassBytes);
    //Format is user:pass
    string[] userPassArray = userPassString.Split(':');
    request.Credentials = new NetworkCredential(userPassArray[0], userPassArray[1]);
}

We need a reference to the request stream:

using (Stream requestStream = request.GetRequestStream())
{
    using (XmlTextWriter writer = new XmlTextWriter(requestStream, Encoding.UTF8))

And, we use it to write the SEARCH query:

writer.WriteStartDocument();
writer.WriteStartElement("searchrequest", "DAV:");
writer.WriteStartElement("sql", "DAV:");
writer.WriteString(string.Format(@"
        SELECT 
            ""urn:schemas:httpmail:subject"",
            ""urn:schemas:httpmail:fromname"",
            ""urn:schemas:httpmail:date"",
            ""urn:schemas:httpmail:htmldescription""
        FROM SCOPE('Deep traversal of ""{0}""')
        WHERE ""DAV:ishidden"" = False                
        ", url));
writer.WriteEndDocument();

SCOPE('Deep traversal of "url"') tells the server to also look for messages in subfolders. You can find a reference of the Exchange Store properties here and a description of the SEARCH method here.

And now, the most important part.

We get the response stream from the server and set a XmlReader over it:

using (WebResponse response = request.GetResponse())
{
    using (Stream responseStream = response.GetResponseStream())
    {
        using (XmlTextReader reader = new XmlTextReader(responseStream))

We load the transformation and create a parameter representing a link to the folder:

XslCompiledTransform transform = new XslCompiledTransform();
transform.Load(context.Request.MapPath("RSS.xsl"));
XsltArgumentList arguments = new XsltArgumentList();
arguments.AddParam("link", string.Empty, url);

And finally, we send the transformed XML to the client:

context.Response.ContentType = "text/xml";
transform.Transform(reader, arguments, context.Response.OutputStream);

Authorization might fail if the supplied credentials are not correct. We'll handle that case by wrapping GetResponse() in a try-catch block and requesting authentication from the client again:

catch (WebException ex)
{
    if ((ex.Response as HttpWebResponse).StatusCode == HttpStatusCode.Unauthorized)
    {
        context.Response.StatusCode = 401;
        context.Response.AppendHeader("WWW-Authenticate",
            string.Format(@"Basic realm=""{0}""", url));
    }
    else
    {
        //rethrow on other errors
        throw;
    }
}

If there were no problems to this point, the request completes and your RSS reader will be displaying an item for each message in the folder.

The Transformation

The XSLT we're using is quite simple.

First, declare the stylesheet and the namespaces we're using:

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:d="urn:schemas:httpmail:" xmlns:a="DAV:">

Add a parameter for the link URL. Parameters are useful, among other things, to include data that is not part of the input document.

<xsl:param name="link"/>

Next is the "root" template that will create the rss element. The apply-templates element will select each item (message) and pass it to the corresponding template.

<xsl:template match="/">
    <rss version="2.0">
        <channel>
            <title>
                <xsl:value-of select="$link" />
            </title>
            <link>
                <xsl:value-of select="$link" />    
            </link>
            <xsl:apply-templates select="a:multistatus/a:response" />
        </channel>
    </rss>
</xsl:template>

The matching template creates the RSS item elements and their link, and uses apply-templates again for the rest of the properties:

<xsl:template match="a:response">
    <item>
        <link>
            <xsl:value-of select="a:href/text()" />
        </link>
        <xsl:apply-templates select="a:propstat/a:prop" />
    </item>
</xsl:template>

The last template completes the item properties:

<xsl:template match="a:prop">
    <title>
        <xsl:value-of select="d:subject/text()" />
    </title>
    <author>
        <xsl:value-of select="d:fromname/text()" />
    </author>
    <date>
        <xsl:value-of select="d:date/text()" />
    </date>
    <description>
        <xsl:value-of select="d:htmldescription/text()" />
    </description>
</xsl:template>

Further Development (Homework!)

While this sample supports only RSS, it would be trivial to add, for example, Atom support. You would have to:

  • Create a new Atom.xsl file
  • Parametrize the loading of XslCompiledTransform (for example, sending the desired format in the QueryString)

Once the transformation loading is parameterized, you'd only need to add new XSL files to support other formats.

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