This week I wanted to get my ASP.NET web application to create a text file and return it to the browser. The catch was, the text files are created by some pretty gnarly FoxPro code (sourcing Fox data). The solution seems to be to use COM interop. It’s a lot easier to rework that vintage FoxPro procedural code into a VFP COM server than it is to re-implement it using DataSets in C# (or VB for that matter).

Rick Strahl has a good article discussing this, and there is quite a lot of “high level” information on COM interop out there on the web also. Briefly, here is what I did:

I re-packaged the pertinent FoxPro code into a class method of a COM server. I tested this out in the VFP Command window:

x = CREATEOBJECT("MyServer.MyClass")
_cliptext = x.GetTextDoc("123")

Now, in the web application I wanted a hyperlink that would return the text document to the browser. So I was looking to implement a URL like this:

<a href="CreateTextDoc.aspx?param=1234">Create Text Document</a>

Now, CreateTextDoc.aspx is basically all code-behind – there is no HTML code, just the directives:

<%@ Page language="c#" 
         Codebehind="CreateTextDoc.aspx.cs" 
         AutoEventWireup="false" 
         Inherits="MyCompany.MyProject.MyBasePage" 
         ASPCOMPAT=true
%>

That last page directive is specifically for pages that instantiate COM objects. Some people say it’s optional, only necessary when you are embedding the object into the page directly with <object> tags. Others say that you should use it even if you are using the early binding wrapper classes. I figured, better safe than sorry.

Now, the code-behind:

  using System;
  using System.Web;
  using MyServer;
  :
  private void Page_Load(object sender, System.EventArgs e)
  {

      // get the parameter:
      //
      string cParam = "";
      cParam = Request["param"] ;

      // We're returning a text file:
      //
      Response.ContentType = "text/plain";

      // This forces nicer behavior in IE, prompting to download or open as text.
      // also the filename is now "1234.txt" instead of "CreateTextDoc.aspx"
      //
      Response.AddHeader("content-disposition","attachment; filename="+cParam+".txt" ) ;
		
      // Create the COM server wrapper:
      //
      MyServer.MyClass x = new MyServer.MyClassClass() ;

      // Generate and return the text:
      //
      Response.Write( x.GetTextDoc( cParam )) ;

      return ;
  }

Nothing tricky there, once you understand the odd invocation of the COM server through the .NET wrapper class.

This worked a treat: In FireFox, the text file appears in the browser quite naturally. IE screwed up, however, because it appeared to be trying to recognise the plain text response as some other kind of format. Rather than do the registry hack, I added the content-disposition header to the response. Now the behavior in IE is actually preferable: I get a dialog prompting me to Open or Save As. If I Open, the text file opens in Notepad. This is better for my users – although I would have been content with the Firefox behavior (pardon the pun).

Now we get to the problem: Every time I hit that URL to generate a text file, another instance of my COM server executable was created. Ouch! If I waited long enough – about 20 minutes – they would terminate and disappear from Task Manager. But as it was, this didn’t scale well at all.

I mentioned this behavior to Rick, and he couldn’t explain why the MyServer.exe instances were hanging around. But he did have a good suggestion: “If the object is not releasing you can call Marshal.ReleaseComObject() to make sure it gets cleaned up and doesn’t wait for the GC to clean it up for you.

This change was easy to make. First, I added the reference to the top of the file:

using System.Runtime.InteropServices;

Then, after the call to GetTextDoc(..):

      // Create the COM server wrapper:
      //
      sisserver.ArtForm x = new sisserver.ArtFormClass() ;

      // Call the Artform listing generator:
      //
      Response.Write( x.GetArtFormDoc( cArtform ) ) ;
      
      // Dispose of the COM server manually:
      // 
     System.Runtime.InteropServices.Marshal.ReleaseComObject( x );

This worked beautifully. No more hanging executables in Task Manager! My application scales again. Thanks for the pointer, Rick.