Click here to Skip to main content
15,896,372 members
Articles / Programming Languages / C#

Self Contained Assembly

Rate me:
Please Sign up or sign in to vote.
4.95/5 (13 votes)
13 Nov 2011CPOL9 min read 49.5K   1.5K   43  
Shows how deployment of dynamically loaded assembly can be simplified by reducing the assembly to single self contained file.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Text;

namespace AssemblyLoader
{
    public class Loader
    {
        static bool resolveAssembly;

        static Dictionary<string, Assembly> loadedDependancies = new Dictionary<string, Assembly>();

        static Dictionary<string, ZipAssemblyInfo> assembliesInZipResource = new Dictionary<string, ZipAssemblyInfo>();

        static ResolveEventHandler handler = FindRequiredAssembly;

        public static bool ResolveAssembly
        {
            get { return resolveAssembly; }
            set
            {
                if (resolveAssembly == value) return;

                resolveAssembly = value;

                if (value == true)
                {
                    AppDomain.CurrentDomain.AssemblyResolve += handler;
                }
                else
                {
                    AppDomain.CurrentDomain.AssemblyResolve -= handler;
                }
            }
        }

        /// <summary>
        /// AssemblyResolve Handler.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        private static Assembly FindRequiredAssembly(object sender, ResolveEventArgs args)
        {
            var requiredAssembly = new AssemblyName(args.Name);

            var shortName = requiredAssembly.Name;

            if (loadedDependancies.ContainsKey(shortName))
                return loadedDependancies[shortName];

            var assemblyFileName = shortName + ".dll";

            if (assembliesInZipResource.ContainsKey(shortName) == false)
            {
                throw new FileNotFoundException(String.Format("Unable to find {0} in zip resource of assemblies", assemblyFileName));
            }

            var info = assembliesInZipResource[shortName];

            using (var resourceStream = info.ContainingAssembly.GetManifestResourceStream(info.ManifestResourceName))
            {
                using (var zipStore = ZipStorer.Open(resourceStream, FileAccess.Read))
                {
                    using (var extractedStream = new MemoryStream())
                    {
                        zipStore.ExtractFile(zipStore.ReadCentralDir()[0], extractedStream);

                        var assemblyBytes = extractedStream.GetBuffer();

                        //Load assembly in ReflectionOnly context to verify the name before loading for execution.
                        var foundAssembly = Assembly.ReflectionOnlyLoad(assemblyBytes);

                        //Verify that we have found correct assembly before loading it.
                        //This method do not care about match of version and PublicTokenKey
                        // Optionally string comparison can be used [foundAssembly.FullName == args.Name ]
                        if (!AssemblyName.ReferenceMatchesDefinition(foundAssembly.GetName(), requiredAssembly))
                        {
                            //if more than one resolve handler are present then we can return from here
                            // otherwise we can throw exception to indicate failure .

                            // return null;
                            throw new FileNotFoundException("File Name  = " + requiredAssembly);
                        }

                        //Load assembly for execution.
                        foundAssembly = Assembly.Load(assemblyBytes);

                        loadedDependancies.Add(shortName, foundAssembly);

                        return foundAssembly;
                    }
                }
            }
        }

        public static Assembly LoadAssembly(string filePath)
        {
            if (!File.Exists(filePath)) throw new FileNotFoundException("File Name = " + filePath);

            var assembly = Assembly.LoadFrom(filePath);

            var fileNameInZip = string.Empty;
            var fileNameWithExtension = string.Empty;
            var fileNameWithoutExtension = string.Empty;

            Debug.WriteLine(String.Format("Inspecting zip files in assembly {0}.", assembly.FullName));

            foreach (var resourceName in assembly.GetManifestResourceNames())
            {
                if (resourceName.EndsWith(".zip"))
                {
                    using (var stream = assembly.GetManifestResourceStream(resourceName))
                    {
                        using (var zipStore = ZipStorer.Open(stream, FileAccess.Read))
                        {
                            foreach (var zipFileEntry in zipStore.ReadCentralDir())
                            {
                                //Full path of file in zip (i.e. including directories)
                                fileNameInZip = zipFileEntry.FilenameInZip;

                                if (fileNameInZip.EndsWith(".dll"))
                                {
                                    fileNameWithExtension = Path.GetFileName(fileNameInZip);

                                    // This is the name of assembly if above dll file.
                                    fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileNameWithExtension);

                                    if (assembliesInZipResource.ContainsKey(fileNameWithoutExtension) == false)
                                    {
                                        var info = new ZipAssemblyInfo(fileNameWithExtension, assembly, resourceName, zipFileEntry);

                                        assembliesInZipResource.Add(fileNameWithoutExtension, info);
                                    }
                                    else
                                    {
                                        var info = assembliesInZipResource[fileNameWithoutExtension];
                                        Debug.WriteLine(String.Format("Assembly {0} is already found in zip. Info = {1}", fileNameWithoutExtension, info));
                                        if (info.ZipFileEntry.Crc32 != zipFileEntry.Crc32)
                                        {
                                            var error = "Two files with same name but different Crc values are detected.";
                                            var detailMsg = String.Format("Assembly {0} and {1} both contains assembly {3} in their                                                                  embedded resource zip file and  the crc32 value for them are not same.",
                                                info.ContainingAssembly.GetName().Name, assembly.GetName().Name,
                                                fileNameWithoutExtension);

                                            Debug.Fail(error, detailMsg);

                                            throw new InvalidOperationException(error);
                                        }
                                    }

                                    //Here we are assuming that assembly name and file name will be same
                                    //i.e. Assembly with Name A will be in A.dll
                                    // So when we get A.dll in zip resource we are assuming that it contains assembly A.
                                    //In Resolve Handler  when we try to resolve Assembly A we will use the assembly name as key.

                                    // if there is any possibility that the assembly name and dll file name can be different,
                                    //then one can extract assembly and load it in reflection only context and then retrieve
                                    // assembly name. However this seems very inefficient and unnecessary as no one usually changes the
                                    //name of compiled assembly dlls.
                                }
                            }
                        }
                    }
                }
            }

            return assembly;
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Honeywell Automation India Ltd.
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions