How to Load All Assemblies from Within Your /Bin Directory

how to load all assemblies from within your /bin directory

Well, you can hack this together yourself with the following methods, initially use something like:

string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

to get the path to your current assembly. Next, iterate over all DLL's in the path using the Directory.GetFiles method with a suitable filter. Your final code should look like:

List<Assembly> allAssemblies = new List<Assembly>();
string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

foreach (string dll in Directory.GetFiles(path, "*.dll"))
allAssemblies.Add(Assembly.LoadFile(dll));

Please note that I haven't tested this so you may need to check that dll actually contains the full path (and concatenate path if it doesn't)

Can I tell my application to load all assemblies (DLLs) from a specific location?

Yes, shared files (if for some reason you do not want to put them in GAC) can be deployed, for example, in the Common Programs folder.

According to the way your application works you may need to load them explictly with Assembly.LoadFrom() using the path you get from Environment.GetSpecialFolder() for Environment.SpecialFolders.CommonPrograms or attaching and event handler for AppDomain.AssemblyResolve event.

Let's see a raw and simple example:

// Put this in your initialization code
public static void Main()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveAssembly(MyResolveEventHandler);
}

private static Assembly ResolveAssembly(object sender, ResolveEventArgs e)
{
// Clean, check, double check and verify path name to be sure it's a valid
// assembly name and it's not a relative path itself, you may even check e.RequestingAssembly
string path = ...;

return Assembly.LoadFrom(path);
}

If you have the directory and you want to load them all at startup (just in case...):

private static LoadThemAll(folderPath)
{
foreach (var path in Directory.GetFiles(folderPath, "*.dll"))
Assembly.LoadFrom(path);
}

Do not forget to add proper error handling (for non managed DLLs, wrong versions and everything else may happen, especially because that assemblies will be loaded in your AppDomain and everyone may put a malicious one in that folder).

Of course all of these can be applied to any (accessible) folder, which one is the best...well it depends on what you're trying to load and how it's deployed (you can even search in Registry to dynamically find where support files are located).

Finally in case assemblies you referenced are strong signed and they reside on a known, fixed, immutable location you can use <codebase> in your .config file.

Search for interface in all assemblies of bin

You can find them easily using Reflection and a LINQ query

var type = typeof(IRyuDice);
var types = AppDomain.CurrentDomain.GetAssemblies().ToList()
.SelectMany(a => a.GetTypes())
.Where(t => type.IsAssignableFrom(t));

AppDomain.CurrentDomain.GetAssemblies returns a System.Reflection.Assembly[] collection. Then you select all Types in that Assembly and check if your interface is used by that type.

http://msdn.microsoft.com/en-us/library/system.appdomain.getassemblies.aspx

Not all assemblies are being loaded into AppDomain from the bin folder

Adil has it, but in more detail:

The .NET CLR uses Just-In-Time compilation. Among other things, this means it loads assemblies on first use. So, despite assemblies being referenced by an assembly in use, if the references haven't yet been needed by the CLR to execute the program, they're not loaded and so will not appear in the list of assemblies in the current AppDomain.

Another thing which may or may not apply, is that if you have the same version of the assembly in the GAC, the CLR uses the GAC preferentially over local assemblies, UNLESS the path to those assemblies is specified in the DEVPATH environment variable. If this is the case and the CLR is using the GAC copy of any of the "missing" assemblies, they'll have differing CodeBase values and won't show up in your Linq query results.

One other thing: you may want to consider using the Location property instead of the CodeBase property. The Location property contains the absolute path to the assembly that was loaded at runtime. The CodeBase property is slightly different, and may not be the same for all assemblies in a full build of a project.

How to pre-load all deployed assemblies for an AppDomain

I have now been able to get something much closer to a final solution, except it's still not processing the private bin path correctly. I have replaced my previously live code with this and have also solved a few nasty runtime bugs I've had into the bargain (dynamic compilation of C# code referencing far too many dlls).

The golden rule I've since discovered is always use the load context, not the LoadFrom context, since the Load context will always be the first place .Net looks when performing a natural bind. Therefore, if you use the LoadFrom context, you will only get a hit if you actually load it from the same place that it would naturally bind it from - which isn't always easy.

This solution works both for web applications, taking into account the bin folder difference versus 'standard' apps. It can easily be extended to accommodate the PrivateBinPath problem, once I can get a reliable handle on exactly how it is read(!)

private static IEnumerable<string> GetBinFolders()
{
//TODO: The AppDomain.CurrentDomain.BaseDirectory usage is not correct in
//some cases. Need to consider PrivateBinPath too
List<string> toReturn = new List<string>();
//slightly dirty - needs reference to System.Web. Could always do it really
//nasty instead and bind the property by reflection!
if (HttpContext.Current != null)
{
toReturn.Add(HttpRuntime.BinDirectory);
}
else
{
//TODO: as before, this is where the PBP would be handled.
toReturn.Add(AppDomain.CurrentDomain.BaseDirectory);
}

return toReturn;
}

private static void PreLoadDeployedAssemblies()
{
foreach(var path in GetBinFolders())
{
PreLoadAssembliesFromPath(path);
}
}

private static void PreLoadAssembliesFromPath(string p)
{
//S.O. NOTE: ELIDED - ALL EXCEPTION HANDLING FOR BREVITY

//get all .dll files from the specified path and load the lot
FileInfo[] files = null;
//you might not want recursion - handy for localised assemblies
//though especially.
files = new DirectoryInfo(p).GetFiles("*.dll",
SearchOption.AllDirectories);

AssemblyName a = null;
string s = null;
foreach (var fi in files)
{
s = fi.FullName;
//now get the name of the assembly you've found, without loading it
//though (assuming .Net 2+ of course).
a = AssemblyName.GetAssemblyName(s);
//sanity check - make sure we don't already have an assembly loaded
//that, if this assembly name was passed to the loaded, would actually
//be resolved as that assembly. Might be unnecessary - but makes me
//happy :)
if (!AppDomain.CurrentDomain.GetAssemblies().Any(assembly =>
AssemblyName.ReferenceMatchesDefinition(a, assembly.GetName())))
{
//crucial - USE THE ASSEMBLY NAME.
//in a web app, this assembly will automatically be bound from the
//Asp.Net Temporary folder from where the site actually runs.
Assembly.Load(a);
}
}
}

First we have the method used to retrieve our chosen 'app folders'. These are the places where the user-deployed assemblies will have been deployed. It's an IEnumerable because of the PrivateBinPath edge case (it can be a series of locations), but in practise it's only ever one folder at the moment:

The next method is PreLoadDeployedAssemblies(), which gets called before doing anything (here it's listed as private static - in my code this is taken from a much larger static class that has public endpoints that will always trigger this code to be run before doing anything for the first time.

Finally there's the meat and bones. The most important thing here is to take an assembly file and get it's assembly name, which you then pass to Assembly.Load(AssemblyName) - and not to use LoadFrom.

I previously thought that LoadFrom was more reliable, and that you had to manually go and find the temporary Asp.Net folder in web apps. You don't. All you have to is know the name of an assembly that you know should definitely be loaded - and pass it to Assembly.Load. After all, that's practically what .Net's reference loading routines do :)

Equally, this approach works nicely with custom assembly probing implemented by hanging off the AppDomain.AssemblyResolve event as well: Extend the app's bin folders to any plugin container folders you may have so that they get scanned. Chances are you've already handled the AssemblyResolve event anyway to ensure they get loaded when the normal probing fails, so everything works as before.



Related Topics



Leave a reply



Submit