A Better Way to Handle Routes to Static Files

Yesterday at work I needed to figure out how to map an ASP.NET MVC route to a directory of static XML files. I came across this blog post about how to do it. As I implemented it, I realized that their example is wrong.

In their example, the work of writing to the ResponseStream is done in the GetHttpHandler method of their implementation of IRouteHandler. But notice what GetHttpHandler returns: an IHttpHandler. In their example, they do the writing to the stream, and then return null.

Is that right? They are essentially saying, “To handle this route, instantiate this class and get an IHttpHandler (which handles the HTTP request made with this route)—and we’ll always give you null.” That doesn’t make sense. So, the proper way to do it is to return an implementation of IHttpHandler which does the actual work of locating the static file and writing it to the ResponseStream, as follows:

public class StaticXmlFileRouteHandler : IRouteHandler
{
    private const string FilenameRouteElementName = "filename";

    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        var filename = requestContext.RouteData.Values[FilenameRouteElementName] as string;

        return new StaticXmlFileHttpHandler(filename);
    }
}

And the object returned is of this class:

public class StaticXmlFileHttpHandler : IHttpHandler
{
    private const string StaticXmlFilePhysicalDirectory = "~/dir-of-static-xml-files/";

    private const string XmlContentType = "application/xml";

    private readonly string filename;

    public StaticXmlFileHttpHandler(string filename)
    {
        this.filename = filename;
    }

    public void ProcessRequest(HttpContext context)
    {
        context.Response.Clear();

        string filepath = context.Server.MapPath(StaticXmlFilePhysicalDirectory + this.filename);

        if (!File.Exists(filepath))
            context.Response.StatusCode = (int)HttpStatusCode.NotFound;
        else
        {
            context.Response.ContentType = XmlContentType;
            context.Response.WriteFile(filepath);
        }

        context.Response.End();
    }

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

(The call to File.Exists should really come from an adapter so as to facilitate testing, but you already knew that.)

blog comments powered by Disqus