Responding to audio “Range” requests in ASP.NET MVC
18 November 2013, by Usman Iqbal
Range requests are used to request a portion of a file (for example, the first half of an mp3 file). This is achieved by sending a “Range” header in the HTTP request.
As we found for one of our customers recently, getting this to work for ASP.NET MVC can be a bit tricky. If you find yourself in a similar position, there are a few things worth bearing in mind:
1. IIS Supports Range Requests natively
IIS already responds appropriately to “Range” request headers and has done so for a long time. So if your multimedia files are simply being served through IIS, range requests should all just work. Unfortunately this wasn’t the case for us – all of our audio files live in an Amazon S3 bucket, which means that our requests have to go through the MVC website.
2. ASP.NET does not support range requests natively
This is exactly why it didn’t work for us – as we were going through our MVC site, the requests were being handled by ASP.NET (not IIS), which ignores the “Range” request header. This is true for .NET Framework up to and including 4.
3. To add support in ASP.NET MVC, you need to write a custom ActionResult
This is what we did to support Range requests. In the end, we took some code written by someone else (see the first web link at the bottom) and heavily modified it to fit our particular needs. If you don’t use MVC, then a Request Handler would suffice – indeed, the code we used was for a custom request handler!
4. The ASP.NET Development Server doesn’t like Response Header changes
Generally speaking, the ASP.NET development server is pretty good. However it does have its limitations – a particularly relevant one is that it can’t handle adding response headers (which is required to properly respond to a “Range” request). However it doesn’t throw an error; instead it silently fails (though if you put a breakpoint in the right place, it says “This operation requires IIS integrated pipeline”.
So, in order to test the new ActionResult \ Request Handler, you’ll need to deploy to IIS.
5. Amazon S3 doesn’t respect content types
As noted earlier, the audio files are hosted in a bucket on Amazon S3. When retrieving files from here, the content type returned was always “application/octet-stream” and not, for example, “audio/mpeg” for an mp3 file.
This causes problems down the line – many browsers expect a specific content type to be returned from its range-enabled request and, if the content-type doesn’t match, it just doesn’t work. We got around this by looking at the file extension and manually changing it (not very elaborate, but it worked in our case!).
6. The Firefox Inspector is annoying for debugging
Browser inspectors were invaluable in confirming this was all working. However, Firefox’s specific inspector was very frustrating to use as, for some reason, when it sent a request for an audio file it refused to store the request details in the “Network” tab! Even navigating to the audio file directly didn’t work (it was there for a split second… then gone!).
When you get to testing this, I would recommend doing the initial testing in something else and then confirming Firefox works!
7. Firefox prefers ogg files to mp3
Audio file support for Html 5 isn’t great, with some people supporting mp3, others supporting ogg etc. Firefox, as of version 21, apparently supports both of these formats. However, it still seems to prefer ogg over mp3 and will actively ask for that first.
The following web pages were very useful in getting this all working:
http://dotnetslackers.com/articles/aspnet/Range-Specific-Requests-in-ASP-NET.aspx#using-the-rangerequesthandlerbase-http-handler – By far the most useful page – discusses the problem of range requests in ASP.NET and gives a solution. This is what we ultimately started with and modified to fit our needs.
http://blogs.visigo.com/chriscoulson/easy-handling-of-http-range-requests-in-asp-net/ – Describes an alternative to the code above.
http://www.codeproject.com/Articles/533932/Custom-ASP-NET-MVC-ActionResults – Some background on defining a Custom ActionResult