views:

155

answers:

5

I'm using a custom framework that uses reflection to do a GetTypeByName(string fullName) on the fully-qualified type name that it gets from the database, to create an instance of said type and add it to the page, resulting in a standard modular kind of thing.

GetTypeByName is a utility function of mine that simply iterates through Thread.GetDomain().GetAssemblies(), then performs an assembly.GetType(fullName) to find the relevant type. Obviously this result gets cached for future reference and speed.

However, I'm experiencing some issues whereby if the web.config gets updated (and, in some scarier instances if the application pool gets recycled) then it will lose all knowledge of certain assemblies, resulting in the inability to render an instance of the module type. Debugging shows that the missing assembly literally does not exist in the current thread assemblies list.

To get around this I added a second check which is a bit dirty but recurses through the /bin/ directory's DLLs and checks that each one exists in the assemblies list. If it doesn't, it loads it using Assembly.Load and fixing the context issue thanks to 'Solving the Assembly Load Context Problem'.

This would work, only it seems that (and I'm aware this shouldn't be possible) some projects still have access to the missing assembly, for example my actual web project rather than the framework itself - and it then complains that duplicate references have been added!

Has anyone ever heard of anything like this, or have any ideas why an assembly would simply drop out of existence on a config change? Short of a solution, what is the most elegant workaround to get all the assemblies in the bin to reload? It needs to be all in one "hit" so that the site visitors don't see any difference other than a small delay, so an app_offline.htm file is out of the question. Programatically renaming a DLL in the bin and then naming it back does work, but requires "modify" permissions for the IIS user account, which is insane.

Thanks for any pointers the community can gather!

+1  A: 

I've never heard of this problem before.

I'm not sure if this will work, as I only recently read about it while researching workarounds to ODAC dependencies, but specifying the probing path for assemblies may fix your issue.

see: http://msdn.microsoft.com/en-us/library/823z9h8w(VS.80).aspx

sample:

<configuration>
   <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
         <probing privatePath="bin;bin2\subbin;bin3"/>
      </assemblyBinding>
   </runtime>
</configuration>
Jim Schubert
Thanks for the idea Jim, but unfortunately that didn't help it out. I even tried making a copy of the bin and telling it to look there but to no avail!
tags2k
+1  A: 

I have a similar problem, when I update 2-5 files, ether web.config, ether other files, and asp.net needs to recreate the running files, then some times did not find some classes/function that exist on dll files, and my system is not working - throw errors like yours.

My solution to that is that I place the app_offline.htm on the root, make my updates, then rename/remove the app_offline.htm and my system works fine.

I am sure that have something to do with the cached compiled files, but I did not have deaply search whats exactly cause that.

[what is the most elegant workaround to get all the assemblies in the bin to reload]

Now what is the most elegant workaround on this is to call the HttpRuntime.UnloadAppDomain and actually make your application to stop and starts again.

http://msdn.microsoft.com/en-us/library/system.web.httpruntime.unloadappdomain(VS.80).aspx

I do not know if this solve your issue, you need to make tests.

probably on Global.asax make something like that

void Application_Error(object sender, EventArgs e) 
{
   Exception ex = Server.GetLastError().GetBaseException();
   ...if ex is your error, and you get more than 2 ...
   {
     HttpRuntime.UnloadAppDomain();
   }
}
Aristos
Thanks Aristos! It's good to hear that I'm not the only one! In terms of your solution, I tried implementing this before with a redirect just before the call so that the visitor who triggers the reload doesn't notice anything except a small delay while the app starts. However, I'm not sure if the UnloadAppDomain call did the job as I got an "Object moved to here" page, with no loading of my site. I'll give this method another shot.
tags2k
yes please make a test on this method. I have implement this method my self and its working, but I do not know what happens in your case.
Aristos
Hi Aristos, I tried this out again but had the same problem - with no redirect I get the error; if I redirect the user then I get an "object moved to here" page. Thanks for your input.
tags2k
@tags2k I am sorry to hear that - I left the answer just for show that did not work.
Aristos
+2  A: 

As you obviously know, there are many situations where the current appdomain is unloaded and reloaded. After each reload, all assemblies are unloaded and the whole application starts running "from scratch".

Assemblies are by default loaded on demand. Usually that is the case when the JIT stumbles across some reference. In consequence, a appdomain reload will clear out the assemblies in the appdomain and they will only appear again later on when the JIT loads them.

As solution I'd rever to using the static Type.GetType() method and supply an assembly qualified name (e.g. a type name with the assembly name included). That's the same thing the framework uses when loading types specified in the config file, and it will make sure that the required assembly is searched and loaded on demand without using any tricks. See the remarks section of the method above (the method name above name is a link).

This will require updates to your database to hold assembly qualified names instead of "only" fully qualified type names. However, it also makes sure that you don't run into name collisions when two assemblies provide different type with the same name, so this is a good idea anyways I think.

Lucero
My issue with using `Type.GetType()` (and the reason I didn't decide upon it in the initial design phase) was that if I store the type name of version 1.1.0.0 of an assembly and then upgrade to version 1.1.0.1, I'm left with none of the previous modules working.When I then can't load the type anymore because it's a different version, I've got to employ even dirtier tricks to re-reference the modules to the right version. Is there a way of storing the namespace such that it will allow any future version of the assembly?
tags2k
Yes, just leave the version out. This will at least work for assemblies which aren't strong named. Or you could add assembly version redirections via policy. http://msdn.microsoft.com/en-us/library/7wd6ex19.aspx
Lucero
As far as the version information goes .net binds against the assembly version of a dll where as windows and MSI use File Versions. You can increment the file version independently of the assembly version so that .net binding will not break after every new build.For example: Your assembly version number could stay at 1.1.0.0 and your file version number can increment a build number after every build.
Aaron Carlson
A: 

I would try to create some basic class from which assembly, which is interesting for you without reflection on Application Start to make sure it is loaded. E.g.

var temp = new BaseModuleBuilder();

This do not look smart, but it is very straitforward and asp.net should do everything for you. In case when your list is too dynamic, it could be something like

var temp = Activator.CreateInstance(Type.GetType("BaseModuleBuilder, Modules.dll"));

Make sure to always specify DLL when working with dynacmically loaded classes.

Sergey Osypchuk
Thanks Sergey, but that's not possible as it would require a circular assembly reference!
tags2k
+2  A: 

Generally, you should avoid relying on what assemblies are currently loaded in an appdomain, as that happens dynamically. Instead, simply call System.Web.Compilation.BuildManager.GetType() instead of Type.GetType() or Assembly.GetType(). This should just do the right thing for you, and not be affected by appdomain cycles.

David Ebbo
Thanks David, I've never heard of that class before but it just helped my butt big-time! Thanks so much - here's your bounty! :D
tags2k
Glad I could help! I guess we didn't do a very good job documenting the various BuildManager methods. I'll try to blog some of it. :)
David Ebbo
asbjornu
asbjornu: you can look for it yourself, but use the set of assemblies returned by BuildManager.GetReferencedAssemblies().
David Ebbo