Monday, March 17, 2008

Dutch 'Webrichtlijnen' and binary downloads

The Dutch Web Guidelines (a quality model for sustainable and accessible websites) describe what to do with downloads on your page. This isn't meant for HTML and images, but when you place Adobes Protable Document Format (pdf) or Rich Text Documents (rtf), those documents can be opened in your current window. It is possible to open those files in new windows (although the target="_blank" solution doesn't conform to XHTML validation, some solutions do exist , like rel="external" and JavaScript replacement). But this can be a browser window. Although you can configure your browser, the majority of users don't have the knowledge to tweak their browser to do what they want with these files.
The solution from the Webrichtlijnen:

Do not automatically open links to downloadable files in a new window.


Guideline R-pd.8.22


One way of getting this done is setting the HTTP Content-disposition header
By having the web server send an extra HTTP header for the downloadable file to the browser, the browser can decide whether to download this file untouched as an attachment to the visitor's hard disk.

The best way to do this is writing a custom HTTP Module which can check on served file extensions or MIME Types and add the content disposition header for configured downloads.
In this example implementation, I configure the file extensions in code, but you can always configure this in your config, database or any file.


public class ContentDispositionModule : IHttpModule
{
#region IHttpModule Members

///<summary>
///Initializes the module and prepares it to handle requests.
///</summary>
///<param name="context">An <see cref="T:System.Web.HttpApplication"></see>
///that provides access to the methods, properties, and events common
///to all application objects within an ASP.NET application </param>
public virtual void Init(HttpApplication context)
{
context.PreSendRequestHeaders += new EventHandler(SetContentDispositionHeader);
}

private static void SetContentDispositionHeader(object sender, EventArgs e)
{
string url = ((HttpApplication)sender).Context.Request.Path;
if (IsUrlPatternMatch(url))
{
((HttpApplication)sender).Context.Response.AppendHeader("Content-Disposition", "attachment");
}
}

///<summary>
///Disposes of the resources (other than memory) used by the module that implements
///<see cref="T:System.Web.IHttpModule"></see>.
///</summary>
public virtual void Dispose()
{
}

/// <summary>
/// Check the url against the binaryExtension patterns
/// </summary>
/// <param name="strUrl">url to check</param>
/// <returns>true if it matches the binaryExtension pattern</returns>
private static bool IsUrlPatternMatch(string strUrl)
{
// get this string from a more configurable place.
string binaryExtensions = "*.pdf,*.rtf";
if (!string.IsNullOrEmpty(binaryExtensions))
{
foreach (string extension in binaryExtensions.Split(','))
{
if (!string.IsNullOrEmpty(extension))
{
if (Regex.IsMatch(strUrl, extension.Trim()))
{
// the url path contains the specified regex string, so return true and break out of loop
return true;
}
}
}
}
return false;
}

#endregion
}


After coding this module, you'll need to change the .config so the module will be activated for usage. Add it to the httpModules of the system.web section. You'll also need to configure IIS that all needed extensions are routed through your ASP.NET ISAPI dll.

No comments: