Custom RouteHandler in ASP.NET 4.0

It’s great that some of the innovations from ASP.NET MVC 1.0 were moved into the ASP.NET 4.0 platform. One of those was the RouteTable. I hadn’t written a custom RouteHandler before, so I thought I’d do a simple one as a demo for myself (and any others who are interested).

This is just an example of how to build one – it’s not secure.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.SessionState;
using System.Web.Routing;
using System.IO;

namespace TestWebApplication1
{
    public class Global : System.Web.HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            RouteTable.Routes.Add(new Route("Assets/{locale}/{assetID}", new CustomRouteHandler()));
        }
    }

    public class CustomRouteHandler : IRouteHandler
    {
        public IHttpHandler GetHttpHandler(RequestContext requestContext)
        {            
            return new SimpleFileHttpHandler(requestContext);
        }
    }

    public class SimpleFileHttpHandler : IHttpHandler
    {
        private RequestContext _requestContext;
        private HttpContext _httpContext;

        public RequestContext RequestContext { get; private set; }

        public SimpleFileHttpHandler(RequestContext requestContext)
        {
            _requestContext = requestContext;
        }

        public bool IsReusable
        {
            get { return false; }
        }

        public void ProcessRequest(HttpContext context)
        {
            _httpContext = context;

            string assetID = _requestContext.RouteData.Values["assetID"].ToString();
            string locale = _requestContext.RouteData.Values["locale"].ToString();

            if (string.IsNullOrWhiteSpace(assetID) || string.IsNullOrWhiteSpace(locale)) {
                throw new ArgumentException();
            }
            // this is not adaquate and definitely not secure as it would allow any file to be selected and downloaded
            // needs to prevent any sort of hack attempts using encoded paths, etc. should just be a relative path within the
            // folder that contains the content and no more. 
            string path = _httpContext.Request.MapPath("~/Content/" + assetID, "", false);
            if (File.Exists(path))
            {               
                // hard coded to the image/jpg type (obviously needs to adjust)
                context.Response.AddHeader("Content-Type", "image/jpg");
                context.Response.AddHeader("Content-Length", new FileInfo(path).Length.ToString());
                context.Response.WriteFile(path);
            }
        }
    }


}

To use it, I created a folder called Content and copied one of the sample photos included with Windows into the folder, and then modified Default.aspx:

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
    CodeBehind="Default.aspx.cs" Inherits="TestWebApplication1._Default" %>

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <h2>
        Welcome to the Route Table Demonstrator
    </h2>
    <h3>This one should work as it's using the not-so-magical route table and a custom iroutehandler.</h3>
    <p>
        &lt;img src=&quot;/Assets/en-us/Penguins.jpg&quot; width=&quot;320&quot; height=&quot;200&quot; /&gt;
    </p>
    <p>
        <img src="/Assets/en-us/Penguins.jpg" width="320" height="200" />
    </p>
    <h3>THis one shouldn't work due to security in web.config for the folder</h3>
    <p>&lt;img src=&quot;/Content/Penguins.jpg&quot; /&gt;</p>
    <p>
        <img src="/Content/Penguins.jpg" />
    </p>    
</asp:Content>

In the Content Folder, I added a web.config file to prevent direct access to the content within the folder:

<?xml version="1.0"?>
<configuration>

  <system.web>
    <authorization>
      <deny users="*"/>
    </authorization>
 
  </system.web>

  <system.webServer>
     <modules runAllManagedModulesForAllRequests="true"/>
  </system.webServer>
</configuration>

This should work without modification on IIS7+ and Visual Studio 2010.

image