Dynamically loading assemblies from shadow directories


Home | Blog | Bio and Contact | CSLA .NET | CSLA Store

13 May 2006

I’ve run into a spot where I’m stuck, and I’m hoping someone has an idea.

<?xml:namespace prefix = o ns = “urn:schemas-microsoft-com:office:office” /> 

CSLA .NET 2.0 includes an ASP.NET data source control: CslaDataSource. This works well, except for one issue, which is that it doesn’t refresh the metadata for your business objects unless you close VS 2005 and reopen the IDE.   The reason for this problem is that your business assembly gets loaded into memory so the data source control can reflect against it to gather the metadata. That part works fine, but once an assembly is loaded into an AppDomain it can’t be unloaded. It is possible to unload an entire AppDomain however, and so that’s the obvious solution: load the business assembly into a temporary AppDomain.   So this is what I’m trying to do, and where I’m stuck. You see VS 2005 has a very complex way of loading assemblies into ASP.NET web projects. It actually appears to use the ASP.NET temporary file scheme to shadow the assemblies as it loads them. Each time you rebuild your solution (or a dependant assembly – like your business assembly), a new shadow directory is created.   The CslaDataSource control is loaded into the AppDomain from the first shadow directory – and from what I can tell that AppDomain never unloads, so the control is always running from that first shadow directory. And then – even if I use a temporary AppDomain – the business assembly is also loaded from that same shadow directory, even if newer ones exist.   And that’s where I’m stuck. I have no idea how to find out the current shadow directory, and even if I do odd things like hard-coding the directory, then I just get in trouble because the new AppDomain thinks it has a different Csla.dll than the AppDomain hosting the Web Forms designer.   Here’s the code that loads the Type object within the temporary AppDomain:      

public IDataSourceFieldSchema[] GetFields()

    {      

List<ObjectFieldInfo> result =

       

new List<ObjectFieldInfo>();

        System.Security.

NamedPermissionSet fulltrust =

       

new System.Security.NamedPermissionSet(“FullTrust”);

     

AppDomain tempDomain = AppDomain.CreateDomain(

       

“__temp”,

       

AppDomain.CurrentDomain.Evidence,

       

AppDomain.CurrentDomain.SetupInformation,

        fulltrust,        

new System.Security.Policy.StrongName[] { });

     

try

      {        

// load the TypeLoader object in the temp AppDomain

       

Assembly thisAssembly = Assembly.GetExecutingAssembly();

       

int id = AppDomain.CurrentDomain.Id;

       

TypeLoader loader =

          (

TypeLoader)tempDomain.CreateInstanceFromAndUnwrap(

            thisAssembly.CodeBase,

typeof(TypeLoader).FullName);

       

// load the business type in the temp AppDomain

       

Type t = loader.GetType(

          _typeAssemblyName, _typeName);                

// load the metadata from the Type object

       

if (typeof(IEnumerable).IsAssignableFrom(t))

        {          

// this is a list so get the item type

          t =

Utilities.GetChildItemType(t);

        }        

PropertyDescriptorCollection props =

         

TypeDescriptor.GetProperties(t);

       

foreach (PropertyDescriptor item in props)

         

if (item.IsBrowsable)

            result.Add(

new ObjectFieldInfo(item));

      }      

finally

      {        

AppDomain.Unload(tempDomain);

      }      

return result.ToArray();

    }   This replaces the method of the same name from ObjectViewSchema in the book.   Notice that it creates a new AppDomain and then invokes a TypeLoader class inside that AppDomain to create the Type object for the business class. The TypeLoader is a new class in Csla.dll that looks like this:  

using System;

using System.Collections.Generic;

using System.Text;

 

namespace Csla.Web.Design

{  

/// <summary>

 

/// Loads a Type object into the AppDomain.

 

/// </summary>

 

public class TypeLoader : MarshalByRefObject

  {    

/// <summary>

   

/// Returns a <see cref=”Type”>Type</see> object based on the

   

/// assembly and type information provided.

   

/// </summary>

   

/// <param name=”assemblyName”>(Optional) Assembly name containing the type.</param>

   

/// <param name=”typeName”>Full type name of the class.</param>

   

/// <remarks></remarks>

   

public Type GetType(

     

string assemblyName, string typeName)

    {      

int id = AppDomain.CurrentDomain.Id;

     

return Csla.Web.CslaDataSource.GetType(

        assemblyName, typeName);     }   } }   Since this object is created in the temporary AppDomain, the business assembly is loaded into that AppDomain. The Type object is [Serializable] and so is serialized back to the main AppDomain so the data source control can get the metadata as needed.   This actually all works – except that it doesn’t pick up new shadow directories as they are created.   Any ideas on how to figure out the proper shadow directory are appreciated.   Honestly, I can’t figure out how this works in general – because obviously some part of the VS designer picks up the new shadow directory and uses it – even though the designer apparently doesn’t. I am quite lost here.   To make matters worse, things operate entirely differently when a debugger is attached to VS than when not. When a debugger is attached to VS then nothing appears to pick up the new shadow directories – so I assume the debugger is interfering somehow. But it makes tracking down the issues really hard.