Assembly unavailable after Web.config change - asp.net

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!

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.

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.

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>

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();
}
}

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.

Related

Biztalk insists "This Assembler cannot retrieve a document specification using this type" but I am confident it is wrong

First and foremost I would like to draw your attention to this:
That's because I have been to all of those links. I've tried those suggestions (I'll get into that). I've even been to the second page of Google.
I'm working on an existing setup which I think I might have broke when I imported bindings from another server without backing up the current bindings. When the imported bindings failed I had nothing to revert to. My bad, I know you are always supposed to back things up but this time I did not. Anyways, or maybe the bindings have nothing to do with any of it, I really don't know.
When executing a stored procedure it says This Assembler cannot retrieve a document specification using this type: "http://Foo.Bar.SQLIO#A_sqli". Now the namespace and database server are covered up here but trust me, they are correct.
Now this is where people will insist the only possible reason this ever happens is either:
There is a namespace collision and you have to explicitly specify the strong name.
The assembly is not in the GAC.
So let's talk about 1 first... So I'm gonna grab the SchemaStrongName, AssemblyName AKA Foo.Bar.SQLIO, Foo.Bar, Version=1.5.0.0, Culture=neutral, PublicKeyToken=585b3f1e468ca8f5 and paste that into the DocumentSpecNames of the XMLTransmit Send pipeline on the Send Port.
Now I rerun and the namespace ambiguity/collision/problems should be gone but they are not. Instead I get a new error which basically says the same thing.
Okay but I am pretty sure that the schema exists exactly once and the dll is deployed to the GAC correctly. Why? Several reasons:
First, the schema is listed only once. .
I can further validate that by programmatically referencing the assembly in a standalone console app, and printing the schema to a file.
static void Main(string[] args)
{
var x = new Foo.Bar.SQLIO();
Console.WriteLine(x.XmlContent);
File.WriteAllText(#"C:\Users\CoolSean\Desktop\OUT-OF-ASSEMBLY.xsd", x.XmlContent);
}
As for the dll being in the GAC I can verify that with gacutil -l Foo.Bar and it is there. If I do gacutil -u Foo.Bar and restart the host instances it fails because it can't find the assembly, meaning I am poking at the right assembly. It does exist and it does contain the schema and even the BizTalkMgmtDb database knows about it.
What can I do to run this down? I've got a working copy of this BizTalk application on another server and I setup a SQL Profiler trace on the broken box and the working box. The database calls are identical right up until the broken box starts logging errors about how it can't find that schema. idk man. What if I recompile Foo.Bar.dll such that it writes to a file any time anyone calls any of its methods. That would tell me ....something. Maybe. Probably not. I'm out of ideas.

What happens when a cshtml page changes on IIS?

To be more specific, when a cshtml is needed, what happens? I would need to build an application with only ashx and a template engine, without the MVC stuffs, simple rendering, but i would'not like to loose the capability to change the cshtml files on the fly. So I have somehow to manage their recompilation, possibly without recycling the application server loading and unloading AppDomains.
On the first request I can build the page and load it in a sort of cache (like RazorEngine on codeplex), then reuse it. When the page changes i should change the page and that's it.
But as far as I understood a new assembly is built for every page, so if there are frequent changes (that is likely to happens in my environment) tons of assemblies will get loaded.
The question is,
How it works for the standard MVC ?
CodeDom is used, or directly IL that is subsequently added without creating new assemblies?
One thing that comes me in mind now is that after building the assembly, it can be decompiled and then the IL loaded directly on the app, that would make sense
Ask for further clarification if it's unclear! (and any suggestion to this Stackoverflow newbie is welcome)
Ok,
First the cshtml files are loaded then the files are compild into their own assemblies with the standard CSharp/VbCodeCompiler, no code dom is present as far as i understood...

cannot be cast to [B]; Same context (Default); Different Temp File

I'm having difficulty finding why exactly the following error is happening. I'll outline the puzzling aspects below the error description.
[A]ASP.common_resultmessagepanel_ascx cannot be cast to[B]ASP.common_resultmessagepanel_ascx.
Type A originates from 'App_Web_resultmessagepanel.ascx.38131f0b.2c4hpv_z, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
in the context 'Default' at location
'C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\MyWebApp\dc3e0df6\ba1606c8\App_Web_resultmessagepanel.ascx.38131f0b.2c4hpv_z.dll'.
Type B originates from 'App_Web_wz3shqfq, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
in the context 'Default' at location
'C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\MyWebApp\dc3e0df6\ba1606c8\App_Web_wz3shqfq.dll'.
The class referenced in the error is a web user control inheriting from System.Web.UI.UserControl and implementing System.Web.UI.ITextControl. The control is registered and used on a master page. None of the parent master pages or implementing pages have instances of the control. The class and the markup page are both in the web application project. The exception does not happen as a direct result of the application code, it happens during internal .NET Framework code execution. The project is a web application, not a web site. The web application is compiled into a single binary, with culture specific resources compiled into one binary per culture.
The context reported for each type in the exception is the same, but I was able to verify that when the exception occurs there are in fact 2 separate class definitions in the Temporary ASP.NET Files folder for the application.
The user control has always existed and was used in the application, but the exception first started happening after the user control was added to a master page.
The exception does not happen consistently. Once the temporary files get created, the exception will happen every time the page is requested. If anything causes the temporary files to be cleared or recreated, it is random as to whether the duplicate temporary class definitions/DLLs will be created again. This could be a web.config change, recycling the app pool, sometimes even just an updated/rebuilt web application binary.
The last bit of the stack trace:
ASP.Default.__BuildControl__control35(Control ctrl) in C:\Projects\ABC.Web\App_Themes\Default\CheckBox.skin:3
System.Web.UI.ControlSkin.ApplySkin(Control control) +12
System.Web.UI.PageTheme.ApplyControlSkin(Control control) +119
System.Web.UI.Control.ApplyStyleSheetSkin(Page page) +61
ASP.masterpages_mymaster_master.__BuildControlpnlResults() in C:\Projects\ABC.Web\MasterPages\MyMaster.master:10
ASP.masterpages_mymaster_master.__BuildControl__control2(Control __ctrl) in C:\Projects\ABC.Web\MasterPages\MyMaster.master:9
System.Web.UI.CompiledTemplateBuilder.InstantiateIn(Control container) +12
System.Web.UI.MasterPage.InstantiateInContentPlaceHolder(Control contentPlaceHolder, ITemplate template) +87
The supposed offending source (the only line in the skin file C:\Projects\ABC.Web\App_Themes\Default\CheckBox.skin):
<asp:CheckBox runat="server" SkinID="FormInput" CssClass="FormLabel FormInputCheckBox" />
At this point I don't know if this issue is caused by the solution, its configuration, IIS and the app pool, or something related to the actual temp file directory itself where maybe old files are not getting cleared out. I've verified that the temp folder is not being indexed by the OS.
I'm worried that in a production environment, the app pool will recycle or some configuration setting will change and cause those temp files to be recreated with the duplicate class definition, and thus the error. We can't have someone testing the application every time the app pool recycles and deleting temp files if the error occurs until the application loads correctly. So I need to find out what is causing the duplication, but at this point I don't really know where else to investigate.
Any ideas?
I've removed the user control from the master page, and put it directly into each of the pages that required it and were implementing the master page.
So far the exception hasn't happened again. I'm going to give it another couple days of test time to see if it crops up again.
I still want to know why the exception was happening at all. Anyone with in-depth knowledge of how IIS runs .net web apps, or how the temp files are created?
New theory!
While it is a web project with a compiled binary, the IIS instance I am running for development is pointed to the project folder. So the source code files are actually in the web path. I think IIS might be compiling the source code files into separate binaries, especially if the app pool recycles. Thus accounting for the duplicated temp files that are being created and the error.
Other developers were experiencing the errors while running the project from within visual studio. I don't know how this would account for those cases, but I wouldn't rule it out as being the cause either.
I'm not sure what's happening in your case, but I had this happen to me under the following circumstances:
Website project type, not Web Application.
/Controls folder containing many ascx usercontrols.
/Client/Controls folder containing other ascx usercontrols, some of which register and reference /Controls usercontrols.
/Controls/BadControl.ascx using /Client/Controls/DupedControl.ascx as a child control.
The compiler runs into a circular dependency as it tries to compile each folder into a separate assembly.
/Controls/BadControl.ascx needs /Client/Controls to be compiled first.
/Client/Controls needs /Controls to be compiled first.
So the compiler punts and compiles DupedControl.ascx into its own separate assembly first. Then /Controls, then /Client/Controls in which DupedControl still gets included.
At this point there are two distinct Types for DupedControl in two separate assemblies. DupedControl.ascx (markup) points to the correct Type -- let's call it TypeA, in the folder's assembly -- while BadControl's reference points to the dupe TypeB in the small extra assembly.
When a page using BadControl executes, DupedControl TypeA gets instantiated via the markup, but BadControl tries to cram it into a TypeB variable, resulting in the error you described.
The solution is to move ascx files around to get rid of the circular reference. I can't remember for certain, but I think maybe the "single page assemblies" and "fixed naming" options might also resolve it.
All that said, Web Application projects compile to a single assembly, so I didn't think this kind of circular folder reference would be possible. Perhaps the problem lies elsewhere.
After a year and a half of seeing this error intermittently pop up for developers in our team, I've finally been able to gather enough data to draw some conclusions.
The key elements in the scenario causing the error are source code files in the web path, and low available memory on the dev machine that is running the application. The low memory condition causes the application pool to recycle or release memory more frequently than it would in a dedicated web hosting environment. When the memory containing the compiled web app code is released, and then a page is requested, the compiled code is reloaded into app pool memory. Since source code files are in the web path, .NET recompiles from the source code files and reloads into memory.
This situation does not happen in a dedicated hosting environment where only the compiled DLL and static files are deployed, and has never happened in our production environment. Additionally memory usage in a dedicated environment should ideally never reach a point where frequent app pool recycling is necessary.
The Visual Studio solution consists of several projects, and developers typically have multiple VS instances, a SQL Server Mgmt instance, and other sundry processes running which cause low available memory on dev machines. The lower the available memory, the more frequently and reliably the error will happen.
To clear the error state, an application pool flush / iisreset will clear out memory, and then a rebuild will usually fix the problem. If available memory is still low, the problem may persist until more memory is available in which to run the application. Simply closing down some applications or otherwise releasing memory back to the OS should do the trick.
I'm still not sure why running the app through Visual Studio's web server instead of IIS has same issue, but if it handles memory the same way IIS does, it stands to reason that the behavior is the same.
SOLVED!
I had similar problem, caused from LoadControl() strange behaviour.. and solved not instantiating my control before.
strange but true..
MyUserControl myuc = new MyUserControl();
myuo = (MyUserControl)Page.LoadControl("~/UserControls/MyUserCOntrol.ascx");
doesnt work
MyUserControl myuc = (MyUserControl)Page.LoadControl("~/UserControls/MyUserCOntrol.ascx");
works
I belive we found a solution to this problem. We always tried fixing this by deleting the whole "Temporary ASP.NET Files" folder.
At some point this solution would no longer be good enough and the error kept popping up every 30 minutes or so. We found that deleting just the file responsible and then restarting the application pool of the application in question is a permenant fix (at least for us). So for you case you would delete the follwing file (bold) and then restart the applications application pool:
[A]ASP.common_resultmessagepanel_ascx cannot be cast to
[B]ASP.common_resultmessagepanel_ascx.
Type A originates from 'App_Web_resultmessagepanel.ascx.38131f0b.2c4hpv_z, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
in the context 'Default' at location
'C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\MyWebApp\dc3e0df6\ba1606c8\App_Web_resultmessagepanel.ascx.38131f0b.2c4hpv_z.dll'.
Type B originates from 'App_Web_wz3shqfq, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
in the context 'Default' at location
'C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\MyWebApp\dc3e0df6\ba1606c8\App_Web_wz3shqfq.dll'.
This permenantly fixed the problem for us.
EDIT:
Found a permenant solution, at least for us.
In web.config look for tag and add batch="false" to it. So it would look something like:
compilation debug="false" targetFramework="4.7.2" batch="false"
The error never repeated after this.

Is there a way to run code when an assembly loads?

I'm building an application that will load untrusted assemblies for inspection (i.e. retrieval of the assembly full name). For security reasons, I'm trying to think of a way that these assemblies could be written that would allow them to execute code when loaded. I haven't code up with a method yet, but wanted to throw it out here to see if anyone could.
I'm aware that I could load these assemblies into an untrusted app domain, effectively stopping them from doing almost anything, but I wanted to avoid the complexity if it's un-needed.
Specifically, I will be calling Assembly.Load and <LoadedAssebmly>.FullName. Maybe there's a better way to load the assembly name without using the Assembly class?
Thanks,
Matt
First of all, there's the AssemblyName class. It allows you to find the assembly's name without loading it. Second, you can load assemblies using Assembly.ReflectionOnlyLoad, which uses the reflection-only context -- no code can be executed from such an assembly.
Yes, it is possible: .Net: Running code when assembly is loaded
I suggest, you use a method to inspect the assembly, that doesn't load it, i.e. Mono.Cecil

ASP.NET - How do I stop classes/dlls from one project being compiled into another project

I have one of those annoying problems where something that used to work stopped working.
Check out this code:
Assembly _abc_assembly = Assembly.LoadFile(“c:\junk\abcabstract\bin\abc.dll”);
ABC.ContentAttribute attribute;
attribute = (ABC.ContentAttribute)_abc_assembly.CreateInstance("ABC.TextAttribute");
ContentAttribute is defined in the dll.
Obviously, this should work. You should be able to cast an object to itself.
But it produces this error:
alt text http://www.yart.com.au/stackoverflow/compile1.png
This bug is discussed here http://www.yoda.arachsys.com/csharp/plugin.html which is how I got even this far. From this post I gather that the class ContentAttribute is somehow ending up in ABC.DLL and the website project's DLL.
The website project I have looks like this:
alt text http://www.yart.com.au/stackoverflow/compile2.png
Now ContentAttribute is not in this project, it is in the dll ABC.DLL. You can see that as I have expanded every branch and the file ContentAttribute.cs is not there.
Yet somehow it is ending up in the dll for the website creating a duplicating reference. ContentAttribute is somehow ending up in ABC.DLL and the website project's DLL.
Can anyone tell me:
a) Why is ContentAttribute in two dlls? I didn’t think including a dll in a project forced that code into the projects DLL.
b) How to stop it from happening?
By the way, I definitely don't want to change the website project into a website application if I can avoid it.
Notes:
Deleting the temporary ASP.NET files does not work. As soon as I compile my website project they get recreated.
alt text http://www.yart.com.au/stackoverflow/compile3.png
It's a namespace collision. It doesnt know which ContentAttribute to use since it is finding 2 in different namespaces/assemblies.
You may have an old copy of the DLL named differently. Delete your temporary ASP.Net directories and recompile.
To avoid in the future:
Use fully qualified names for your objects if you need to get it to work.
ABC.ContentAttribute ca = new ABC.ContentAttribute();
or if casting do the same
ABC.ContentAttribute ca =(ABC.ContentAttribute)ca2;
ABC.DLL is referenced by your website and it becomes part of it (it is in Bin folder). ASP.NET compiles your website and ABC.DLL is placed in temp location (C:\Windows...\Temporary ASP.NET Filse...). It gets loaded by ASP.NET automatically. You are trying to load ABC.DLL manually from different location (D:\junk\abcabstract\bin\abc.dl). Two assemblies do not match hence you get the error.
To stop this from happening you have to rethink your plugin architecture I guess. Can you give more information?
Update:
Why don't you fix it like this:
// Assembly _abc_assembly = Assembly.LoadFile(“c:\junk\abcabstract\bin\abc.dll”);
// ContentAttribute attribute;
// attribute = (ContentAttribute)_abc_assembly.CreateInstance("ABC.TextAttribute");
ContentAttribute attribute = new ContentAttribute();

Categories

Resources