Wednesday, October 28, 2009 #

Legacy .NET 1.1 COM in a classic ASP application after installing the .NET 2.0 Runtime

I've just had a fun evening trying to come up with a half-suitable solution for this problem and thought it worth documenting here although my next entry isn't due for a couple of days.

As previously mentioned, we run a legacy classic ASP system for most of our clients and we're in the process of writing a replacement system in .NET.

This week we finished the user acceptance testing  on the first deployable .NET application - a small interface built as a replacement for a small portion of that classic ASP system and needed therefore to build it to a live server.

The servers are fairly locked down as you'd expect and a change request had to be submitted to get .NET Framework 3.5 installed along with the .NET 2.0 runtime. I installed some routine security updates at the same time and rebooted the servers one at a time whilst testing that the legacy sites still worked.

All seemed great until the next day when a very tired CTO turned up asking if he could get some sleep tonight because he had been receiving Nagios alerts all night telling him that the live sites were failing over.
Looking at the logs we could see that the application pools for most of the legacy sites had been recycling every hour and freezing for a vast majority of that hour - and with a bit of creative debugging the problem was isolated to the CreateObject calls to a pile of both in-house and third party managed COM objects installed on the server.

Rather than failing gracefully and logging an error, for some reason the calls were simply backing the server up and causing it to stop responding to requests.

I had a hunch that this was to do with the new version of the runtime we had installed and set about trying to prove that this was the case.

When an un-managed process (in this case our IIS worker process) is started up, it is obviously done so without initializing the .NET framework. If you then attempt to load a managed assembly into that process (in this case our COM object), the most recent version of the framework will be loaded into that process.

At the moment, only one version of the .NET Framework is capable of being loaded into a single process, and this is determined on a first come first served basis.  99% of the time if you load a .NET 1.1 assembly into a .NET 2.0 process,  things will work fine because there are few breaking changes between the two runtimes - however there are cases where this does not hold true and as people always say "your mileage may vary".

In IIS, the application was set up as being a .NET 1.1 website - but I wasn't convinced that this had much to do with anything and decided to try out a little hack based on my previous experience with desktop application development, COM and the different .NET runtimes.

When you deploy a .NET application, you can supply a configuration file with various overrides/version-mappings/etc alongside that application with the convention <applicationname>.exe.config. This means that you can also supply a configuration file for an application you don't "own" and modify that way it works on your machine.

<?xml version="1.0"?>
<configuration>
 <startup>
 <supportedRuntime version="v1.1.4322"/>
 <requiredRuntime version="v1.1.4322"/>
 </startup>
</configuration>

Some of the suggestions on the internet were to stick this in a web.config file at application root, but:

  1. I've read elsewhere that this is not supported in web applications 
  2. I had a feeling that this directive would be ignored because the web.config file would only be read when a .NET resource was requested.

I decided that my nail was much bigger, and instead created an inetsrv.exe.config, a w3wp.exe.config and a dllhost.exe.config with the above directive in and placed them in system32/inetsrv and system32 respectively.

Restarting IIS and hitting my test page with all the CreateObjects (my fail case) yielded in a success and my next step was therefore going to be to find something that didn't involve forcing all IIS processes and god knows what else to run with .NET 1.1 and .NET 1.1 only.

As previously mentioned, which runtime loaded into the worker process depends entirely on who first requests that a .NET assembly be loaded into process. The alternative in the desktop world and certain other places would be to manually invoke the .NET runtime with an explicit call to CorBindToCurrentRuntime  or similar.

I decided the best approach would be to force the IIS worker process to do this for me by invoking some .NET code in the context of the application. This would then use the framework version specified in the application and solve all the problems.


A few options were considered such as:

  1. Creating a global.asax to get executed when application starts
  2. Creating a web.config file as outlined in a few of the responses to questions like this on the internet
  3. Creating a dummy aspx file which could be requested by global.asa on application startup

1 and 2 wouldn't work because no requests are ever made to the server through the aspnet extension, so 3 seemed to be the only reasonable solution.

The following aspx file was created on the server:

<%@ Page Language="vb" AutoEventWireup="false" %>

<p>Hello World</p>

And the following code added to global.asa:

if not Application("hasInitializedNet") then

	Dim reqUrl 
	reqUrl = "http://" & Request.ServerVariables("LOCAL_ADDR") & "/default.aspx"

	Dim objXmlHttp
	Dim strHTML

	Set objXmlHttp = Server.CreateObject("Msxml2.ServerXMLHTTP")

	objXmlHttp.open "GET", reqUrl , False

	objXmlHttp.send

	strHTML = objXmlHttp.responseText
	
	Set objXmlHttp = nothing

	Application("hasInitializedNet") = true

end if


The application was restarted and voila, the page was requested when the first user hit the server and the right version of.NET was loaded into the application from the start.

I can't think of a neater way that would allow us to still have our .NET 3.5 applications on the same machine and this solution is good enough for now (Until we get another pair of servers in for the new systems).

Feel free to leave any better suggestions in the comments!


Technorati tags: , , ,

posted @ Wednesday, October 28, 2009 11:32 PM | Feedback (0)

Copyright © Rob Ashton

Design by Rob Ashton, Based On A Design By Bartosz Brzezinski