Contact   •   Articles   •   Products   •   Search

Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs

Assembly Loading across AppDomains

Every time I need to load .NET assemblies from a non-default path in an application I end up spending quite a bit of time trying to get it right. Today was no different: I had a situation where I needed to create a type in a seperate AppDomain based on a dynamically generated assembly that might live in a directory other than the application’s base path. This is sort of like a perfect storm of combinations that are problematic for Assembly loading: Loading across AppDomains and loading an assembly out of a non-default path. Although the behavior I eventually found is fairly straightforward, there are a lot of similar combinations that I ended up with that didn’t work before I found the working solution.

My scenario for this is for a code generator for a FoxPro tool that I’m building for a customer. Basically it’s a Web Service client generator for VFP that creates a .NET Web Service proxy and then generates code for a matching FoxPro proxy to call the .NET proxy. For .NET folks this sounds pretty clumsy, but for Fox developers this is quite useful  because normally creating the .NET proxy and then creating the calls to the proxy from FoxPro take a bit of work and setup, so I decided to build a tool to automate most of this process.

All this worked well right out of the chute, except for one thing that is usually a problem in scenarios where code is dynamically generated – in this case by WSDL.exe. The problem is that the assembly can be generated in any location the user chooses and the user might generate the assembly multiple times and generate the proxy multiple times. This of course would fail on the second attempt as the assembly would already be loaded. So the code somehow needs to be able to unload the assembly from memory when the proxy’s been built.

In order to do that a new AppDomain that can host the assembly is required. By default any assembly loaded into a .NET AppDomain stays loaded and locked in that AppDomain for the lifetime of that AppDomain. Most of the time that AppDomain is the default AppDomain that is created when the application starts and any assembly loaded into it stays loaded until shut down. The assembly DLL on disk is locked while it’s in memory so the file can’t be replaced as would need to happen for any subsequent WSDL.exe generations.

Using a separate AppDomain allows you to get around this limitation. By using cross AppDomain remoting you can instantiate a type in another AppDomain execute any methods on it and then unload the AppDomain and so unlock the assembly and remove any references from Memory. This is quite a common scenario for plug-ins and anything else that loads dynamic code that might change at runtime during the lifetime of the main application.

The other complication is loading the assembly that will be loaded in the alternate AppDomain out of any path the user specifies. Normally when loading in the current AppDomain this process is reasonably straight forward by using Assembly.LoadFrom() to load assemblies out of arbitrary paths. Note that strongly typed assemblies cannot be loaded from non-application paths – this only works for unsigned assemblies.

Long story short I had a number of false starts on this loading up the WSDL.exe generated assembly from an arbitrary path into a new AppDomain. In the end I wound up with a working factory class that looks like this:

public class WsdlClassParserFactory : MarshalByRefObject
    public AppDomain LocalAppDomain = null;
    public string ErrorMessage = string.Empty;

    /// <summary>
    /// Creates a new instance of the WsdlParser in a new AppDomain
    /// </summary>
    /// <returns></returns>        
    public WsdlClassParser CreateWsdlClassParser()

        string AssemblyPath = Assembly.GetExecutingAssembly().Location; 
WsdlClassParser parser = null; try { parser = (WsdlClassParser) this.LocalAppDomain.CreateInstanceFrom(AssemblyPath,
typeof(Westwind.WebServices.WsdlClassParser).FullName).Unwrap() ; } catch (Exception ex) { this.ErrorMessage = ex.Message; } return parser; } public bool CreateAppDomain(string appDomain) { if (string.IsNullOrEmpty(appDomain)) appDomain = "wsdlparser" + Guid.NewGuid().ToString().GetHashCode().ToString("x"); AppDomainSetup domainSetup = new AppDomainSetup(); domainSetup.ApplicationName = appDomain; // *** Point at current directory domainSetup.ApplicationBase = Environment.CurrentDirectory; // AppDomain.CurrentDomain.BaseDirectory; this.LocalAppDomain = AppDomain.CreateDomain(appDomain, null, domainSetup); // *** Need a custom resolver so we can load assembly from non current path AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); return true; } Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { try { Assembly assembly = System.Reflection.Assembly.Load(args.Name); if (assembly != null) return assembly; } catch { // ignore load error } // *** Try to load by filename - split out the filename of the full assembly name // *** and append the base path of the original assembly (ie. look in the same dir) // *** NOTE: this doesn't account for special search paths but then that never // worked before either. string[] Parts = args.Name.Split(','); string File = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\" + Parts[0].Trim() + ".dll"; return System.Reflection.Assembly.LoadFrom(File); } /// <summary> /// /// </summary> public void Unload() { if (this.LocalAppDomain != null) { AppDomain.Unload(this.LocalAppDomain); this.LocalAppDomain = null; } } }

To use the factory create an actual instance to work with then looks like this:

WsdlClassParserFactory factory = new WsdlClassParserFactory();            
WsdlClassParser parser = factory.CreateWsdlClassParser();
... do work with the parser

where the Unload() allows for unloading the AppDomain and releasing the assembly with it. This is quite a bit of code just for instantiating a type in separate AppDomain, but the factory is useful in that it makes it easy to load the type and unload it easily later. The class instance is required in order to hold on to the to AppDomain reference so it can be unloaded.

Loading an AppDomain is straight forward. The biggest headache is properly configuring the ApplicationBase path and related directories. For my particular application I point the base path to the current directory which in case of this application may not be the same directory the main executable started in – IOW, a non-default path. In my case I want to generate and load the assembly in this path and so the base path settings makes the AppDomain look in the base path (plus any PrivateBinPath subdirs of those were set).

The trick to getting the assembly to load from an arbitrary directory is to use AppDomain.CreateInstanceFrom() which expects a file location. When assemblies or AppDomains ask for paths I’m never sure what the heck is being asked for: A fully qualified path, a CodeBase path or just an assembly name (without the .dll) which is searched for in the app’s bin path. Here the fully qualified OS path is required for the assembly as can be retrieved by Assembly.Location. You would think that this would be enough – and it is if you are using Assembly.LoadFrom() in an application. However, when running cross AppDomains, the assembly still failed to load from the external path when I didn’t provide a custom assembly resolver. I’d get the following error:

Unable to cast transparent proxy to type ‘Westwind.WebServices.WsdlParser’

Oddly AppDomain.CreateInstanceFrom() doesn’t fail and returns an object, but when casting the object to the actual type representation the above error pops up. I would have expected a failure to be more spectacular – an exception would have been nice but apparently SOME sort of Transparent Proxy is returned. Apparently not the right one though and I have no idea what this pseudo proxy could be – not a whole lot of debugging info on a Transparent Proxy object.

So, the trick to resolving this issue is to provide an assembly resolver:

AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);

and implement custom logic in the resolver to retrieve the assembly explicitly. The handler first tries a plain Load with the assembly name that .NET passes in. Oddly in my case that just works because the assembly is already loaded since it was the one launching the new AppDomain (the launcher is automatically loaded into the new AppDomain). Which makes me really wonder why the hell the AppDomain can’t find the type without an assembly resolver.

Either way, it works with the simple resolver. In other situations when an assembly has not yet been loaded it might also be useful to have a fallback using LoadFrom() and specify the directory in some shape. Here the folder is based on the current assembly so assemblies are searched for in the same folder as the loader’s assembly.


You’d think the process of assembly loading in general, and loading assemblies across AppDomains should be easier, since loading additional types out of the same assembly is a common scenario – having to resort to an explicit assembly resolver seems like overkill especially given that the resolver isn’t doing anything other than forwarding to Assembly.Load().

I’ve hit this snag one too many times in the past and even so this little exercise cost me a couple hours of poking around and experimenting today until I found the right combination. Hopefully this will be useful to some of you the next time you need to create a cross AppDomain proxy.

Make Donation
Posted in .NET  

Feedback for this Post

# re: Assembly Loading across AppDomains
by Chuck January 27, 2009 @ 2:22pm
Great article. I think this has the missing pieces I needed. I am creating an app for managing different environments of our software which means ultimately that each environment is going to have different assembly versions. I can't use bindingredirect as I could have five different environments loaded at a time. It makes since to create an appdomain per environment. I removed my assembly resolution code and maybe that is why I am getting the casting error. Thanks
# re: Assembly Loading across AppDomains
by Chuck January 27, 2009 @ 2:46pm
You noted that strong typed assemblies need to be loaded from an applicationpath. Is this correct or am I not understanding this correctly? I am loading mine from the database and haven't had any issues outside of having to load a ton of dependencies, which is expected. I am loading different versions of the same assembly and have hit a number of quirky issues related to that, but after reading that I am second guessing myself, maybe the assemblies need to reside on disk. I figured the AppDomain was a good way to fix this. Do you foresee any issues with me loading my assemblies from the db (no probing path) or I am misunderstanding the statement.
# re: Assembly Loading across AppDomains
by Rick Strahl March 20, 2009 @ 5:00pm
@Chuck - file based loading of strongly typed assemblies from disk requires them to be loaded out of the privateBin folder. If you load an assembly from a stream that should still work though (which is silly since that can also come from a file).

Strongly typed assemblies that aren't gac'd are a pain in the butt and I would recommend not using them especially in dynamic load scenarios. I'm of the opinion that any strongly typed assembly should go into the GAC or else use unsigned assemblies locally. Unless you fully control the assembly versioning and loading keeping straight what's what otherwise is painful at least to me.
# re: Assembly Loading across AppDomains
by Allen March 24, 2009 @ 1:59am
Just what the doctor order, have been fighting dynamic assembly loading issues and the CreateAppDomain method is what resolved my problems.
# re: Assembly Loading across AppDomains
by Crackerjack May 21, 2009 @ 6:03pm
You are my hero!
# re: Assembly Loading across AppDomains
by Ffred August 06, 2009 @ 1:23pm
I've been writing a routine to unregister, update, and re-register a COM assembly with dependencies. Your handler idea solved my problem, EXCEPT: invoking Assembly.Load within the handler invoked the handler again, which led to stack overflow. I got rid of that part and only invoked LoadFrom, and that did the trick. Thanks!
# re: Assembly Loading across AppDomains
by Geoff Rayback August 18, 2009 @ 12:03pm
Dead on. Very nice solution to this problem. I am fighting a very similar issue where we are dynamically generating WCF clients for testing and we couldn't re-generate the client without shutting down the tool every time. Lame. This is exactly what I was looking for.
# re: Assembly Loading across AppDomains
by Carl November 13, 2009 @ 2:14am
Hi Rick, how many Visual Studio projects you make for the whole stuff? I guess at least 2, right?
one(call it "Project1") runs as an application in the default appdomain, and call the "code block 1", while the WsdlClassParserFactory and WsdlClassParser live in another project (call it "Project2").

If that's true, then in order to make "Project1" compile, it must add "Project2" as a reference, when the code below("code block 1") runs, the assembly of "Project2" must have been loaded into the default appdomain:
// code block 1
WsdlClassParserFactory factory = new WsdlClassParserFactory();  // runs in Project1, default appdomain     
WsdlClassParser parser = factory.CreateWsdlClassParser();

inside the WsdlClassParserFactory.CreateWsdlClassParser() method
// code block 2
public WsdlClassParser CreateWsdlClassParser()

  string AssemblyPath = Assembly.GetExecutingAssembly().Location;  // get the assembly path of "Project2"
  WsdlClassParser parser = null;
      parser = (WsdlClassParser) this.LocalAppDomain.CreateInstanceFrom(AssemblyPath,                                           
          typeof(Westwind.WebServices.WsdlClassParser).FullName).Unwrap() ;     
  catch (Exception ex)
      this.ErrorMessage = ex.Message;
  return parser;

In "code block 2", it creates a new appdomain, instantiate class "WsdlClassParser" by loading "Project2" assembly into the new appdomain, and finally unload the domain, which will make the "Project2" assembly unloaded from the new appdomain.

But "Project2" assembly was loaded and locked by default appdomain as well, and seems there's no way to unload it.
# re: Assembly Loading across AppDomains
by paul davidson November 27, 2009 @ 7:04am
Hey Rick.

I have been struggling for a while with an issue with loading dll's into an app domain. I came across this solution you show, but I am not sure if it will do what I require, or if its possible?

Basically..I have an updater program (say, UpdateService) that will be called, a sort of tool. When I have finished a new version of code for some software I am building, UpdateService will be run, and the user will choose the directory that the new software version is located at. UpdateService will then need to manually go through the whole folder, getting all dll's, loading them into a temp app domain, to get to the culture of each one, and then unload them when finished.

Basically so that once the UpdateService has run, it will have an object fully populated with all locations of the files, along with certain details like culture if appropriate.

I can't seem to get any of the dll's loaded into this temp app domain. Always advising me file load exception, or could not find file or one of its dependencies. All the dll's will be at a location anywhere on a computer, so they will not be in the app base for the UpdateService program.

Is this possible using say a tweaked version of what you have?
# re: Assembly Loading across AppDomains
by Rick Strahl November 27, 2009 @ 12:14pm
You can control the PrivateBinPath and launch paths in the AppDomainSetup. You should be able to configure this so the launched AppDomain can find assemblies anywhere given you have permissions to access them.
# re: Assembly Loading across AppDomains
by paul davidson November 30, 2009 @ 1:57am
When I created the app domain, I created it passing in the directory name of the full file path for the dll. It's not really something I have ever had to deal with before coming from a web background in .net, so I was unsure what to pass in exactly. I tried:

AppDomain TempLoadingAppDomain = AppDomain.CreateDomain("TempSystemVersionAppDomain",
                                  new System.Security.Policy.Evidence(AppDomain.CurrentDomain.Evidence),

The bit where I have "AppDomain.CurrentDomain.BaseDirectory" I also tried putting the ".GetDirectoryName" for the source dll file.
# re: Assembly Loading across AppDomains
by CHOLAN.R December 21, 2009 @ 6:58am

I feel this does not serve the purpose...

The code uses:

// *** Need a custom resolver so we can load assembly from non current path
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);

So, the new assembly gets loaded in the AppDomain.CurrentDomain rather than the New AppDomain :)

The code should have been,

this.LocalAppDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);

Unfortunately, this throws an error:

Could not load file or assembly '<Assembly Name of the assembly holding AppDomain.CurrentDomain>' or one of its dependencies. The system cannot find the file specified.

Want to cross-check? Try:

foreach (System.Reflection.Assembly item in AppDomain.CurrentDomain.GetAssemblies())

Which of the following should I learn?
1. Think before posting a comment.
2. Another workaround to solve this issue.
# re: Assembly Loading across AppDomains
by Conrad May 19, 2010 @ 10:22am
I have a rather large suite of external medical image DLLs. They are essentially the same name whether 32-bit or 64-bit and installed in the GAC. The supplier changes the version numbers very slightly to differentiate 32/64-bit versions :-$

Currently I use assembly redirects to load the correct version at runtime. However, I have many .NET solutions for different parts of a distributed system and I really don't want to keep changing lots of .config files for each and every project in the solution. I visit endless machines if performing an upgrade at a hospital.

What I would like is to load the relevant DLLs dynamically at run time and store the current version for 32-bit machines and 64-bit machines at a central point. My development machine is 64-bit and so I have added the 64-bit references to the project and I obviously can code against the libraries with intellisense. I would like to retain this ability, but somehow not have any application crash because these were not found when the application started at runtime.

Ideally this would somehow require the application to examine the platform it ran on before it actually attempted to load *any 3rd party DLLs* (ie, not part of the .NET Framework), check the platform and IntPtr size and pull the filenames for the correct versions from a DB and load them dynamically.

If I simply adopt a dynamic loading strategy as above, I lose the intellisense coding option, and type safety with the background compiler, plus gain all the overhead of using Reflection everywhere just to make method calls. This would affect hundreds-of-thousands of lines of code and create a big code-change overhead.

Is there a simple way to load the correct DLLs from the GAC and still add VS.NET project references against them on a development machine so that intellisense is maintained? In other words, at run-time, if the version is wrong, make the program refuse to load what the compiled reference is and load the correct version from the GAC?
# re: Assembly Loading across AppDomains
by Conrad May 20, 2010 @ 4:28am
It also appears, you create a new AppDomain, but the dyanmically loaded assembly is not loaded in to it, but the original AppDomain the main program is running in. CHOLAN.R's comments appear to be correct.
# re: Assembly Loading across AppDomains
by Keith Patrick June 02, 2010 @ 2:45pm
I've been having similar problems as Conrad/Cholan.R. What I'm trying to do is load an array of bytes (was saved to a database) into a clean appdomain. When listening to my new domain's AssemblyResolve, I hear no event, and when I load my assembly, I get FileLoadException due to minor version being mismatched (which doesn't seem to make sense, as I've got a single copy of the assembly with only 1 version). Listening to the AppDomain.CurrentDomain's event gets me somewhere, but I still can't load the bytes into my new appdomain because all I have is the name of the assembly - where is the file itself?!??!?! (it isn't in any shadow copy path that I have found)
So in a nutshell, I'm getting FileLoadException if I call AppDomain.Load(Byte[]) due to minor version not matching. If I try to override the behavior with AssemblyResolve, I can trap the event, but there's really nothing I can do at that point because I have an assembly name but no physical file/location to manually load.
# re: Assembly Loading across AppDomains
by Keith Patrick June 03, 2010 @ 1:43pm
Just adding some more things I found about my situation with this problem: in my case, parent domain has an assembly A.dll (version 2), and the domain should start up a second domain and load up version 1 of A.dll. Calling _ChildDomain.Load(ABytes) fails due to a minor version mismatch. My thinking is that because Load is trying to return an assembly back to the parent domain, it's forcing the parent domain to attempt to also load A.dll, but it already has its own mismatched version and fails.
To get around this, I tried calling CreateInstanceAndUnwrap on a proxy class such that it can take in the bytes, find the child domain itself, and load it that way. However, even doing that, I get the same Fusion errors ("WRN: Comparing the assembly name resulted in the mismatch: Minor Version") when Fusion tries to look in the application directory for the assembly (and this is where I'm confused because I provided it the raw bytes, so it should not be looking for the assembly any where)
# re: Assembly Loading across AppDomains
by Uncle Bob July 13, 2010 @ 10:46pm
Well, in my case Assembly.Load called from AssemblyResolve handler raises yet another AssemblyResolve event, thus causing StackOverflowException. So I'm not quite sure it's a good idea to call Assembly.Load if you're unsure if this assembly is loaded yet. I'm using only Assembly.LoadFrom and it works just fine. Helpfully, In the other hand, LoadFrom lets me load different versions of the same assembly.
# re: Assembly Loading across AppDomains
by Donny V July 28, 2010 @ 1:52pm
My problem is that the ResolveEventHandler is not going off all the time.

I noticed that assemblies are not loaded until they are actually called.

So my project is setup like this.
--Main App
---Core dll
---Common dll
-3rd party dll

I have a function that I'm calling in Core dll.
Core dll is also referencing Common dll and using a function in there.
The Core and Common dll are both referencing the same 3rd party dll.

What is happening is when I call the function in Core dll it loads Core dll
and runs the function. That function calls a function in Common dll,
which then loads the Common dll.

Now this is where the error begins.
The next thing that should happen is the 3rd party dll that Common dll is using
should load, but it does not. I'm thinking maybe because its 3 levels deep is the reason.

Has anyone come across this and can anyone help me out.

This is the code I'm using to load assemblies.
public partial class App : Application
        protected override void OnStartup(StartupEventArgs e)
                AppDomain currentDomain = AppDomain.CurrentDomain;
                currentDomain.AssemblyResolve += new ResolveEventHandler(AppResolveEventHandler);
                WpfSingleInstance.Make("InletCleaning", this);
            catch (Exception ex)
        private Assembly AppResolveEventHandler(object sender, ResolveEventArgs args)
             *  This handler is called only when the common language runtime tries to bind to the assembly and fails.
                string CommonAssmbPath = string.Empty;
                Assembly MyAssembly = null;
                Assembly objExecutingAssemblies = Assembly.GetExecutingAssembly();
                AssemblyName[] arrReferencedAssmbNames = objExecutingAssemblies.GetReferencedAssemblies();
                //Loop through the array of referenced assembly names.
                foreach (AssemblyName strAssmbName in arrReferencedAssmbNames)
                    //Check for the assembly names that have raised the "AssemblyResolve" event.
                    if (strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(",")))
                        //Build the path of the assembly from where it has to be loaded.
                        String Folder = System.Reflection.Assembly.GetCallingAssembly().Location;
                        string appFolder = System.IO.Path.GetDirectoryName(Folder);
                        CommonAssmbPath = appFolder.Substring(0, (appFolder.LastIndexOf("\\"))) + "\\Common\\" + args.Name.Substring(0, args.Name.IndexOf(",")) + ".dll";
                        //Check to see if dll exists
                        if (File.Exists(CommonAssmbPath))
                            //Load the assembly from the specified path.                     
                            MyAssembly = Assembly.LoadFrom(CommonAssmbPath);
                            MessageBox.Show("Could not find dll [" + CommonAssmbPath + "]");
                //Return the loaded assembly.
                return MyAssembly;
            catch (Exception ex)
                return null;
# re: Assembly Loading across AppDomains
by vletroye March 28, 2012 @ 7:17am
I did combine your code with this one to avoid the infinite loops I was experiencing in the ResolveEventHandler (remove this handler asap):

Next, I did also used some code from here to prevent the Assembly files to be locked at any time (Load the assembly as a byte[]):

But without your sample, I would still be blocked as no other piece of code found on google was working for me... You saved my day ;)

# re: Assembly Loading across AppDomains
by Jared Broad December 13, 2012 @ 12:10pm
Not sure if others have this issue; but despite the event handler code above it still wouldn't cast. I solved it by removing reference to plugin.dll from host, and replacing it with a separate interface assembly, IPlugin, referenced in both the plugin and host. Then the host can successfully cast to IPlugin calling the "namespace.Plugin" class. Make Plugin class implement IPlugin interface.
namespace HostProgram {
    public class Plugin : MarshalByRefObject, IPlugin {
        public string Run(string sText) {
            return sText + "World!";

namespace HostProgram {
    public interface IPlugin {
        string Run(string sText);

namespace HostProgram {
    class Program {
        static void Main(string[] args) {
            //Attempt to load the DLL from plugin directory:
            Loader cLoader = new Loader();
            IPlugin cPlugin = cLoader.CreateInstance<IPlugin>(@"C:\random\Plugin.dll", "HostProgram.Plugin");
# re: Assembly Loading across AppDomains
by noontz March 10, 2014 @ 5:03am
Nailed it for me in a plugin scenario and I have to totally agree:

It simply doesn't make sense the current domain needs to resolve the calling assembly??

Thanks for the post & Cool runnings form a colder part of the world!
# re: Assembly Loading across AppDomains
by Michael Brown September 29, 2014 @ 12:08pm
CHOLAN.R is correct. The code loads all dependencies within the parent AppDomain, not in the domain that you created.

AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);

It does not appear possible to load the dependencies in the newly created domain which is rather pointless, and I have not found a workaround for this as of yet. It never fires events on the new app domain (try it):

this.LocalAppDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2015