I recently had a need, in an ASP.NET MVC3 application, to read raw HTML, CSS, JS, and image files from disk and return them to the user… A sort of “pass-through” if you will. Normally I’d have simply routed to a custom HTTP handler per file type or just allowed MVC3 to map existing files to supply its own .NET HTTP handlers and do all of this work for me, but in this case I needed the mapped “directory” to switch behind the scenes based on Session settings… So I ultimately had to feed these files through a Controller and Action Method to gain access to the Session.
One problem that came up was being able to determine the MIME type of the content that I’m reading from disk. This is done for you by the HTTP handlers provided in the .NET framework, but when you’re serving files through MVC Controllers, the default HTTP handlers are not used and thus you’re left to figure out the MIME types for yourself.
So, I began to investigate, using ILSpy, how the native/default ASP.NET HTTP handlers determine the MIME types. I came upon a class in the System.Web namespace called System.Web.MimeMapping – this class keeps a private, sealed dictionary of type MimeMappingDictionaryClassic (which extends a private abstract class called MimeMappingDictionaryBase) which holds all knows extensions and their associated MIME types… A sample of the decompiled code which populates it is below:
protected override void PopulateMappings()
{
base.AddMapping(".323", "text/h323");
base.AddMapping(".aaf", "application/octet-stream");
base.AddMapping(".aca", "application/octet-stream");
base.AddMapping(".accdb", "application/msaccess");
base.AddMapping(".accde", "application/msaccess");
base.AddMapping(".accdt", "application/msaccess");
base.AddMapping(".acx", "application/internet-property-stream");
base.AddMapping(".afm", "application/octet-stream");
base.AddMapping(".ai", "application/postscript");
base.AddMapping(".aif", "audio/x-aiff");
base.AddMapping(".aifc", "audio/aiff");
base.AddMapping(".aiff", "audio/aiff");
... // It goes on for a long time
And so on… In total, there are 342 lines of known mappings!
Ultimately, my goal was to get a hold of this functionality in the easiest, most flexible way possible.
In .NET 4.5, MimeMapping exposes a public static method called GetMimeMapping which takes in a file name (or extension) and returns the appropriate MIME type from the aforementioned dictionary. Unfortunately my project is on .NET 4.0 and in that version of the framework this method is internal, not public (why, Microsoft, why?!) and thus was not available to me. So, I felt that I was left with 3 options:
-
Upgrade to .NET 4.5 (not possible at this time due to corporate politics and so on)
-
Copy and paste the entire list of mappings into a dictionary of my own and reference it (yuck!)
-
REFLECTION TO THE RESCUE!
So, with a short bit of code, you too can steal the functionality of the GetMimeMapping method, even if it isn’t public!
First, set up the reflection and cache the MethodInfo in an assembly that references the System.Web namespace. Below is a custom static class I built which wraps the reflective method:
/// <summary>
/// Exposes the Mime Mapping method that Microsoft hid from us.
/// </summary>
public static class MimeMappingStealer
{
// The get mime mapping method info
private static readonly MethodInfo _getMimeMappingMethod = null;
/// <summary>
/// Static constructor sets up reflection.
/// </summary>
static MimeMappingStealer()
{
// Load hidden mime mapping class and method from System.Web
var assembly = Assembly.GetAssembly(typeof(HttpApplication));
Type mimeMappingType = assembly.GetType("System.Web.MimeMapping");
_getMimeMappingMethod = mimeMappingType.GetMethod("GetMimeMapping",
BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
}
/// <summary>
/// Exposes the hidden Mime mapping method.
/// </summary>
/// <param name="fileName">The file name.</param>
/// <returns>The mime mapping.</returns>
public static string GetMimeMapping(string fileName)
{
return (string)_getMimeMappingMethod.Invoke(null /*static method*/, new[] { fileName });
}
}
Now, a quick test via a console application to ensure that it works:
static void Main(string[] args)
{
var fileName1 = "whatever.js";
var fileName2 = "somefile.css";
var fileName3 = "myfile.html";
Console.WriteLine("Output for " + fileName1 + " = "
+ MimeMappingStealer.GetMimeMapping(fileName1));
Console.WriteLine("Output for " + fileName2 + " = "
+ MimeMappingStealer.GetMimeMapping(fileName2));
Console.WriteLine("Output for " + fileName3 + " = "
+ MimeMappingStealer.GetMimeMapping(fileName3));
Console.ReadKey();
}
And running the console application results in success!