Click here to Skip to main content
11,571,102 members (64,308 online)
Click here to Skip to main content

Loading Assemblies in Separate Directories Into a New AppDomain

, 12 Sep 2009 CPOL 32.9K 37
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 [...]

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)

Share

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 2015
  • Codeproject MVP 2015
  • 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

You may also be interested in...

Comments and Discussions

 
GeneralNeeded an example showing how to do this Pin
Espen Harlinn16-Aug-12 10:36
mvpEspen Harlinn16-Aug-12 10:36 
GeneralRe: Needed an example showing how to do this Pin
Sacha Barber16-Aug-12 10:38
mvpSacha Barber16-Aug-12 10:38 
GeneralMy vote of 5 Pin
Richard Osafo20-Dec-11 23:38
memberRichard Osafo20-Dec-11 23:38 
QuestionInteresting, but how do I do that in a Text Transformation? Pin
Andreas Saurwein Franci Gonçalves6-Jan-11 5:54
memberAndreas Saurwein Franci Gonçalves6-Jan-11 5:54 
AnswerRe: Interesting, but how do I do that in a Text Transformation? Pin
Sacha Barber6-Jan-11 6:30
mvpSacha Barber6-Jan-11 6:30 
GeneralRe: Interesting, but how do I do that in a Text Transformation? Pin
Andreas Saurwein Franci Gonçalves6-Jan-11 15:35
memberAndreas Saurwein Franci Gonçalves6-Jan-11 15:35 
GeneralNames Pin
Yllusio11-May-10 1:28
memberYllusio11-May-10 1:28 
GeneralLink Broken Pin
knoami1-Nov-09 6:42
memberknoami1-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 | Terms of Use | Mobile
Web03 | 2.8.150624.2 | Last Updated 13 Sep 2009
Article Copyright 2009 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid