Welcome and enjoy the blog

Hide complexity and inconsistency of legacy classes using facade

May 22nd, 2010 by ganton | Posted in Design Patterns | No Comments »
Tagged as: , ,

Very often in a system we may have modules that are implemented by different persons for a long period of time that are inconsistent and it is difficult to use them in a new implementation. Here in this post you can find a sample of solving these problems using facade design pattern, unique interface and IoC.

Let’s assume that we have a set of report building classes that have no common interface but they need similar data in order to build different report views. These are the sample legacy classes that we need to use.

   1: public class BanchmarkReportBuilder

   2: {

   3:     public Stream CreateBenchmarkReport(Data data, AnotherData anotherData, ReportType type)

   4:     {

   5:         return null;

   6:     }

   7: }

   8:  

   9: public class MonitoringReportBuilder

  10: {

  11:     public Stream CreateMonitoringReport(int dataId, int anotherDataId)

  12:     {

  13:         return null;

  14:     }

  15: }

  16:  

  17: public class AdditionalReportBuilder

  18: {

  19:     public Stream CreateAdditionalReport(Data data, int anotherDataId, IList<int> someIds, ReportType type)

  20:     {

  21:         return null;

  22:     }

  23: }

Here are the sample data classes.

   1: public class Data

   2: {

   3:     public int Id { get; set; }

   4:     public string Info { get; set; }

   5: }

   6:  

   7: public class AnotherData

   8: {

   9:     public int Id { get; set; }

  10:     public IList<object> Data { get; set; }

  11: }

  12:  

  13: public enum ReportType

  14: {

  15:     Word = 1,

  16:     Pdf = 2

  17: }

  18:  

  19: public class ReportData

  20: {

  21:     public int Id { get; set; }

  22:     public string Name { get; set; }

  23:     public string ClassReferenceName { get; set; }

  24: }

It is obvious how they were used. Everywhere the same data retrieved in different web pages and then supplied to the report classes. So, we have these old not very nice implementations and they are used even “better”. Let’s say that at some point it is required to have an automated system that will send e-mails with link(s) to a specific report(s) and the user should be able to click on the link and to go to a general report page. This page should somehow build the report and send it back to the end-user. But it may be a problem with the implementations demonstrated above. For sure, we can re-factor all the implementations and then their usage and then to use them in our new implementation but this will cost us plenty of time. In order to reduce the time of the implementation we can skip re-factoring part and to build an unique interface around these legacy classes and to implement just the simple method of the unique interface. This action can be assumed as re-factoring :) .Below is the interface and its implementation in legacy classes.

   1: public interface IReportBuilder

   2: {

   3:     Stream Build(Data data, AnotherData anotherData, ReportType type, params int[] someIds);

   4: }

   5:  

   6: public class BanchmarkReportBuilder : IReportBuilder

   7: {

   8:     public Stream CreateBenchmarkReport(Data data, AnotherData anotherData, ReportType type)

   9:     {

  10:         return null;

  11:     }

  12:  

  13:     public Stream Build(Data data, AnotherData anotherData, ReportType type, params int[] someIds)

  14:     {

  15:         return CreateBenchmarkReport(data, anotherData, type);

  16:     }

  17: }

  18:  

  19: public class MonitoringReportBuilder : IReportBuilder

  20: {

  21:     public Stream CreateMonitoringReport(int dataId, int anotherDataId)

  22:     {

  23:         return null;

  24:     }

  25:  

  26:     public Stream Build(Data data, AnotherData anotherData, ReportType type, params int[] someIds)

  27:     {

  28:         return CreateMonitoringReport(data.Id, anotherData.Id);

  29:     }

  30: }

  31:  

  32: public class AdditionalReportBuilder : IReportBuilder

  33: {

  34:     public Stream CreateAdditionalReport(Data data, int anotherDataId, IList<int> someIds, ReportType type)

  35:     {

  36:         return null;

  37:     }

  38:  

  39:     public Stream Build(Data data, AnotherData anotherData, ReportType type, params int[] someIds)

  40:     {

  41:         return CreateAdditionalReport(data, anotherData.Id, someIds, type);

  42:     }

  43:  

  44: }

What we have now is an unique interface and a very simple implementation of the interface’s method. Actually, the implementation of Build method in all of the legacy classes is just a wrapper around existing legacy method. The next problem that we need to solve is how the new general implementation will now which builder class to instantiate. In my case we use IoC container where it is possible declaratively to specify classes in a XML file configuration. Let’s say that we have a class like those below that holds a general instance of IoC container for all application.

   1: public static class IoCInstance

   2: {

   3:     public static TResult GetObject<TResult>(string name)

   4:     {

   5:         return default(TResult);

   6:     }

   7: }

Next question to answer is how we will know which of the reports we need to build. Fortunately, we have a report meta-data stored in the database. For example, a table with columns id, name, timestamp and classReferenceName is more then enough for this sample. In a real case, you will have more tables and more info based on your needs. We also need to have some repository classes that will retrieve the information from the database. All these sample classes are below.

   1: public interface IReportRepository

   2: {

   3:     ReportData GetReport(int id);

   4: }

   5:  

   6: public interface IDataRepository

   7: {

   8:     Data GetData(int id);

   9:     AnotherData GetAnotherData(int id);

  10: }

  11:  

  12: public class ReportRepository

  13: {

  14:     public ReportData GetReport(int id)

  15:     {

  16:         return new ReportData { Id = 1, Name = "Banchmark Report", ClassReferenceName = "BanchmarkReportBuilder" };

  17:     }

  18: }

  19:  

  20: public class DataRepository

  21: {

  22:     public Data GetData(int id)

  23:     {

  24:         return new Data { Id = 1, Info = "info" };

  25:     }

  26:  

  27:     public AnotherData GetAnotherData(int id)

  28:     {

  29:         return new AnotherData { Id = 1, Data = "data" };

  30:     }

  31: }

As we discussed above using the IReportBuilder interface we solve the inconsistence and complexity of all legacy classes. Having reports’ meta-data in the database we can add in the link a parameter containing report id. Then in the general page we will use it to receive all the information that we need for a specific report. Then having the IoC we can use it to get the right builder instance. All of this is fine but we also want to hide the complexity of using all these different classes. To solve this we will use a facade class that will be able to return a stream of a generated report and the report name based on the general set of parameters that we will send with the link. A sample of a facade class is below.

   1: public interface IReportBiuldingFacade

   2: {

   3:     Stream Build(int reportId, int dataId, int anotherDataId, ReportType documentType, int[] answersetIds, out string reportName);

   4: }

   5:  

   6: public class ReportBiuldingFacade

   7: {

   8:     private IReportRepository reportRepository;

   9:     private IDataRepository dataRepository;

  10:     private string applicationRootPath;

  11:  

  12:     public ReportBiuldingFacade(IReportRepository reportRepository, IDataRepository dataRepository, string applicationRootPath)

  13:     {

  14:         this.reportRepository = reportRepository;

  15:         this.dataRepository = dataRepository;

  16:         this.applicationRootPath = applicationRootPath;

  17:     }

  18:  

  19:     public Stream Build(int reportId, int dataId, int anotherDataId, ReportType documentType, int[] answersetIds, out string reportName)

  20:     {

  21:  

  22:         ReportData reportData = reportRepository.GetReport(reportId);

  23:         Data data = dataRepository.GetData(dataId);

  24:         AnotherData anotherData = dataRepository.GetAnotherData(anotherDataId);

  25:  

  26:         IReportBuilder reportBuilder = IoCInstance.GetObject<IReportBuilder>(reportData.ClassReferenceName);

  27:  

  28:         reportName = reportData.Name.Trim() + "." + documentType.ToString().ToLower();

  29:  

  30:         return reportBuilder.Build(data, anotherData, documentType, answersetIds);

  31:     }

  32: }

And to make the sample complete below you can find a unit test class with methods for the facade. The unit test methods are implemented using NUnit framework and Rhino.Mocks framework. You can see that the tests target is the facade logic and all other classes are mocked. In order to be able to mock the repositories the IoC in the facade constructor was used. In order to abstract from the report builder a stub was created and declared in IoC container XML configuration file for the tests. The stub contains an Action that is used in the test in order to check that the stub method was executed. The first class is the stub and the next is the unit test for the facade.

   1: public class ReportBuilderStub : IReportBuilder

   2: {

   3:     public System.Action MethodExecuted;

   4:  

   5:     public Stream Build(Data data, AnotherData anotherData, ReportType reportType, IList<int> somIds)

   6:     {

   7:         if (MethodExecuted != null)

   8:         {

   9:             MethodExecuted();

  10:         }

  11:  

  12:         return null;

  13:     }

  14: }

   1: [TestFixture]

   2: public class ReportBuildingFacadeTestFixture

   3: {

   4:     [Test]

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

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

   7:     [TestCase(true, true, null, ExpectedException = typeof(ArgumentNullException))]

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

   9:     public void ConstructorTest(bool initializeReportRepository, bool initializeDataRepository, string path)

  10:     {

  11:         IReportRepository reportRepository = null;

  12:         IDataRepository dataRepository = null;

  13:         MockRepository repository = new MockRepository();

  14:  

  15:         if (initializeReportRepository)

  16:         {

  17:             reportRepository = repository.Stub<IReportService>();

  18:         }

  19:  

  20:         if (initializeDataRepository)

  21:         {

  22:             dataRepository = repository.Stub<IQuestionnaireReportService>();

  23:         }

  24:  

  25:         IReportBuildingFacade facade = new ReportBuildingFacade(reportRepository, dataRepository, path);

  26:     }

  27:  

  28:     [Test]

  29:     [TestCase(0, 1, 1, ExpectedException = typeof(ArgumentOutOfRangeException))]

  30:     [TestCase(1, 0, 1, ExpectedException = typeof(ArgumentOutOfRangeException))]

  31:     [TestCase(1, 1, 0, ExpectedException = typeof(ArgumentOutOfRangeException))]

  32:     public void BuildWithWrongParametersTest(int reportId, int dataId, int anotherDataId)

  33:     {

  34:         MockRepository repository = new MockRepository();

  35:         IReportRepository reportRepository = repository.Stub<IReportRepository>();

  36:         IDataRepository dataRepository = repository.Stub<IDataRepository>();

  37:  

  38:         IReportBuildingFacade facade = new ReportBuildingFacade(reportRepository, dataRepository, "path");

  39:  

  40:         string reportName;

  41:         facade.Build(reportId, dataId, anotherDataId, ReportType.Pdf, new int[] { 1 }, out reportName);

  42:     }

  43:  

  44:     [Test]

  45:     [TestCase(1, 1, 1, (int)DocumentType.Pdf)]

  46:     public void BuildTest(int reportId, int dataId, int anotherDataId, int reportTypeId)

  47:     {

  48:         MockRepository repository = new MockRepository();

  49:         IReportRepository reportRepository = repository.StrictMock<IReportRepository>();

  50:         Expect.Call(reportRepository.GetReport(1)).Return(

  51:             new ReportData

  52:             {

  53:                 Id = reportId,

  54:                 Name = "report",

  55:                 ClassReferenceName = "BanchmarkReportBuilder",

  56:             });

  57:         IDataRepository dataRepository = repository.StrictMock<IDataRepository>();

  58:         Expect.Call(dataRepository.GetData(dataId))

  59:             .Return(new Data());

  60:         Expect.Call(dataRepository.GetAnotherData(anotherDataId))

  61:             .Return(new AnotherData());

  62:  

  63:         repository.ReplayAll();

  64:  

  65:         IReportBuildingFacade facade = new ReportBuildingFacade(reportRepository, dataRepository, "path");

  66:         bool buildMethodExecuted = false;

  67:  

  68:         ReportBuilderStub reportBuilder = IoCInstance.GetObject<ReportBuilderStub>("BanchmarkReportBuilder");

  69:         reportBuilder.MethodExecuted = () => buildMethodExecuted = true;

  70:  

  71:         string reportName;

  72:         facade.Build(reportId, dataId, anotherDataId, ReportType.Pdf, new int[] { 1 }, out reportName);

  73:  

  74:         repository.VerifyAll();

  75:  

  76:         Assert.AreEqual("report.pdf", reportName, "returned report name is not correct.");

  77:         Assert.True(buildMethodExecuted, "Build Method of the report builder is not executed.");

  78:     }

  79: }

What to use where or inner join?

May 7th, 2010 by ganton | Posted in SQL | 1 Comment »
Tagged as:

Actually, I received a question like this few days ago and if such a question appear to people why not to right the answer here. :)  If you have two queries like those below what do you think is the difference?

   1: select count(a.id)

   2: from answer a, answerset aset

   3: where a.answersetId = aset.id and aset.isReviewed = 1

   1: select count(a.id)

   2: from answer a

   3: inner join answerset aset on a.answersetId = aset.id

   4: where aset.isReviewed = 1

Logically, there is no difference. They will return the same result and the execution plan will be the same and it is obvious that the performance will be also the same. The execution plan is below. You can see that both queries share 50% of the query cost.

image

Then what to use? I think it is up to you and up to the coding styles that you may follow in your team. If your team prefer to use where just do it or vice versa.

EXECUTE permission denied on object ’sp_sdidebug’, database ‘master’, owner ‘dbo’.

May 6th, 2010 by ganton | Posted in SQL | No Comments »
Tagged as:

An exception that you can get during application  debugging when you attach to an aspnet_wp.exe process. If you use a published web site it is fine but attaching to it in some case can cause this exception to happened. What is the reason? By default the debugger tries to attach to and debug T-SQl code and if the user that your application uses doesn’t have rights to access stored procedures that are required for debugging  this exception will appear. When attaching to a process we see the window below.

image

By default, selected options for aspnet_wp process are to attach to T-SQL code and Managed code. If you need to debug the T-SQL code you must give proper rights to your user but if you don’t need to debug it you can just reconfigure attach to options. For this just press [Select…] and reconfigure it like it is shown below.

image

As you can see T-SQL option is not selected and the debugger will not attach to this code and the error will disappear and it is ready for testing.

Secure VPN connection terminated locally by the client. Reason 414: Failed to establish a TCP connection.

May 4th, 2010 by ganton | Posted in Other | No Comments »
Tagged as:

At the end of last week I updated my Windows XP with SP3. After a while I’ve tried to connect to the company network using Cisco VPN client and it failed to connect with the error stated in post’s header. If you look in the log you can see that the client tries to connect the server but for some reason it is impossible.

8      13:33:29.058  05/04/10  Sev=Info/6    IPSEC/0×6370001F

TCP SYN sent to 0.0.0.0, src port 1076, dst port 443

9      13:33:29.058  05/04/10  Sev=Info/6    IPSEC/0×6370001C

TCP SYN-ACK received from 0.0.0.0, src port 443, dst port 1076

10     13:33:29.058  05/04/10  Sev=Info/6    IPSEC/0×63700020

TCP ACK sent to 0.0.0.0, src port 1076, dst port 443

11     13:33:34.558  05/04/10  Sev=Info/6    IPSEC/0×6370001D

TCP RST received from 0.0.0.0, src port 443, dst port 1076

12     13:33:34.558  05/04/10  Sev=Info/6    IPSEC/0×6370001F

TCP SYN sent to 0.0.0.0, src port 1076, dst port 443

13     13:33:40.058  05/04/10  Sev=Info/6    IPSEC/0×6370001F

TCP SYN sent to 0.0.0.0, src port 1076, dst port 443

14     13:33:44.074  05/04/10  Sev=Info/6    IPSEC/0×6370001F

TCP SYN sent to 0.0.0.0, src port 1076, dst port 443

15     13:33:49.074  05/04/10  Sev=Info/4    CM/0×6310002A

Unable to establish TCP connection on port 443 with server "0.0.0.0"

After analyzing what was installed and what was changed it was clear that the SP3 update causes the error. I’ve checked the version of my VPN client which was 4.0.3. According to the information in the internet this version of Cisco VPN client has problems with SP2. Well, I had SP2 and it worked fine but with SP3 it doesn’t. In Microsoft support has an article that says about problems with SP2. I replaced Cisco VPN client 4.0.3 with version 5.0.0 and it works fine now.

Convert file extension to MIME type - extended

April 23rd, 2010 by ganton | Posted in ASP.Net, Unit testing | 1 Comment »
Tagged as: ,

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!

Convert file extension to MIME type

April 22nd, 2010 by ganton | Posted in ASP.Net | 1 Comment »
Tagged as:

Sometimes in a web application you need to upload files to the server and then download them to the client. To send a file to the client for downloading we need to specify a MIME type into the response content type parameter. This way we provide an information to the client browser what is the type content type of the output stream. In the application that I work at the moment we need to upload different file types and then to allow the end-user to download them. For this purpose I created a class that converts file extension to a MIME type. I used two sources for information about file extensions and the corresponding MIME type – MIME Type Detection in Internet Explorer and Office 2007 File Format MIME Types for HTTP Content Streaming.

   1: public static class FileExtensionConverter

   2: {

   3:     private static IDictionary<string, string> extensionMIMETypeMapping;

   4:     private static string defaultMIMEType = "application/octet-stream";

   5:  

   6:     static FileExtensionConverter()

   7:     {

   8:         extensionMIMETypeMapping = new Dictionary<string, string>()

   9:         {

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

  11:             { "rtf", "text/richtext" },

  12:             { "wav", "audio/wav" },

  13:             { "gif", "image/gif" },

  14:             { "jpeg", "image/jpeg" },

  15:             { "png", "image/png" },

  16:             { "tiff", "image/tiff" },

  17:             { "bmp", "image/bmp" },

  18:             { "avi", "video/avi" },

  19:             { "mpeg", "video/mpeg" },

  20:             { "pdf", "application/pdf" },

  21:             { "doc", "application/msword" },

  22:             { "dot", "application/msword" },

  23:             { "docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },

  24:             { "dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template" },

  25:             { "xls", "application/vnd.ms-excel" },

  26:             { "xlt", "application/vnd.ms-excel" },

  27:             { "csv", "application/vnd.ms-excel" },

  28:             { "xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },

  29:             { "xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template" },

  30:             { "ppt", "application/vnd.ms-powerpoint" },

  31:             { "pot", "application/vnd.ms-powerpoint" },

  32:             { "pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation" },

  33:             { "potx", "application/vnd.openxmlformats-officedocument.presentationml.template" }

  34:         };

  35:     }

  36:  

  37:     public static string ToMIMEType(string extension)

  38:     {

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

  40:         {

  41:             return defaultMIMEType;

  42:         }

  43:  

  44:         string lowerExtension = extension.ToLower();

  45:         string mime;

  46:         if (!extensionMIMETypeMapping.TryGetValue(lowerExtension, out mime))

  47:         {

  48:             mime = defaultMIMEType;

  49:         }

  50:  

  51:         return mime;

  52:     }

  53: }

You can see that it is not a rocket science but I thought that it may be useful to have it ready and to write it from scratch. In this class a dictionary is used to store the mapping and it is initialized in a static constructor with hard coded mapping information. For sure, a better solution is to store this information outside of the application in a store such as a database table or xml file or even a simple plain text file. If the type is not in the mapping it returns a default mapping that is “application/octet-stream”.