It never fails. You need to reflect through all of the assemblies in you web site and you find that the reflection is missing some of the classes. Asp.net is smart and it does not load a given assembly until it is needed. So how can you reflect through all of the assemblies in your web site? Well first, you don't. Odds are you do not need to reflect through the System and Microsoft assemblies. What you need is a list of the custom assemblies in your web site; Once you have a list of the assemblies reflecting them becomes a simple foreach loop.
To accomplish this I created an AssemblyList class in my Utilities namespace. It has a static List<Assembly> to store the assemblies. Because the list is private I added a ClearAssemblies method to empty the list so we can repopulate it. Since adding, removing or changing an assembly should cause an asp.net application to restart this method should ever be needed.
I added a IsVaild method to the class that takes the name of the assembly and check to see if it starts with a name that is (hopefully) only used by Microsoft or that we would not want to include in our reflection calls. If we did not use this method to limit our list a default web site project would include System, System.Configuration, System.Core, System.Data, System.Data.DataSetextensions, System.Drawing,.... etc etc and all of the assemblies they reference. The .net framework is large and you do not want to reflect the whole thing if you can help.
There are several overloads of the LoadAssemblies method. The first one LoadAssemblies() tries to load the applications entry assembly, the assembly that called the executing assembly and executing assembly and any assemblies they reference into the list.
The second overload, LoadAssemblies(String startLocation, Assembly assembly), tries to load all the assemblies in the "startLocation" and the passed in assembly. Really this overload just calls two of the other overloads.
The third overload, LoadAssemblies(String path), takes the file path of where the assemblies are stored and tries to load all of the "Vaild" ones it finds. Hint- Server.MapPath("~/bin");
The fourth and final overload, LoadAssemblies(Assembly assembly), uses recursion to load all the referenced assemblies of the pass in assembly and tries to load them as well.
The final method in the static class is GetList(). As its name implies it returns the current list of assemblies.
The trick is where and how do you call LoadAssemblies. Before we get into that, here is the code for the AssemblyList class.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; namespace ObjectHelpDesk.Utilities { public static class AssemblyList { private static List<Assembly> _assemblies = null; /// <summary> /// Determines whether the assembly with the specified name is vaild. /// </summary> /// <param name="name">The name of the assembly.</param> /// <returns> /// <c>true</c> if the specified name is vaild; otherwise, <c>false</c>. /// </returns> private static Boolean IsVaild(String name) { String loweredName = name.ToLower(); Boolean result = true; if (String.IsNullOrEmpty(loweredName)) result = false; else if (loweredName.StartsWith("mscor")) result = false; else if (loweredName.StartsWith("ajax")) result = false; else if (loweredName.StartsWith("system")) result = false; else if (loweredName.StartsWith("microsoft")) result = false; else if (loweredName.StartsWith("ms.")) result = false; else if (loweredName.StartsWith("aspnet_")) result = false; return result; } /// <summary> /// Clears the assemblies list so they can be reloaded. /// </summary> public static void ClearAssemblies() { _assemblies = null; } /// <summary> /// Tries to load the assemblies from the entry, call and excuting assemblies. /// </summary> public static void LoadAssemblies() { if (_assemblies != null) return; Assembly assembly = Assembly.GetEntryAssembly(); if (assembly != null) LoadAssemblies(assembly); assembly = Assembly.GetCallingAssembly(); if (assembly != null) LoadAssemblies(assembly); assembly = Assembly.GetExecutingAssembly(); if (assembly != null) LoadAssemblies(assembly); } /// <summary> /// Loads the assemblies from a path and an assmebly. /// </summary> /// <param name="startLocation">The start location.</param> /// <param name="assembly">The assembly.</param> public static void LoadAssemblies(String startLocation, Assembly assembly) { LoadAssemblies(startLocation); LoadAssemblies(assembly); } /// <summary> /// Loads the assemblies found at the path into the list of assemblies. /// </summary> /// <param name="path">The path to search for assemblies (the bin directory).</param> private static void LoadAssemblies(String path) { if (_assemblies == null) _assemblies = new List<Assembly>(); foreach (String fileName in System.IO.Directory.GetFiles(path, "*.dll")) { AssemblyName assemblyName = AssemblyName.GetAssemblyName(fileName); if (IsVaild(assemblyName.Name)) { Assembly assembly = Assembly.Load(assemblyName); LoadAssemblies(assembly); } } } /// <summary> /// Loads the specific assembly and any assembiles it references into the list. /// </summary> /// <param name="assembly">The assembly.</param> private static void LoadAssemblies(Assembly assembly) { if (_assemblies == null) _assemblies = new List<Assembly>(); if (IsVaild(assembly.GetName().Name)) { if (!_assemblies.Contains(assembly)) { _assemblies.Add(assembly); AssemblyName[] names = assembly.GetReferencedAssemblies(); foreach (AssemblyName name in names) { if (IsVaild(name.Name)) { Assembly asm = Assembly.Load(name); LoadAssemblies(asm); } } } } } /// <summary> /// Gets the list of assemblies. /// </summary> /// <returns></returns> public static List<Assembly> GetList() { if (_assemblies == null) LoadAssemblies(); return _assemblies; } } }
While I am loath to put anything in the global.asax, there are times where it is the best place to add the code. I have a test web site project called "WebPortal" and I added a global.asax. Here is the code -
using System; using System.Collections; using System.Configuration; using System.Data; using System.Linq; using System.Web; using System.Web.Security; using System.Web.SessionState; using System.Xml.Linq; namespace WebPortal { public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { ObjectHelpDesk.Utilities.AssemblyList.LoadAssemblies(Server.MapPath("~/bin"), System.Reflection.Assembly.GetExecutingAssembly()); } protected void Session_Start(object sender, EventArgs e) { } protected void Application_BeginRequest(object sender, EventArgs e) { } protected void Application_AuthenticateRequest(object sender, EventArgs e) { } protected void Application_Error(object sender, EventArgs e) { } protected void Session_End(object sender, EventArgs e) { } protected void Application_End(object sender, EventArgs e) { } } }
Notice I used Server.MapPath to get the sites bin directory and I passed the executing assembly.
Just to test the code I added a AssemblyList.aspx page to the webPortal project.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="AssemblyList.aspx.cs" Inherits="WebPortal.AssemblyList" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Untitled Page</title> </head> <body> <form id="form1" runat="server"> <asp:Label runat="server" ID="lblAssemblies" /> </form> </body> </html>
I modified the Page_Load to write out the assembly list.
using System; using System.Collections; using System.Configuration; using System.Data; using System.Linq; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Xml.Linq; using System.Text; using System.Reflection; namespace WebPortal { public partial class AssemblyList : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { StringBuilder sb = new StringBuilder(); foreach (Assembly item in ObjectHelpDesk.Utilities.AssemblyList.GetList()) { sb.AppendFormat("{0}<br/>\r\n", item.GetName().Name); } lblAssemblies.Text = sb.ToString(); } } }
And for the WebPortal project the global.asax Application start ended up looking like this
protected void Application_Start(object sender, EventArgs e) { ObjectHelpDesk.Utilities.AssemblyList.LoadAssemblies(Server.MapPath("~/bin"), System.Reflection.Assembly.GetExecutingAssembly()); }
Now you can use reflection on the assemblies in you project and look in those 3rd party assemblies for classes that have your Interface, inherit your class or whatever else you may need to use reflection to find.
How do you use it to find your class? Here is one possibility
Type t = null; foreach (Assembly item in ObjectHelpDesk.Utilities.AssemblyList.GetList()) { t = item.GetType("MyUberClass", false, true); if (t != null) break; }
Happy coding,
-Jeff