Convert file extension to MIME type - extended

April 23rd, 2010 by ganton | Print

In my last post I wrote a sample class that converts file extension to MIME type. Is it obvious that in many cases it is much better to have configurable list of mappings. For instance, you may have a configurable “white list” of file extensions that your web application allow to be uploaded and then downloaded. In this case to have a configurable mapping information will be very useful. I’ve changed the FileExtensionConverter from my previous post in a way to use configurable set of mappings. The updated version is below.

   1: public class FileExtensionConverter

   2: {

   3:     private IDictionary<string, string> extensionMimeTypeMapping;

   4:     private string defaultMimeType;

   5:  

   6:     public FileExtensionConverter(IMimeTypeMappingReader reader)

   7:     {

   8:         ParametersGuard.NotNull("reader", reader);

   9:         reader.Load(out extensionMimeTypeMapping, out defaultMimeType);

  10:     }

  11:  

  12:     public string ToMIMEType(string extension)

  13:     {

  14:         if (extension == null || extension.Length == 0)

  15:         {

  16:             return defaultMimeType;

  17:         }

  18:  

  19:         string lowerExtension = extension.ToLower();

  20:         string mime;

  21:         if (!extensionMimeTypeMapping.TryGetValue(lowerExtension, out mime))

  22:         {

  23:             mime = defaultMimeType;

  24:         }

  25:  

  26:         return mime;

  27:     }

  28: }

The main difference is that it now requires a reader to be provided to its constructor. This reader is a class that allows us to abstract the reading\parsing part from the converter class and this way we can use different implementations of the reader for reading data from plain text file, XML or database.

   1: public interface IMimeTypeMappingReader

   2: {

   3:     void Load(out IDictionary<string, string> mapping, out string defaultMIMEType);

   4: }

This interface consists of only one method that reads/parses mapping information. I’ve created an implementation for this interface that works with an XML file like those below.

   1: <?xml version="1.0" encoding="utf-8" ?>

   2: <root>

   3:   <default>

   4:     <mime>application/octet-stream</mime>

   5:   </default>

   6:   <mapping>

   7:     <extension>txt</extension>

   8:     <mime>text/plain</mime>

   9:   </mapping>

  10:   <mapping>

  11:     <extension>rtf</extension>

  12:     <mime>text/richtext</mime>

  13:   </mapping>

  14:   <mapping>

  15:     <extension>wav</extension>

  16:     <mime>audio/wav</mime>

  17:   </mapping>

  18:   <mapping>

  19:     <extension>gif</extension>

  20:     <mime>image/gif</mime>

  21:   </mapping>

  22:   <mapping>

  23:     <extension>jpeg</extension>

  24:     <mime>image/jpeg</mime>

  25:   </mapping>

  26:   <mapping>

  27:     <extension>png</extension>

  28:     <mime>image/png</mime>

  29:   </mapping>

  30:   <mapping>

  31:     <extension>tiff</extension>

  32:     <mime>image/tiff</mime>

  33:   </mapping>

  34:   <mapping>

  35:     <extension>bmp</extension>

  36:     <mime>image/bmp</mime>

  37:   </mapping>

  38:   <mapping>

  39:     <extension>avi</extension>

  40:     <mime>video/avi</mime>

  41:   </mapping>

  42:   <mapping>

  43:     <extension>mpeg</extension>

  44:     <mime>video/mpeg</mime>

  45:   </mapping>

  46:   <mapping>

  47:     <extension>pdf</extension>

  48:     <mime>application/pdf</mime>

  49:   </mapping>

  50:   <mapping>

  51:     <extension>doc</extension>

  52:     <mime>application/msword</mime>

  53:   </mapping>

  54:   <mapping>

  55:     <extension>dot</extension>

  56:     <mime>application/msword</mime>

  57:   </mapping>

  58:   <mapping>

  59:     <extension>docx</extension>

  60:     <mime>application/vnd.openxmlformats-officedocument.wordprocessingml.document</mime>

  61:   </mapping>

  62:   <mapping>

  63:     <extension>dotx</extension>

  64:     <mime>application/vnd.openxmlformats-officedocument.wordprocessingml.template</mime>

  65:   </mapping>

  66:   <mapping>

  67:     <extension>xls</extension>

  68:     <mime>application/vnd.ms-excel</mime>

  69:   </mapping>

  70:   <mapping>

  71:     <extension>xlt</extension>

  72:     <mime>application/vnd.ms-excel</mime>

  73:   </mapping>

  74:   <mapping>

  75:     <extension>csv</extension>

  76:     <mime>application/vnd.ms-excel</mime>

  77:   </mapping>

  78:   <mapping>

  79:     <extension>xlsx</extension>

  80:     <mime>application/vnd.openxmlformats-officedocument.spreadsheetml.sheet</mime>

  81:   </mapping>

  82:   <mapping>

  83:     <extension>xltx</extension>

  84:     <mime>application/vnd.openxmlformats-officedocument.spreadsheetml.template</mime>

  85:   </mapping>

  86:   <mapping>

  87:     <extension>ppt</extension>

  88:     <mime>application/vnd.ms-powerpoint</mime>

  89:   </mapping>

  90:   <mapping>

  91:     <extension>pot</extension>

  92:     <mime>application/vnd.ms-powerpoint</mime>

  93:   </mapping>

  94:   <mapping>

  95:     <extension>pptx</extension>

  96:     <mime>application/vnd.openxmlformats-officedocument.presentationml.presentation</mime>

  97:   </mapping>

  98:   <mapping>

  99:     <extension>potx</extension>

 100:     <mime>application/vnd.openxmlformats-officedocument.presentationml.template</mime>

 101:   </mapping>

 102: </root>

Below is the implementation of IMimeTypeMappingReader for the XML file.

   1: public class XmlMimeTypeMappingReader : IMimeTypeMappingReader

   2: {

   3:     private string fileContent;

   4:  

   5:     public XmlMimeTypeMappingReader(IFileReadWriter readWriter, string filePath)

   6:     {

   7:         ParametersGuard.NotNull("readWriter", readWriter);

   8:         ParametersGuard.NotNullOrEmpty("filePath", filePath);

   9:  

  10:         fileContent = readWriter.ReadAllText(filePath);

  11:         if (fileContent.Length == 0)

  12:         {

  13:             throw new ApplicationException("The file is empty. File - " + filePath);

  14:         }

  15:     }

  16:  

  17:     public void Load(out IDictionary<string, string> mapping, out string defaultMimeType)

  18:     {

  19:         mapping = new Dictionary<string, string>();

  20:         defaultMimeType = "application/octet-stream";

  21:         string mimeElementName = "mime";

  22:         string extensionElementName = "extension";

  23:  

  24:         XDocument document = XDocument.Parse(fileContent);

  25:  

  26:         XElement defaultElement = document.Root.Element("default");

  27:         XElement mimeElement;

  28:         if (defaultElement != null)

  29:         {

  30:             mimeElement = defaultElement.Element(mimeElementName);

  31:             if (mimeElement.Value.Length != 0)

  32:             {

  33:                 defaultMimeType = mimeElement.Value;

  34:             }

  35:         }

  36:  

  37:         XElement extensionElement;

  38:         foreach (XElement element in document.Root.Elements("mapping"))

  39:         {

  40:             mimeElement = element.Element(mimeElementName);

  41:             extensionElement = element.Element(extensionElementName);

  42:             if (mimeElement.Value.Length == 0 || extensionElement.Value.Length == 0)

  43:             {

  44:                 throw new ApplicationException("Invalid mapping data. Mime type or extension is not specified.");

  45:             }

  46:  

  47:             mapping.Add(extensionElement.Value, mimeElement.Value);

  48:         }

  49:     }

  50: }

You may notice that the constructor of this class requires an implementation of IFileReadWriter. it is required in order to abstract the actual file read from file system from the reader. In other words, it should be a wrapper around file operations. It will allow us to abstract from these operations and to unit test our implementations without accessing the file system. I think it is good for this sample to also provide to you the test classes for FileExtensionConverter and XmlMimetypeMappingReader. For the unit testing NUnit and Rhino.Mocks libraries are used.

FileExtensionConverterTestFixture:

   1: [TestFixture]

   2: public class FileExtensionConverterTestFixture

   3: {

   4:     [Test]

   5:     [TestCase(null, "application/octet-stream")]

   6:     [TestCase("", "application/octet-stream")]

   7:     [TestCase("exe", "application/octet-stream")]

   8:     [TestCase("txt", "text/plain")]

   9:     public void ToMIMETypeTest(string extension, string expectedResult)

  10:     {

  11:         MockRepository mockRepository = new MockRepository();

  12:         IMimeTypeMappingReader reader = mockRepository.StrictMock<IMimeTypeMappingReader>();

  13:         IDictionary<string, string> mapping = new Dictionary<string, string> 

  14:         {

  15:             { "txt", "text/plain" }

  16:         };

  17:         string defaultMimeType = "application/octet-stream";

  18:         Expect.Call(delegate { reader.Load(out mapping, out defaultMimeType); }).OutRef(mapping, defaultMimeType);

  19:         mockRepository.ReplayAll();

  20:  

  21:         FileExtensionConverter converter = new FileExtensionConverter(reader);

  22:         string result = converter.ToMIMEType(extension);

  23:  

  24:         mockRepository.VerifyAll();

  25:  

  26:         Assert.AreEqual(expectedResult, result);

  27:     }

  28: }

And XmlMimetypeMappingReaderTestFixture:

   1: [TestFixture]

   2: public class XmlMimeTypeMappingReaderTestFixture

   3: {

   4:     [Test]

   5:     [TestCase(false, null, ExpectedException = typeof(ArgumentNullException))]

   6:     [TestCase(true, "", ExpectedException = typeof(ArgumentOutOfRangeException))]

   7:     [TestCase(true, "path\\file.xml", ExpectedException = typeof(ApplicationException))]

   8:     public void ConstructorErrorsTest(bool initialize, string path)

   9:     {

  10:         IFileReadWriter fileReadWriter = null;

  11:         MockRepository mockRepository = new MockRepository();

  12:         if (initialize)

  13:         {                

  14:             fileReadWriter = mockRepository.StrictMock<IFileReadWriter>();

  15:             Expect.Call(fileReadWriter.ReadAllText(path)).Return(string.Empty);

  16:         }

  17:  

  18:         mockRepository.ReplayAll();

  19:  

  20:         XmlMimeTypeMappingReader xmlReader = new XmlMimeTypeMappingReader(fileReadWriter, path);

  21:     }

  22:  

  23:     [Test]

  24:     public void ConstructorTest()

  25:     {

  26:         string path = "path//file.xml";

  27:         MockRepository mockRepository = new MockRepository();

  28:         IFileReadWriter fileReadWriter = mockRepository.StrictMock<IFileReadWriter>();

  29:         Expect.Call(fileReadWriter.ReadAllText(path)).Return("test");

  30:         mockRepository.ReplayAll();

  31:  

  32:         XmlMimeTypeMappingReader xmlReader = new XmlMimeTypeMappingReader(fileReadWriter, path);

  33:  

  34:         mockRepository.VerifyAll();

  35:     }

  36:  

  37:     [Test]

  38:     [TestCase("<root><mapping><extension></extension><mime>text/plain</mime></mapping></root>", ExpectedException = typeof(ApplicationException))]

  39:     [TestCase("<root><mapping><extension>txt</extension><mime></mime></mapping></root>", ExpectedException = typeof(ApplicationException))]

  40:     [TestCase("<root><mapping><extension>txt</extension><mime>text/plain</mime></mapping></root>")]

  41:     [TestCase("<root><default><mime>application/octet-stream</mime></default><mapping><extension>txt</extension><mime>text/plain</mime></mapping></root>")]

  42:     public void LoadTest(string xml)

  43:     {

  44:         string path = "path//file.xml";

  45:         MockRepository mockRepository = new MockRepository();

  46:         IFileReadWriter fileReadWriter = mockRepository.StrictMock<IFileReadWriter>();

  47:         Expect.Call(fileReadWriter.ReadAllText(path)).Return(xml);

  48:         mockRepository.ReplayAll();

  49:  

  50:         XmlMimeTypeMappingReader xmlReader = new XmlMimeTypeMappingReader(fileReadWriter, path);

  51:         IDictionary<string, string> mapping;

  52:         string defaultMimeType;

  53:         xmlReader.Load(out mapping, out defaultMimeType);

  54:  

  55:         mockRepository.VerifyAll();

  56:  

  57:         Assert.AreEqual("application/octet-stream", defaultMimeType);

  58:         Assert.AreEqual(1, mapping.Count);

  59:         Assert.AreEqual("text/plain", mapping["txt"]);

  60:     }

  61: }

That’s it. Have fun!

One Response to “Convert file extension to MIME type - extended”

  1. cheenujunk Says:

    Its a very useful page.

Leave a Reply