78

Can ASP.Net routing (not MVC) be used to serve static files?

Say I want to route

http://domain.tld/static/picture.jpg

to

http://domain.tld/a/b/c/picture.jpg

and I want to do it dynamically in the sense that the rewritten URL is computed on the fly. I cannot set up a static route once and for all.

Anyway, I can create a route like this:

routes.Add(
  "StaticRoute", new Route("static/{file}", new FileRouteHandler())
);

In the FileRouteHandler.ProcessRequest method I can rewrite the path from /static/picture.jpg to /a/b/c/picture.jpg. I then want to create a handler for static files. ASP.NET uses the StaticFileHandler for this purpose. Unfortunately, this class is internal. I have tried to create the handler using reflection and it actually works:

Assembly assembly = Assembly.GetAssembly(typeof(IHttpHandler));
Type staticFileHandlerType = assembly.GetType("System.Web.StaticFileHandler");
ConstructorInfo constructorInfo = staticFileHandlerType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null);
return (IHttpHandler) constructorInfo.Invoke(null);

But using internal types doesn't seem to be the proper solution. Another option is to implement my own StaticFileHandler, but doing so properly (supporting HTTP stuff like ranges and etags) is non-trivial.

How should I approach routing of static files in ASP.NET?

Lance U. Matthews
  • 15,725
  • 6
  • 48
  • 68
Martin Liversage
  • 104,481
  • 22
  • 209
  • 256

5 Answers5

58

Why not use IIS to do this? You could create a redirect rule to point any requests from the first route to the second one before it even gets to your application. Because of this, it would be a quicker method for redirecting requests.

Assuming you have IIS7+, you would do something like...

<rule name="Redirect Static Images" stopProcessing="true">
  <match url="^static/?(.*)$" />
  <action type="Redirect" url="/a/b/c/{R:1}" redirectType="Permanent" />
</rule>

Or, if you don't need to redirect, as suggested by @ni5ni6:

<rule name="Rewrite Static Images" stopProcessing="true">
  <match url="^static/?(.*)$" />
  <action type="Rewrite" url="/a/b/c/{R:1}" />
</rule>

Edit 2015-06-17 for @RyanDawkins:

And if you're wondering where the rewrite rule goes, here is a map of its location in the web.config file.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <!-- rules go below -->
        <rule name="Redirect Static Images" stopProcessing="true">
          <match url="^static/?(.*)$" />
          <action type="Redirect" url="/a/b/c/{R:1}" redirectType="Permanent" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>
Dan Atkinson
  • 11,391
  • 14
  • 81
  • 114
  • I would prefer this approach, as well. Moving it out to the pipeline and let the IIS worker process handle the redirect is more efficient than spinning cycles actually processing the route via the runtime, I would assume. Even if the amount of churn on the server is about the same, I would go the route (pun intended) of not using the routes and keep the redirect outside of the application itself. – Joseph Ferris Jul 26 '09 at 19:00
  • What wasn't clear from my initial version of the question was that the rewritten URL is computed on the fly (I'm not concerned about performance here). I have updated the question to clarify that. Anyway, thanks for replying. – Martin Liversage Jul 26 '09 at 20:52
  • 1
    (deleted my previous comment and re-added it with a non-shortened url). Martin, Performance issues aside, why would you want to handle this url in the application, when handling it outside would be better? I suppose that you could create a controller action which handles this, but here is something you could have a look at:- http://geekswithblogs.net/sankarsan/archive/2009/01/18/developing-custom-routehandler.aspx – Dan Atkinson Jul 20 '11 at 11:52
  • I just changed type="Redirect" to "Rewrite" and it works great in case you dont need dynamic generation of rewriten url – ni5ni6 Jun 21 '12 at 14:03
  • 1
    @RyanDawkins In the web.config. Please see my edit for more information about its exact location. – Dan Atkinson Jun 17 '15 at 20:43
  • 1
    Great answer, worked first time. Now that doesn't happen often! – dahui Jan 10 '18 at 11:46
  • Sorry for offtop, but can you look at this question? https://stackoverflow.com/questions/55488381/wwwroot-in-mvc5 – Troll the Legacy Apr 03 '19 at 09:19
  • Exactly what I needed for my React app for client-side routing. – William Herrmann Dec 20 '21 at 22:31
39

After digging through this problem for a few hours, I found that simply adding ignore rules will get your static files served.

In RegisterRoutes(RouteCollection routes), add the following ignore rules:

routes.IgnoreRoute("{file}.js");
routes.IgnoreRoute("{file}.html");
Benj Sanders
  • 481
  • 5
  • 15
Robert Mao
  • 1,911
  • 22
  • 24
  • For me it works, but i can't parse a JS file in a View Folder of an Area, To fix that, i use instead another folder in the base folder of the area like /Areas/MyArea/ClientScripts/foo.js – eka808 Jan 30 '12 at 16:00
  • 30
    Somehow I seem to miss a piece in the puzzle... how does ignoring routes to your static files help with routing from `/static` to `/a/b/c` (which the OP asked for)? Could you please shed some more light on your answer, I would really like to understand this solution. – Oliver Feb 13 '13 at 10:22
  • 21
    Agree with Oliver that this does NOT answer the OP and should not be accepted as the solution. – dhochee Mar 12 '13 at 22:42
  • What if you need your MVC controller to first check if a user logged in before routing there? Ignoring a route is not the same thing as redirecting to a static folder structure. – pilavdzice Sep 18 '14 at 16:47
  • easiest way to add your robots.txt – joverall22 Oct 03 '15 at 04:54
13

I've had a similar problem. I ended up using HttpContext.RewritePath:

public class MyApplication : HttpApplication
{
    private readonly Regex r = new Regex("^/static/(.*)$", RegexOptions.IgnoreCase);

    public override void Init()
    {
        BeginRequest += OnBeginRequest;
    }

    protected void OnBeginRequest(object sender, EventArgs e)
    {
        var match = r.Match(Request.Url.AbsolutePath);
        if (match.Success)
        {
            var fileName = match.Groups[1].Value;
            Context.RewritePath(string.Format("/a/b/c/{0}", fileName));
        }
    }
}
sheikhomar
  • 614
  • 6
  • 6
  • 1
    Thanks so much for sharing this. Ran across this looking for a solution for URL Rewrite during local development, and realized I can do this everywhere instead. I'm using it not for input parsing, but for JS and CSS file version management. – Brendan Moore Dec 17 '13 at 22:37
  • For anyone interested, in order to manage files of the form `filename.v1234.js`, I'm using `private readonly Regex regJS = new Regex("^(.*)([.]v[0-9]+)([.](js|css))$", RegexOptions.IgnoreCase);` and then on matches I do `Context.RewritePath(string.Format("{0}{1}", matchJS.Groups[1].Value, matchJS.Groups[3].Value));` – Brendan Moore Dec 17 '13 at 22:43
7

I came up with an alternative to using the internal StaticFileHandler. In the IRouteHandler I call HttpServerUtility.Transfer:

public class FileRouteHandler : IRouteHandler {

  public IHttpHandler GetHttpHandler(RequestContext requestContext) {
    String fileName = (String) requestContext.RouteData.Values["file"];
    // Contrived example of mapping.
    String routedPath = String.Format("/a/b/c/{0}", fileName);
    HttpContext.Current.Server.Transfer(routedPath);
    return null; // Never reached.
  }

}

This is a hack. The IRouteHandler is supposed to return an IHttpHandler and not abort and transfer the current request. However, it does actually achieve what I want.

Using the internal StaticFileHandler is also somewhat a hack since I need reflection to get access to it, but at least there is some documentation on StaticFileHandler on MSDN making it a slightly more "official" class. Unfortunately I don't think it is possible to reflect on internal classes in a partial trust environment.

I will stick to using StaticFileHandler as I don't think it will get removed from ASP.NET in the foreseeable future.

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
4

You need to add TransferRequestHandler for handling your static files.Please see following answer https://stackoverflow.com/a/21724783/22858

Community
  • 1
  • 1
Prerak K
  • 10,940
  • 7
  • 30
  • 37