views:

137

answers:

2

This one is really pissing me off. Here goes:

My goal is to load assemblies at run-time that contain embedded aspx,ascx etc. What I would also like is to not lock the assembly file on disk so I can update it at run-time without having to restart the application (I know this will leave the previous version(s) loaded).

To that end I have written a virtual path provider that does the trick. I have subscribed to the CurrentDomain.AssemblyResolve event so as to redirect the framework to my assemblies.

The problem is that the when the framework tries to compile the dynamic assembly for the aspx page I get the following:

Compiler Error Message: CS0400: The type or namespace name 'Pages' could not be found in the global namespace (are you missing an assembly reference?)

Source Error: public class app_resource_pages__version_1_0_0_0__culture_neutral__publickeytoken_null_default_aspx : global::Pages._Default, System.Web.SessionState.IRequiresSessionState, System.Web.IHttpHandle

I noticed that if I load the assembly with Assembly.Load(AssemblyName) or Assembly.LoadFrom(filename) I dont get the above error. If I load it with Assembly.Load(byte[]) (so as to not lock it), the exception is thrown but my AssemblyResolve handler, when called is returning the assembly correctly (it is called once).

So I am guessing that it is called once when the framework parses the asp markup but not when it tries to create the dynamic assembly for the aspx page.

+1  A: 

I'm not sure what's causing the missing assembly reference but if we step back a little and go to the point where your program works as expected then we have to solve a different problem. This problem is the locking of the loaded assembly. .Net framework always locks the loaded assemblies. The reason that you can update dll files inside bin folder is actually a trick. You see, AppDomain has a nice property called ShadowCopyDirectories that dictates the directories which will be shadow copied when an assembly is loaded. So by altering the shadow copied directories list you can load from any folder without locking your assemblies :

    protected const string ApplicationAssembliesFolder = "~/Assemblies";

    protected void Application_Start(object sender, EventArgs e)
    {
        var assembliesPath = Server.MapPath(ApplicationAssembliesFolder);

        AppDomain.CurrentDomain.SetShadowCopyPath(
            AppDomain.CurrentDomain.SetupInformation.ShadowCopyDirectories + 
            Path.PathSeparator + assembliesPath);

        Assembly.LoadFrom(
            Path.Combine(assembliesPath, "Example.dll"));
    }
Diadistis
Yep, setting shadowcopypath does prevent the framework from locking the file on disk, but... Subsequent calls to Assembly.LoadFrom( return the same assembly even when the file no longer exists on the disk oO
John
A: 

I think I got it working with the following:

 public Assembly GetAssembly()
    {
        Assembly result = cache.Get(assemblyKey) as Assembly;

        if (result == null)
        {
            lock (this)
            {
                result = cache.Get(assemblyKey) as Assembly;
                if (result == null)
                {
                    assemblyName = System.Reflection.AssemblyName.GetAssemblyName(assemblyFile);
                    result = Assembly.Load(assemblyName);
                    cache.Add(assemblyKey, result, new CacheDependency(assemblyFile), Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.High, new CacheItemRemovedCallback(OnAssemblyRemoved));
                }
            }
        }
        return result;
    }

This only works when the new assembly has a different version from the old one which kind of makes sense. The application does not restart but the new assembly is loaded. I tried updating both the markup of the aspx page and the codebehind and it worked as expected.

To summarize:

  1. AppDomain.CurrentDomain.SetShadowCopyPath
  2. Assembly.Load(AssemblyName.GetAssemblyName(assemblyFile))
John