Friday, November 30, 2012

Sending stream to ServiceStack

Recently I needed to make a ServiceStack service which can receive big files, so I wanted to use streaming to accomplish this. Unfortunately there isn't much information about using streams with ServiceStack, so I decided to share my experience.

We'll create a sample solution containing both Server and Client. We will create a class library containing the service itself and an Utility project. So here is the structure of our solution:
So let's continue with the implementation of the service.
First of all we'll need to install the ServiceStack nuget package in the ServiceStackStreaming.Service project:
PM> Install-Package ServiceStack
This will add the dlls needed for ServiceStack. Now let's create the DTO:
using ServiceStack.ServiceHost;

namespace ServiceStackStreaming.Service
{
    [Route("/upload/{FileName}", "POST")]
    public class UploadPackage : IRequiresRequestStream
    {
        public System.IO.Stream RequestStream { get; set; }

        public string FileName { get; set; }
    }
}
To enable Streaming support we need to implement IRequiresRequestStream which needs a RequestStream property of type System.IO.Stream. We'll add a FileName property and include it in the Route so that we would be able to pass the uploaded file name.
The next thing to do is to create the service itself:
using ServiceStack.Common.Web;
using ServiceStackStreaming.Utility;
using System;
using System.IO;

namespace ServiceStackStreaming.Service
{
    public class UploadService : ServiceStack.ServiceInterface.Service
    {
        public object Post(UploadPackage request)
        {
            // hack - get the properties from the request
            if (string.IsNullOrEmpty(request.FileName))
            {
                var segments = base.Request.PathInfo.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
                request.FileName = segments[1];
            }

            string resultFile = Path.Combine(@"C:\Temp", request.FileName);
            if (File.Exists(resultFile))
            {
                File.Delete(resultFile);
            }
            using (FileStream file = File.Create(resultFile))
            {
                request.RequestStream.Copy(file);
            }
            
            return new HttpResult(System.Net.HttpStatusCode.OK);
        }
    }
}

Our dummy service will save the incoming file in the "C:\Temp" directory. With the code from line 12 to line 17 we are getting the FileName property if it's not set. It seems that when using streaming the additional properties are not processed and they are always null, so we'll do this little hack to get the properties parsing the request url.
The other trick we use here is the extension method of the System.IO.Stream class wich we have implemented in the ServiceStackStreaming.Utility project:
using System.IO;

namespace ServiceStackStreaming.Utility
{
    public static class StreamExtender
    {
        public static void Copy(this Stream instance, Stream target)
        {
            int bytesRead = 0;
            int bufSize = copyBuf.Length;

            while ((bytesRead = instance.Read(copyBuf, 0, bufSize)) > 0)
            {
                target.Write(copyBuf, 0, bytesRead);
            }
        }
        private static readonly byte[] copyBuf = new byte[0x1000];
    }
}
this simply copes the instance stream to the target stream. Another option is to use ServiceStack StreamExtensions WriteTo method instead of creating this utility method.
The last thing we need to do to create a functional service is to add the AppHost class, we will inherit  AppHostHttpListenerBase as we want to host the service in a window console application.
using ServiceStack.WebHost.Endpoints;

namespace ServiceStackStreaming.Service
{
    public class AppHost : AppHostHttpListenerBase
    {
        public AppHost() : base("Agent", typeof(UploadService).Assembly) { }

        public override void Configure(Funq.Container container)
        {
            // we can add the routing here instead of adding it as attribute to the DTO
            //Routes
            //    .Add("/upload/{FileName}", "POST");
        }
    }
}
We can configure the route here, but I prefer doing this with attribute.
Now let's host the service. To do this we'll need to add the same ServiceStack nuget to the SertviceStackStreaming.Server project and add the following code to the Program.cs file:
using ServiceStackStreaming.Service;
using System;

namespace ServiceStackStreaming.Server
{
    class Program
    {
        static void Main(string[] args)
        {
            var appHost = new AppHost();
            appHost.Init();
            appHost.Start("http://*:1999/");

            Console.WriteLine("Service listening on port 1999!");
            Console.ReadKey();
        }
    }
}

This will be enough to host the service listening to port 1999.
Now let's call the service from the ServiceStackStreaming.Client (again we'll have to instal the sam e nuget package here).
using ServiceStackStreaming.Utility;
using System.IO;
using System.Net;

namespace ServiceStackStreaming.Client
{
    class Program
    {
        static void Main(string[] args)
        {
            string filePath = @"c:\temp\upload.zip";
            
            HttpWebRequest client = (HttpWebRequest)WebRequest.Create("http://localhost:1999/upload/upload-copy.zip");
            client.Method = WebRequestMethods.Http.Post;
            
            // the following 4 rows enable streaming 
            client.AllowWriteStreamBuffering = false;
            client.SendChunked = true;
            client.ContentType = "multipart/form-data;";
            client.Timeout = int.MaxValue;

            using (FileStream fileStream = File.OpenRead(filePath))
            {
                fileStream.Copy(client.GetRequestStream());
            }

            var response = new StreamReader(client.GetResponse().GetResponseStream()).ReadToEnd();
        }
    }
}
And that's it. We create WebRequest, set the needed properties to enable streaming on the client and copy the file stream to the request stream. This will call the service and will upload the "C:\Temp\upload.zip" file as upload-copy.zip file.

You can find the sample code here: ServiceStackStreaming.zip

Thursday, November 15, 2012

Testing private methods with Visual Studio

When writing unit tests in most of the cases we have to test private methods. Let's see how this can be achieved using Visual Studio. I will give you three options:

  1. You can test private methods using Reflection, but this is not always a good option, as when you change the name or input arguments of a Method, you won't get any error when building, but you will get exceptions run time.
    We will use the project from my previous post Getting console output within a unit test.
    We will add a private method to the DummyClass:
    using System;
    
    namespace ConsoleLogger
    {
        public class DummyClass
        {
            public void WriteToConsole(string text)
            {
                Console.Write(text);
            }
    
            private void PrivateWriteToConsole(string text)
            {
                Console.Write("Private: " + text);
            }
        }
    }
    
    Now we can add a unit test to test the method using reflection:
    [TestMethod]
    public void PrivateWriteToConsoleReflection()
    {
        var currentConsoleOut = Console.Out;
    
        DummyClass target = new DummyClass();
        Type type = typeof(DummyClass);
    
        string text = "Hello";
    
        using (var consoleOutput = new ConsoleOutput())
        {
            var method = type.GetMethod("PrivateWriteToConsole", 
                BindingFlags.NonPublic | BindingFlags.Instance);
            method.Invoke(target, new object[1] { text });
    
            Assert.AreEqual(string.Format("Private: {0}", text), 
                consoleOutput.GetOuput());
        }
    
        Assert.AreEqual(currentConsoleOut, Console.Out);
    }
    We get and invoke the method using reflection.

  2. Another options is to use PrivateObject class. Using it you can easily call private method, but you have the same problem, you won't get compile exception when name or parameters are changed. Here is the same test written using PrivateObject:
    [TestMethod]
    public void PrivateWriteToConsolePrivateObject()
    {
        var currentConsoleOut = Console.Out;
    
        PrivateObject target = new PrivateObject(typeof(DummyClass));
    
        string text = "Hello";
    
        using (var consoleOutput = new ConsoleOutput())
        {
            target.Invoke("PrivateWriteToConsole", text);
    
            Assert.AreEqual(string.Format("Private: {0}", text), 
                consoleOutput.GetOuput());
        }
    
        Assert.AreEqual(currentConsoleOut, Console.Out);
    }
    
  3. And here is the third option which I think is the best. You can add .accessor file containing the name of the assembly whose private methods you want to see, or you can use Visual Studio "Create Unit Tests..." wizard to do this for you:

    this will add the following unit test:
    /// 
    ///A test for PrivateWriteToConsole
    ///
    [TestMethod]
    [DeploymentItem("ConsoleLogger.exe")]
    public void PrivateWriteToConsoleTest()
    {
        DummyClass_Accessor target = new DummyClass_Accessor(); // TODO: Initialize to an appropriate value
        string text = string.Empty; // TODO: Initialize to an appropriate value
        target.PrivateWriteToConsole(text);
        Assert.Inconclusive("A method that does not return a value cannot be verified.");
    }
    
    we will modify it for our needs:
    /// 
    ///A test for PrivateWriteToConsole
    ///
    [TestMethod]
    [DeploymentItem("ConsoleLogger.exe")]
    public void PrivateWriteToConsoleTest()
    {
        var currentConsoleOut = Console.Out;
    
        DummyClass_Accessor target = new DummyClass_Accessor();
    
        string text = "Hello";
    
        using (var consoleOutput = new ConsoleOutput())
        {
            target.PrivateWriteToConsole(text);
    
            Assert.AreEqual(string.Format("Private: {0}", text),
                consoleOutput.GetOuput());
        }
    
        Assert.AreEqual(currentConsoleOut, Console.Out);
    }
    
    When Visual Studio builds the project it will generate ConsoleLogger_Accessor.exe assembly, containing DummyClass_Accessor class with public methods only.
  4. Just to mention that you can test internal methods using the same approaches, but you will have one more option - in the AssemblyInfo file of the assembly being tested, you can add  InternalsVisibleTo attribute to specify which assembly will see the internal methods, in our case:
    [assembly: InternalsVisibleTo("ConsoleLogger.Tests")]
    
    now we will add an internal method to the same class:
    using System;
    
    namespace ConsoleLogger
    {
        public class DummyClass
        {
            public void WriteToConsole(string text)
            {
                Console.Write(text);
            }
    
            private void PrivateWriteToConsole(string text)
            {
                Console.Write("Private: " + text);
            }
    
            internal void InternalWriteToConsole(string text)
            {
                Console.Write("Internal: " + text);
            }
        }
    }
    
    and here is the working test method:
    [TestMethod]
    public void InternalWriteToConsoleTest()
    {
        var currentConsoleOut = Console.Out;
    
        DummyClass target = new DummyClass();
    
        string text = "Hello";
    
        using (var consoleOutput = new ConsoleOutput())
        {
            target.InternalWriteToConsole(text);
    
            Assert.AreEqual(string.Format("Internal: {0}", text),
                consoleOutput.GetOuput());
        }
    
        Assert.AreEqual(currentConsoleOut, Console.Out);
    }
    
And here's the code - ConsoleLogger.zip

Getting console output within a unit test

Today I needed to test a method which writes to the Console to validate the ouput. It is not hard to change the default console output and check the result. However you may forget to return the original output at the end. So let's take a look at my solution.

Let say we have the following class we want to test:
using System;

namespace ConsoleLogger
{
    public class DummyClass
    {
        public void WriteToConsole(string text)
        {
            Console.Write(text);
        }
    }
}

I have created a small helper class to redirect the output to a StringWriter:
using System;
using System.IO;

namespace ConsoleLogger.Tests
{
    public class ConsoleOutput : IDisposable
    {
        private StringWriter stringWriter;
        private TextWriter originalOutput;

        public ConsoleOutput()
        {
            stringWriter = new StringWriter();
            originalOutput = Console.Out;
            Console.SetOut(stringWriter);
        }

        public string GetOuput()
        {
            return stringWriter.ToString();
        }

        public void Dispose()
        {
            Console.SetOut(originalOutput);
            stringWriter.Dispose();
        }
    }
}
Now let's write the unit test:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace ConsoleLogger.Tests
{
    [TestClass]
    public class DummyClassTest
    {
        [TestMethod]
        public void WriteToConsoleTest()
        {
            var currentConsoleOut = Console.Out;

            DummyClass target = new DummyClass(); 
            
            string text = "Hello";

            using (var consoleOutput = new ConsoleOutput())
            {
                target.WriteToConsole(text);

                Assert.AreEqual(text, consoleOutput.GetOuput());
            }

            Assert.AreEqual(currentConsoleOut, Console.Out);
        }
    }
}
This way we are sure that the original output will be restored and it's easy to get the output from the console.

You can find the sample here ConsoleLogger.zip.

Thursday, November 8, 2012

How to configure local Nuget Repository

After my last posts about Nuget packaging I wanted to share another useful experience with Nuget.
You can create a local repository to store all the packages you need and not to download those every time.
  1. To do this I have created a folder C:\NugetConfig\Repo and I have copied there the Newtonsoft.Json.4.5.10.nupkg package file
  2. To make the both solutions use this local repository all I have to do is to change the following settings in the NuGet.targets file:
    <ItemGroup Condition=" '$(PackageSources)' == '' ">
        <!-- Package sources used to restore packages. By default will used the registered sources under %APPDATA%\NuGet\NuGet.Config -->
        <!--
            <PackageSource Include="https://nuget.org/api/v2/" />
            <PackageSource Include="https://my-nuget-source/nuget/" />
        -->
    </ItemGroup>
    
    and adding s new PackageSource location
    <ItemGroup Condition=" '$(PackageSources)' == '' ">
        <!-- Package sources used to restore packages. By default will used the registered sources under %APPDATA%\NuGet\NuGet.Config -->
        <!--
            <PackageSource Include="https://nuget.org/api/v2/" />
            <PackageSource Include="https://my-nuget-source/nuget/" />
        -->
        <PackageSource Include="C:\NugetConfig\Repo" />
    </ItemGroup>
    
    And that's it. This will make the solution search for the used packages in the given folder and you will get а meaningful error if the package could not be found.
  3. Furthermore you can add you local repository to the visual studio package sources
    so that you will be able to search and add packages from it to any new solution:
As usual you can find the code here NugetConfig-Local-Repo.zip.

Share Nuget packages between solutions part 2

Let's see how we can fix the problem explained here.
According to the latest release notes by now you should be able to change the packages folder by adding this setting in the Nuget.config file:
<configuration>
  <config>
    <add key="repositoryPath" value="C:\myteam\teampackages"></add>
  </config>
  ... 
</configuration>
However unfortunately I couldn't make this work :( So I found a workaround. I want to make the Second.sln use the package folder of First.sln. So I have made the following changes to the NuGet.targets file.
  1. I this section:
    <PropertyGroup Condition=" '$(OS)' == 'Windows_NT'">
        <!-- Windows specific commands -->
        <NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath>
        <PackagesConfig>$([System.IO.Path]::Combine($(ProjectDir), "packages.config"))</PackagesConfig>
    </PropertyGroup>
    
    I have added the following row:
    <PackagesDir>$(SolutionDir)..\First\packages</PackagesDir> 
  2. Change the arguments of the NewGetCommand from:
    <RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(RequireConsentSwitch) -solutionDir "$(SolutionDir)</RestoreCommand>
    
    to:
    <RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" -o "$(PackagesDir)" $(RequireConsentSwitch) -solutionDir "$(SolutionDir)</RestoreCommand>
    
    I have added -o "$(PackagesDir)" which should make the packages to be downloaded to the PackagesDir folder
  3. To make the configuration more flexible we can change the PackagesDir definition to
    <PackagesDir Condition="'$(PackagesDir)' == ''">$(SolutionDir)..\First\packages</PackagesDir> 
    this will allow us to predefine the PackagesDir value in the .csproj.user files.
  4. To test this we can add the C:\ NugetConfig\First\Dummy\Dummy.csproj.user with the following content:
    <?xml version="1.0" encoding="utf-8"?>
    <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
        <PropertyGroup>
            <PackagesDir>C:\NugetConfig\packages</PackagesDir>
        </PropertyGroup>
    </Project>
    
    This will make the Nuget to get files in the C:\NugetConfig\packages folder.
And here is the code NugetConfig-part-2.zip

Share Nuget packages between solutions part 1

Many of you may use Nuget to add reference assemblies to your projects. Anyway sometimes we need to share a project between solutions. In this case if the relative path to this project from the different solutions is different you won't be able to use the default nuget packaging functionality.
Let me explain this with samples.
  1. Let's create an empty Console application named First in folder C:\NugetConfig\First
  2. Add a dummy class library project to this solution in the same folder.
  3. Add reference in the console application to the class library. You should have the following structure now:
  4. Now add Newtonsoft.Json package to the solution:
  5. Install the package to the Dummy project:
  6. Add a dummy class to the dummy project which references Newton Json:
    namespace Dummy
    {
        public class Dummy
        {
            public Newtonsoft.Json.Required Required { get; set; }
    
            public Dummy()
            {
                this.Required = Newtonsoft.Json.Required.Always;
            }
    
            public override string ToString()
            {
                return this.Required.ToString();
            }
        }
    } 
  7. Add a call to the dummy project in Program.cs
    using System;
    
    namespace First
    {
        class Program
        {
            public static void Main(string[] args)
            {
                var dummy = new Dummy.Dummy();
                Console.WriteLine(dummy);
            }
        }
    }
    
  8. Now enable Nuget Package Restore on this solution:

    Now if we delete the Newtonsoft.Json folder from the packages folder and run the build the package is downloaded and the solution builds successfully
  9. Let's create a new project in another solution named Second in folder in folder C:\NugetConfig\Second
  10. Add reference to the Dummy project 
  11. Add a call to the dummy project in Program.cs
    using System;
    
    namespace Second
    {
        class Program
        {
            public static void Main(string[] args)
            {
                var dummy = new Dummy.Dummy();
                Console.WriteLine(dummy);
            }
        }
    }
    
  12. We can build the second solution now as we have already downloaded the needed nuget package. But let's delete the package from C:\NugetConfig\First\packages folder. 
  13. Now we won't be able to build the solution. Let's enable Nuget Package Restore on this solution as well.
  14. Trying to build the solution leads to this error:
So here is the actual problem:
In the Dummy project there is a reference to the NewtonJson
 <Reference Include="Newtonsoft.Json">
      <HintPath>..\packages\Newtonsoft.Json.4.5.10\lib\net40\Newtonsoft.Json.dll</HintPath>
 </Reference>
But our folder looks like this:

So we search for the Newtonsoft.Json.dll in folder C:\NugetConfig\First\packages folder as the HintPath is relative from the Dummy.csproj file.
But the build downloads the package to C:\NugetConfig\Second\packages

You can find the code here NugetConfig-part-1.zip

To see how we can resolve this issue go to the part 2.

Monday, November 5, 2012

Compare two dll files programmatically using Hash

In the past days I needed to compare two .NET assemblies to see if they are functionally the same. As you may know, when you build the same project several times the resulting output .dll or .exe file is always different. That's because of the ModuleVerisonId property(MVID). So in order to check if two assemblies are the same we can disassemble them to IL code and compare them.
The easiest way to disassemble the file is to use MSIL Disassembler. However it is not in a common location. It can be in any of the following locations:

C:\Program Files\Microsoft SDKs\Windows\v6.0\bin\ildasm.exe
C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\ildasm.exe
C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\ildasm.exe
C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\x64\ildasm.exe
C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\ildasm.exe
C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\x64\ildasm.exe
C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\ildasm.exe
C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\x64\ildasm.exe
C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\ildasm.exe
C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\x64\ildasm.exe


So in my following solution I have embedded the ildasm.exe file in a class library to be able to use it on any machine.When we disassemble an assembly we check if the ildasm.exe file exists in the executing assembly folder and if not the file is extracted there from our dll file.
Using the ildasm file we get the IL code and save it to a temporary file.
Then we need to remove the following three rows:
  1. MVID - as I wrote before this is a unique GUID generated with every build
  2. Image Base (The image base tells us as to where the program will be loaded in memory by the Windows loader.) - this is different with every build as well
  3. Time-date stamp - the time and date when the ildasm is run
So we read the temp file content, remove these rows we use regex and then save the file content to the same file.
You can find the Disassembler file here:
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace FileHasher
{
    public class Disassembler
    {
        public static Regex regexMVID = new Regex("//\\s*MVID\\:\\s*\\{[a-zA-Z0-9\\-]+\\}", RegexOptions.Multiline | RegexOptions.Compiled);
        public static Regex regexImageBase = new Regex("//\\s*Image\\s+base\\:\\s0x[0-9A-Fa-f]*", RegexOptions.Multiline | RegexOptions.Compiled);
        public static Regex regexTimeStamp = new Regex("//\\s*Time-date\\s+stamp\\:\\s*0x[0-9A-Fa-f]*", RegexOptions.Multiline | RegexOptions.Compiled);
        
        private static readonly Lazy<Assembly> currentAssembly = new Lazy<Assembly>(() =>
        {
            return MethodBase.GetCurrentMethod().DeclaringType.Assembly;
        });

        private static readonly Lazy<string> executingAssemblyPath = new Lazy<string>(() =>
        {
            return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        });

        private static readonly Lazy<string> currentAssemblyFolder = new Lazy<string>(() =>
        {
            return Path.GetDirectoryName(currentAssembly.Value.Location);
        });

        private static readonly Lazy<string[]> arrResources = new Lazy<string[]>(() =>
        {
            return currentAssembly.Value.GetManifestResourceNames();
        });

        private const string ildasmArguments = "/all /text \"{0}\"";

        public static string ILDasmFileLocation
        {
            get
            {
                return Path.Combine(executingAssemblyPath.Value, "ildasm.exe");
            }
        }

        static Disassembler()
        {
            //extract the ildasm file to the executing assembly location
            ExtractFileToLocation("ildasm.exe", ILDasmFileLocation);
        }

        /// <summary>
        /// Saves the file from embedded resource to a given location.
        /// </summary>
        /// <param name="embeddedResourceName">Name of the embedded resource.</param>
        /// <param name="fileName">Name of the file.</param>
        protected static void SaveFileFromEmbeddedResource(string embeddedResourceName, string fileName)
        {
            if (File.Exists(fileName))
            {
                //the file already exists, we can add deletion here if we want to change the version of the 7zip
                return;
            }
            FileInfo fileInfoOutputFile = new FileInfo(fileName);

            using (FileStream streamToOutputFile = fileInfoOutputFile.OpenWrite())
            using (Stream streamToResourceFile = currentAssembly.Value.GetManifestResourceStream(embeddedResourceName))
            {
                const int size = 4096;
                byte[] bytes = new byte[4096];
                int numBytes;
                while ((numBytes = streamToResourceFile.Read(bytes, 0, size)) > 0)
                {
                    streamToOutputFile.Write(bytes, 0, numBytes);
                }

                streamToOutputFile.Close();
                streamToResourceFile.Close();
            }
        }

        /// <summary>
        /// Searches the embedded resource and extracts it to the given location.
        /// </summary>
        /// <param name="fileNameInDll">The file name in DLL.</param>
        /// <param name="outFileName">Name of the out file.</param>
        protected static void ExtractFileToLocation(string fileNameInDll, string outFileName)
        {
            string resourcePath = arrResources.Value.Where(resource => resource.EndsWith(fileNameInDll, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
            if (resourcePath == null)
            {
                throw new Exception(string.Format("Cannot find {0} in the embedded resources of {1}", fileNameInDll, currentAssembly.Value.FullName));
            }
            SaveFileFromEmbeddedResource(resourcePath, outFileName);
        }

        public static string GetDisassembledFile(string assemblyFilePath)
        {
            if (!File.Exists(assemblyFilePath))
            {
                throw new InvalidOperationException(string.Format("The file {0} does not exist!", assemblyFilePath));
            }

            string tempFileName = Path.GetTempFileName();
            var startInfo = new ProcessStartInfo(ILDasmFileLocation, string.Format(ildasmArguments, assemblyFilePath));
            startInfo.WindowStyle = ProcessWindowStyle.Hidden;
            startInfo.CreateNoWindow = true;
            startInfo.UseShellExecute = false;
            startInfo.RedirectStandardOutput = true;

            using (var process = System.Diagnostics.Process.Start(startInfo))
            {
                string output = process.StandardOutput.ReadToEnd();
                process.WaitForExit();

                if (process.ExitCode > 0)
                {
                    throw new InvalidOperationException(
                        string.Format("Generating IL code for file {0} failed with exit code - {1}. Log: {2}",
                        assemblyFilePath, process.ExitCode, output));
                }

                File.WriteAllText(tempFileName, output);
            }

            RemoveUnnededRows(tempFileName);
            return tempFileName;
        }

        private static void RemoveUnnededRows(string fileName)
        {
            string fileContent = File.ReadAllText(fileName);
            //remove MVID
            fileContent = regexMVID.Replace(fileContent, string.Empty);
            //remove Image Base
            fileContent = regexImageBase.Replace(fileContent, string.Empty);
            //remove Time Stamp
            fileContent = regexTimeStamp.Replace(fileContent, string.Empty);
            File.WriteAllText(fileName, fileContent);
        }

        public static string DisassembleFile(string assemblyFilePath)
        {
            string disassembledFile = GetDisassembledFile(assemblyFilePath);
            try
            {
                return File.ReadAllText(disassembledFile);
            }
            finally
            {
                if (File.Exists(disassembledFile))
                {
                    File.Delete(disassembledFile);
                }
            }
        }
    }
}

So using this class we can get the IL code of a .NET assemlby. Now we can create a Hash Calculator to calculate the hash of the file, so that we can store the hash and compare it with newer files. To calculate the hash I use the following approach:
  1. Check if the file ends with .dll or .exe - if yes this can be a .NET assembly, if no get file hash
  2. Try to open the assembly with Assembly.LoadFile to see if this is a valid .NET assembly, if there is a BadImageFormatException, this is not a .NET one so get file hash
  3. If this is a .net assembly get the IL code file and calculate it's hash
To calculate file hash we use MD5CryptoServiceProvider. So here is the Hash Calculator:
using System;
using System.IO;
using System.Reflection;

namespace FileHasher
{
    public class HashCalculator
    {
        public string FileName { get; private set; }

        public HashCalculator(string fileName)
        {
            this.FileName = fileName;
        }

        public string CalculateFileHash()
        {
            if (Path.GetExtension(this.FileName).Equals(".dll", System.StringComparison.InvariantCultureIgnoreCase)
                || Path.GetExtension(this.FileName).Equals(".exe", System.StringComparison.InvariantCultureIgnoreCase))
            {
                return GetAssemblyFileHash();
            }
            else
            {
                return GetFileHash();
            }
        }

        private string GetFileHash()
        {
            return CalculateHashFromStream(File.OpenRead(this.FileName));
        }

        private string GetAssemblyFileHash()
        {
            string tempFileName = null;
            try
            {
                //try to open the assembly to check if this is a .NET one
                var assembly = Assembly.LoadFile(this.FileName);
                tempFileName = Disassembler.GetDisassembledFile(this.FileName);
                return CalculateHashFromStream(File.OpenRead(tempFileName));
            }
            catch(BadImageFormatException)
            {
                return GetFileHash();
            }
            finally
            {
                if (File.Exists(tempFileName))
                {
                    File.Delete(tempFileName);
                }
            }
        }

        private string CalculateHashFromStream(Stream stream)
        {
            using (var readerSource = new System.IO.BufferedStream(stream, 1200000))
            {
                using (var md51 = new System.Security.Cryptography.MD5CryptoServiceProvider())
                {
                    md51.ComputeHash(readerSource);
                    return Convert.ToBase64String(md51.Hash);
                }
            }
        }
    }
}

You can find the code here AssemblyHasher.zip or on github AssemblyHasher