Click here to Skip to main content
14,267,154 members

Loading Assemblies in Separate Directories Into a New AppDomain

Rate this:
4.89 (8 votes)
Please Sign up or sign in to vote.
4.89 (8 votes)
12 Sep 2009CPOL
How to load assemblies in separate directories into a new AppDomain

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
{
    /// <summary>
    /// 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 
    /// <c>AssemblyLoader</c> class
    /// which does the actual ReflectionOnly loading 
    /// of the Assembly into
    /// the new AppDomain.
    /// </summary>
    public class SeperateAppDomainAssemblyLoader
    {
        #region Public Methods
        /// <summary>
        /// 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
        /// </summary>
        /// <param name="assemblyLocation">The Assembly file 
        /// location</param>
        /// <returns>A list of found namespaces</returns>
        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
        /// <summary>
        /// Creates a new AppDomain based on the parent AppDomains 
        /// Evidence and AppDomainSetup
        /// </summary>
        /// <param name="parentDomain">The parent AppDomain</param>
        /// <returns>A newly created AppDomain</returns>
        private AppDomain BuildChildDomain(AppDomain parentDomain)
        {
            Evidence evidence = new Evidence(parentDomain.Evidence);
            AppDomainSetup setup = parentDomain.SetupInformation;
            return AppDomain.CreateDomain("DiscoveryRegion", 
                evidence, setup);
        }
        #endregion

        /// <summary>
        /// Remotable AssemblyLoader, this class 
        /// inherits from <c>MarshalByRefObject</c> 
        /// to allow the CLR to marshall
        /// this object by reference across 
        /// AppDomain boundaries
        /// </summary>
        class AssemblyLoader : MarshalByRefObject
        {
            #region Private/Internal Methods
            /// <summary>
            /// Gets namespaces for ReflectionOnly Loaded Assemblies
            /// </summary>
            /// <param name="path">The path to the Assembly</param>
            /// <returns>A List of namespace strings</returns>
            [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;
            }

            /// <summary>
            /// Attempts ReflectionOnlyLoad of current 
            /// Assemblies dependants
            /// </summary>
            /// <param name="args">ReflectionOnlyAssemblyResolve 
            /// event args</param>
            /// <param name="directory">The current Assemblies 
            /// Directory</param>
            /// <returns>ReflectionOnlyLoadFrom loaded
            /// dependant Assembly</returns>
            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);
            }

            /// <summary>
            /// ReflectionOnlyLoad of single Assembly based on 
            /// the assemblyPath parameter
            /// </summary>
            /// <param name="assemblyPath">The path to the Assembly</param>
            [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:

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

Comments and Discussions

 
QuestionWhy reflection only context? Pin
Theo Bebekis28-Sep-15 1:41
memberTheo Bebekis28-Sep-15 1:41 
GeneralNeeded an example showing how to do this Pin
Espen Harlinn16-Aug-12 10:36
mentorEspen Harlinn16-Aug-12 10:36 
GeneralRe: Needed an example showing how to do this Pin
Sacha Barber16-Aug-12 10:38
mvaSacha 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
mvaSacha 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
Yllusyo11-May-10 1:28
memberYllusyo11-May-10 1:28 
This is a good post containing most of the hints anyone would need to load assemblies in a separate appdomain. But it took me a few passes through the code to understand it. Can you please use more correct names for your functions?
LoadAssembly is a nice function name but if you return a list of namespaces by it, it's just plain evil. And so...

Kind regards
Yllusio.

I'm not responsible for what I didn't say and hardly for what I did say.

GeneralLink Broken Pin
knoami1-Nov-09 6:42
memberknoami1-Nov-09 6:42 
GeneralRe: Link Broken Pin
Member 1019550510-Oct-17 6:35
professionalMember 1019550510-Oct-17 6:35 

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

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

Article
Posted 12 Sep 2009

Tagged as

Stats

55.7K views
43 bookmarked