Click here to Skip to main content
Click here to Skip to main content

Loading Assemblies in Separate Directories Into a New AppDomain

By , 12 Sep 2009
Rate this:
Please Sign up or sign in to vote.

As some of you may know, I have been working on a code generator for my Cinch MVVM framework, which I am pleased to say I am nearly done with. The last stumbling block has been that I need to extract a bunch of Namespaces from Assemblies that the main code referenced, which I want to do by the use of Reflection which is very easy. But I also wanted the Assemblies that I would need to examine loaded into a new AppDomain so that I could Unload the newly created AppDomain when I am finished Reflecting out the Namespaces from the Assemblies.

After many failed attempts, and messing around with:

  • Asembly.ReflectionOnlyLoad
  • AppDomain.Load(Byte[] rawAssembly)
  • Fusion paths
  • Probing in my App.Config
  • AppDomain Evidence and AppDomainSetup

I finally found nirvana and hit the sweet spot, which is as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Globalization;
using System.Security.Policy;
using System.Reflection;
using System.Diagnostics.CodeAnalysis;

namespace ConsoleApplication1
{

    /// <span class="code-SummaryComment"><summary></span>
    /// Loads an assembly into a new AppDomain and obtains all the
    /// namespaces in the loaded Assembly, which are returned as a 
    /// List. The new AppDomain is then Unloaded.
    /// 
    /// This class creates a new instance of a 
    /// <span class="code-SummaryComment"><c>AssemblyLoader</c> class</span>
    /// which does the actual ReflectionOnly loading 
    /// of the Assembly into
    /// the new AppDomain.
    /// <span class="code-SummaryComment"></summary></span>
    public class SeperateAppDomainAssemblyLoader
    {
        #region Public Methods
        /// <span class="code-SummaryComment"><summary></span>
        /// Loads an assembly into a new AppDomain and obtains all the
        /// namespaces in the loaded Assembly, which are returned as a 
        /// List. The new AppDomain is then Unloaded
        /// <span class="code-SummaryComment"></summary></span>
        /// <span class="code-SummaryComment"><param name="assemblyLocation">The Assembly file </span>
        /// location<span class="code-SummaryComment"></param></span>
        /// <span class="code-SummaryComment"><returns>A list of found namespaces</returns></span>
        public List<String> LoadAssembly(FileInfo assemblyLocation)
        {
            List<String> namespaces = new List<String>();

            if (string.IsNullOrEmpty(assemblyLocation.Directory.FullName))
            {
                throw new InvalidOperationException(
                    "Directory can't be null or empty.");
            }

            if (!Directory.Exists(assemblyLocation.Directory.FullName))
            {
                throw new InvalidOperationException(
                   string.Format(CultureInfo.CurrentCulture,
                   "Directory not found {0}", 
                   assemblyLocation.Directory.FullName));

            }

            AppDomain childDomain = BuildChildDomain(
                AppDomain.CurrentDomain);

            try
            {
                Type loaderType = typeof(AssemblyLoader);
                if (loaderType.Assembly != null)
                {
                    var loader = 
                        (AssemblyLoader)childDomain.
                            CreateInstanceFrom(
                            loaderType.Assembly.Location, 
                            loaderType.FullName).Unwrap();

                    loader.LoadAssembly(
                        assemblyLocation.FullName);
                    namespaces = 
                        loader.GetNamespaces(
                        assemblyLocation.Directory.FullName);
                }
                return namespaces;
            }

            finally
            {

                AppDomain.Unload(childDomain);
            }
        }
        #endregion

        #region Private Methods
        /// <span class="code-SummaryComment"><summary></span>
        /// Creates a new AppDomain based on the parent AppDomains 
        /// Evidence and AppDomainSetup
        /// <span class="code-SummaryComment"></summary></span>
        /// <span class="code-SummaryComment"><param name="parentDomain">The parent AppDomain</param></span>
        /// <span class="code-SummaryComment"><returns>A newly created AppDomain</returns></span>
        private AppDomain BuildChildDomain(AppDomain parentDomain)
        {
            Evidence evidence = new Evidence(parentDomain.Evidence);
            AppDomainSetup setup = parentDomain.SetupInformation;
            return AppDomain.CreateDomain("DiscoveryRegion", 
                evidence, setup);
        }
        #endregion


        /// <span class="code-SummaryComment"><summary></span>
        /// Remotable AssemblyLoader, this class 
        /// inherits from <span class="code-SummaryComment"><c>MarshalByRefObject</c> </span>
        /// to allow the CLR to marshall
        /// this object by reference across 
        /// AppDomain boundaries
        /// <span class="code-SummaryComment"></summary></span>
        class AssemblyLoader : MarshalByRefObject
        {
            #region Private/Internal Methods
            /// <span class="code-SummaryComment"><summary></span>
            /// Gets namespaces for ReflectionOnly Loaded Assemblies
            /// <span class="code-SummaryComment"></summary></span>
            /// <span class="code-SummaryComment"><param name="path">The path to the Assembly</param></span>
            /// <span class="code-SummaryComment"><returns>A List of namespace strings</returns></span>
            [SuppressMessage("Microsoft.Performance", 
                "CA1822:MarkMembersAsStatic")]
            internal List<String> GetNamespaces(string path)
            {

                List<String> namespaces = new List<String>();

                DirectoryInfo directory = new DirectoryInfo(path);
                ResolveEventHandler resolveEventHandler = 
                    (s,e)=> { 
                                return OnReflectionOnlyResolve(
                                    e, directory); 
                            };

                AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve 
                    += resolveEventHandler;

                Assembly reflectionOnlyAssembly = 
                    AppDomain.CurrentDomain.
                        ReflectionOnlyGetAssemblies().First();
                
                foreach (Type type in reflectionOnlyAssembly.GetTypes())
                {
                    if (!namespaces.Contains(type.Namespace))
                        namespaces.Add(type.Namespace);
                }

                AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve 
                    -= resolveEventHandler;
                return namespaces;

            }

            /// <span class="code-SummaryComment"><summary></span>
            /// Attempts ReflectionOnlyLoad of current 
            /// Assemblies dependants
            /// <span class="code-SummaryComment"></summary></span>
            /// <span class="code-SummaryComment"><param name="args">ReflectionOnlyAssemblyResolve </span>
            /// event args<span class="code-SummaryComment"></param></span>
            /// <span class="code-SummaryComment"><param name="directory">The current Assemblies </span>
            /// Directory<span class="code-SummaryComment"></param></span>
            /// <span class="code-SummaryComment"><returns>ReflectionOnlyLoadFrom loaded</span>
            /// dependant Assembly<span class="code-SummaryComment"></returns></span>
            private Assembly OnReflectionOnlyResolve(
                ResolveEventArgs args, DirectoryInfo directory)
            {

                Assembly loadedAssembly = 
                    AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies()
                        .FirstOrDefault(
                          asm => string.Equals(asm.FullName, args.Name, 
                              StringComparison.OrdinalIgnoreCase));

                if (loadedAssembly != null)
                {
                    return loadedAssembly;
                }

                AssemblyName assemblyName = 
                    new AssemblyName(args.Name);
                string dependentAssemblyFilename = 
                    Path.Combine(directory.FullName, 
                    assemblyName.Name + ".dll");

                if (File.Exists(dependentAssemblyFilename))
                {
                    return Assembly.ReflectionOnlyLoadFrom(
                        dependentAssemblyFilename);
                }
                return Assembly.ReflectionOnlyLoad(args.Name);
            }


            /// <span class="code-SummaryComment"><summary></span>
            /// ReflectionOnlyLoad of single Assembly based on 
            /// the assemblyPath parameter
            /// <span class="code-SummaryComment"></summary></span>
            /// <span class="code-SummaryComment"><param name="assemblyPath">The path to the Assembly</param></span>
            [SuppressMessage("Microsoft.Performance", 
                "CA1822:MarkMembersAsStatic")]
            internal void LoadAssembly(String assemblyPath)
            {
                try
                {
                    Assembly.ReflectionOnlyLoadFrom(assemblyPath);
                }
                catch (FileNotFoundException)
                {
                    /* Continue loading assemblies even if an assembly
                     * can not be loaded in the new AppDomain. */
                }
            }
            #endregion
        }
    }
}

I think this code is pretty useful and I hope you find as much use for it as I have. It took me long enough to figure this out, and many Google searches were done and much consulting of APIs/picking friends knowledge was done to bring you this code, so enjoy it. It nearly killed me getting that one to work.

As usual, here is a small demo app: http://sachabarber.net/wp-content/uploads/2009/09/AppDomainMonkery.zip.

License

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

About the Author

Sacha Barber
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)
 
- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence
 
Both of these at Sussex University UK.
 
Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
GeneralNeeded an example showing how to do this PinmvpEspen Harlinn16-Aug-12 10:36 
GeneralRe: Needed an example showing how to do this PinmvpSacha Barber16-Aug-12 10:38 
GeneralMy vote of 5 PinmemberRichard Osafo20-Dec-11 23:38 
QuestionInteresting, but how do I do that in a Text Transformation? PinmemberAndreas Saurwein Franci Gonçalves6-Jan-11 5:54 
AnswerRe: Interesting, but how do I do that in a Text Transformation? PinmvpSacha Barber6-Jan-11 6:30 
GeneralRe: Interesting, but how do I do that in a Text Transformation? PinmemberAndreas Saurwein Franci Gonçalves6-Jan-11 15:35 
GeneralNames PinmemberYllusio11-May-10 1:28 
GeneralLink Broken Pinmemberknoami1-Nov-09 6:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140421.2 | Last Updated 13 Sep 2009
Article Copyright 2009 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid