I sometimes have .Net library code that needs to work both in web applications and in console or service applications. The differences between the two environments, even for a library, are sometimes surprising. For example, I recently needed to find all the assemblies available to (i.e., in the same folder with) the application. My first implementation worked fine for the command line app, but failed miserably for the web site. After trying a few different approaches, I found one that works well. I’m recording this for the benefit of anyone else who has to perform this trick.
The “obvious” way to do this, I thought, was to use Assembly.GetExecutingAssembly().Location to find where the assembly lived, use DirectoryInfo.GetFiles() to find the dll and exe files in that location’s directory, and then use Assembly.LoadFile() to load them. This all works just fine in the command line application, but in a web site, there’s shadow copying involved – the assembly returned to you from GetExecutingAssembly isn’t in your Bin folder, it’s in a semi-randomly named folder nesteded several levels deep from your Temporary ASP.Net Files folder. To add difficulty, that one assembly (along with its pdb file, perhaps) is all that’s in the folder. Each of the assemblies in your Bin folder gets copied to a unique folder of its own before it’s loaded into the application, so there’s no easy way to find them all and iterate over them. I didn’t check this, but it’s entirely possible that the assembly isn’t copied at all until it needs to be loaded, in which case even scrubbing through the funny folders looking for assemblies wouldn’t work.
So if Assembly.Location doesn’t work, what does? Something on the AppDomain, perhaps. AppDomain.CurrentDomain.BaseDirectory works for command line apps, but for web sites it gives you the root of the site, not the bin folder. For a web site, the Bin folder is in PrivateBinPath. PrivateBinPath is null for command line apps, though. So it turns out the way to find all your assemblies is by using PrivateBinPath if it’s not null, or else BaseDirectory.
Now that we have a way to find all the available assemblies, what about loading them? If your application is doing shadow copying, you can’t use Assembly.LoadFromFile, since you’re working with assemblies from the Bin folder, but your application is loading them from the shadow copy directories with the funny names, which means if you need a type called “Foo.Bar.Baz,MyAssembly” and you load it from an assembly in the Bin folder, it won’t be compatible with the types that are already loaded in your application, because they were all loaded from different assemblies in the shadow copy folders.
A better way to load them is to ask the AppDomain to Load() them. Get the name from the file, strip the extension, pass it to AppDomain.CurrentDomain.Load(). That way it will load types from the correct assemblies – either from the application directory, or from the shadow copy directories, whichever is correct for your application.
No Comments